aboutsummaryrefslogtreecommitdiff
path: root/zenserver/vfs.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2021-05-11 13:05:39 +0200
committerStefan Boberg <[email protected]>2021-05-11 13:05:39 +0200
commitf8d9ac5d13dd37b8b57af0478e77ba1e75c813aa (patch)
tree1daf7621e110d48acd5e12e3073ce48ef0dd11b2 /zenserver/vfs.cpp
downloadzen-f8d9ac5d13dd37b8b57af0478e77ba1e75c813aa.tar.xz
zen-f8d9ac5d13dd37b8b57af0478e77ba1e75c813aa.zip
Adding zenservice code
Diffstat (limited to 'zenserver/vfs.cpp')
-rw-r--r--zenserver/vfs.cpp898
1 files changed, 898 insertions, 0 deletions
diff --git a/zenserver/vfs.cpp b/zenserver/vfs.cpp
new file mode 100644
index 000000000..71f0bbdda
--- /dev/null
+++ b/zenserver/vfs.cpp
@@ -0,0 +1,898 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "vfs.h"
+
+#include <zencore/except.h>
+#include <zencore/filesystem.h>
+#include <zencore/snapshot_manifest.h>
+#include <zencore/stream.h>
+#include <zencore/windows.h>
+
+#include <map>
+
+#include <atlfile.h>
+#include <projectedfslib.h>
+#include <spdlog/spdlog.h>
+
+#pragma comment(lib, "projectedfslib.lib")
+
+namespace zen {
+
+//////////////////////////////////////////////////////////////////////////
+
+struct ProjFsCliOptions
+{
+ bool IsDebug = false;
+ bool IsClean = false;
+ std::string CasSpec;
+ std::string ManifestSpec;
+ std::string MountPoint;
+};
+
+struct GuidHasher
+{
+ size_t operator()(const GUID& Guid) const
+ {
+ static_assert(sizeof(GUID) == (sizeof(size_t) * 2));
+
+ const size_t* Ptr = reinterpret_cast<const size_t*>(&Guid);
+
+ return Ptr[0] ^ Ptr[1];
+ }
+};
+
+class ProjfsNamespace
+{
+public:
+ HRESULT Initialize(const char* SnapshotSpec, const char* CasSpec)
+ {
+ std::filesystem::path ManifestSpec = zen::ManifestSpecToPath(SnapshotSpec);
+
+ CAtlFile ManifestFile;
+ HRESULT hRes = ManifestFile.Create(ManifestSpec.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
+ if (FAILED(hRes))
+ {
+ spdlog::error("MANIFEST NOT FOUND!"); // TODO: add context
+
+ return hRes;
+ }
+
+ ULONGLONG FileLength = 0;
+ ManifestFile.GetSize(FileLength);
+
+ std::vector<uint8_t> Data;
+ Data.resize(FileLength);
+
+ ManifestFile.Read(Data.data(), (DWORD)Data.size());
+
+ zen::MemoryInStream MemoryStream(Data.data(), Data.size());
+
+ ReadManifest(/* out */ m_Manifest, MemoryStream);
+
+ uint64_t TotalBytes = 0;
+ uint64_t TotalFiles = 0;
+
+ m_Manifest.Root.VisitFiles([&](const zen::LeafNode& Node) {
+ TotalBytes += Node.FileSize;
+ TotalFiles++;
+ });
+
+ m_FileByteCount = TotalBytes;
+ m_FileCount = TotalFiles;
+
+ // CAS root
+
+ zen::CasStoreConfiguration Config;
+ Config.RootDirectory = CasSpec;
+ m_CasStore->Initialize(Config);
+
+ return S_OK;
+ }
+
+ struct LookupResult
+ {
+ const zen::TreeNode* TreeNode = nullptr;
+ const zen::LeafNode* LeafNode = nullptr;
+ };
+
+ bool IsOnCasDrive(const char* Path)
+ {
+ ZEN_UNUSED(Path);
+
+ // TODO: programmatically determine of CAS and workspace path is on same drive!
+ return true;
+ }
+
+ LookupResult LookupNode(const std::wstring& Name) const
+ {
+ if (Name.empty())
+ return {nullptr};
+
+ zen::ExtendableWideStringBuilder<MAX_PATH> LocalName;
+ LocalName.Append(Name.c_str());
+
+ // Split components
+
+ const wchar_t* PathComponents[MAX_PATH / 2];
+ size_t PathComponentCount = 0;
+
+ const size_t Length = Name.length();
+
+ wchar_t* Base = LocalName.Data();
+ wchar_t* itStart = Base;
+
+ for (int i = 0; i < Length; ++i)
+ {
+ if (Base[i] == '\\')
+ {
+ // Component separator
+
+ Base[i] = L'\0';
+
+ PathComponents[PathComponentCount++] = itStart;
+
+ itStart = Base + i + 1;
+ }
+ }
+
+ // Push final component
+ if (Name.back() != L'\\')
+ PathComponents[PathComponentCount++] = itStart;
+
+ const zen::TreeNode* Node = &m_Manifest.Root;
+
+ if (PathComponentCount == 1)
+ {
+ if (PrjFileNameCompare(L"root", Name.c_str()) == 0)
+ return {Node};
+ else
+ return {nullptr};
+ }
+
+ for (size_t i = 1; i < PathComponentCount; ++i)
+ {
+ const auto& part = PathComponents[i];
+
+ const zen::TreeNode* NextNode = nullptr;
+
+ for (const zen::TreeNode& ChildNode : Node->Children)
+ {
+ if (PrjFileNameCompare(part, ChildNode.Name.c_str()) == 0)
+ {
+ NextNode = &ChildNode;
+ break;
+ }
+ }
+
+ if (NextNode)
+ {
+ Node = NextNode;
+
+ continue;
+ }
+
+ if (i == PathComponentCount - 1)
+ {
+ for (const zen::LeafNode& Leaf : Node->Leaves)
+ {
+ if (PrjFileNameCompare(part, Leaf.Name.c_str()) == 0)
+ return {nullptr, &Leaf};
+ }
+ }
+
+ return {nullptr};
+ }
+
+ return {Node};
+ }
+
+ const zen::SnapshotManifest& Manifest() const { return m_Manifest; }
+ zen::CasStore& CasStore() { return *m_CasStore; }
+
+ uint64_t FileCount() const { return m_FileCount; }
+ uint64_t FileByteCount() const { return m_FileByteCount; }
+
+private:
+ zen::SnapshotManifest m_Manifest;
+ std::unique_ptr<zen::CasStore> m_CasStore;
+
+ size_t m_FileCount = 0;
+ size_t m_FileByteCount = 0;
+};
+
+/** Projected File System Provider
+ */
+
+class ProjfsProvider
+{
+public:
+ HRESULT ReadManifest(const char* ManifestSpec, const char* CasSpec);
+ HRESULT Initialize(std::filesystem::path RootPath, bool Clean);
+ void Cleanup();
+
+ struct Callbacks;
+
+private:
+ static void DebugPrint(const char* Format, ...);
+
+ HRESULT StartDirEnum(const PRJ_CALLBACK_DATA* CallbackData, LPCGUID EnumerationId);
+ HRESULT EndDirEnum(const PRJ_CALLBACK_DATA* CallbackData, LPCGUID EnumerationId);
+ HRESULT GetDirEnum(const PRJ_CALLBACK_DATA* CallbackData,
+ LPCGUID EnumerationId,
+ LPCWSTR SearchExpression,
+ PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle);
+ HRESULT GetPlaceholderInformation(const PRJ_CALLBACK_DATA* CallbackData);
+ HRESULT GetFileStream(const PRJ_CALLBACK_DATA* CallbackData, UINT64 ByteOffset, UINT32 Length);
+ HRESULT QueryFileName(const PRJ_CALLBACK_DATA* CallbackData);
+ HRESULT NotifyOperation(const PRJ_CALLBACK_DATA* CallbackData,
+ BOOLEAN IsDirectory,
+ PRJ_NOTIFICATION NotificationType,
+ LPCWSTR DestinationFileName,
+ PRJ_NOTIFICATION_PARAMETERS* OperationParameters);
+ void CancelCommand(const PRJ_CALLBACK_DATA* CallbackData);
+
+ class DirectoryEnumeration;
+
+ zen::RwLock m_Lock;
+ std::unordered_map<GUID, std::unique_ptr<DirectoryEnumeration>, GuidHasher> m_DirectoryEnumerators;
+ ProjfsNamespace m_Namespace;
+ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT m_PrjContext = nullptr;
+ bool m_GenerateFullFiles = false;
+};
+
+class ProjfsProvider::DirectoryEnumeration
+{
+public:
+ DirectoryEnumeration(ProjfsProvider* Outer, LPCGUID EnumerationGuid, const wchar_t* RelativePath)
+ : m_Outer(Outer)
+ , m_EnumerationId(*EnumerationGuid)
+ , m_Path(RelativePath)
+ {
+ ResetScan();
+ }
+
+ ~DirectoryEnumeration() {}
+
+ void ResetScan()
+ {
+ // Restart enumeration from beginning
+
+ m_InfoIterator = m_Infos.end();
+
+ const ProjfsNamespace::LookupResult Lookup = m_Outer->m_Namespace.LookupNode(m_Path);
+
+ if (Lookup.TreeNode == nullptr && Lookup.LeafNode == nullptr)
+ return;
+
+ if (Lookup.TreeNode)
+ {
+ const zen::TreeNode* RootNode = Lookup.TreeNode;
+
+ // Populate info array
+
+ FILETIME FileTime;
+ GetSystemTimeAsFileTime(&FileTime);
+
+ for (const zen::TreeNode& ChildNode : RootNode->Children)
+ {
+ PRJ_FILE_BASIC_INFO Fbi{0};
+
+ Fbi.IsDirectory = TRUE;
+ Fbi.FileSize = 0;
+ Fbi.CreationTime = Fbi.LastAccessTime = Fbi.LastWriteTime = Fbi.ChangeTime = *((LARGE_INTEGER*)&FileTime);
+ Fbi.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
+
+ m_Infos.insert({ChildNode.Name, Fbi});
+ }
+
+ for (const zen::LeafNode& Leaf : RootNode->Leaves)
+ {
+ PRJ_FILE_BASIC_INFO Fbi{0};
+
+ Fbi.IsDirectory = FALSE;
+ Fbi.FileSize = Leaf.FileSize;
+ Fbi.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ Fbi.CreationTime = Fbi.LastAccessTime = Fbi.LastWriteTime = Fbi.ChangeTime =
+ *reinterpret_cast<const LARGE_INTEGER*>(&Leaf.FileModifiedTime);
+
+ m_Infos.insert({Leaf.Name, Fbi});
+ }
+ }
+
+ m_InfoIterator = m_Infos.begin();
+ }
+
+ HRESULT HandleRequest(_In_ const PRJ_CALLBACK_DATA* CallbackData,
+ _In_opt_z_ LPCWSTR SearchExpression,
+ _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle)
+ {
+ int EnumLimit = INT_MAX;
+
+ DebugPrint("ENUM '%S' -> pattern %S\n", CallbackData->FilePathName, SearchExpression);
+
+ HRESULT hRes = S_OK;
+
+ if (CallbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN)
+ ResetScan();
+
+ if (m_InfoIterator == m_Infos.end())
+ return S_OK;
+
+ if (CallbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RETURN_SINGLE_ENTRY)
+ EnumLimit = 1;
+
+ if (!m_Predicate)
+ {
+ if (SearchExpression)
+ {
+ bool isWild = PrjDoesNameContainWildCards(SearchExpression);
+
+ if (isWild)
+ {
+ if (SearchExpression[0] == L'*' && SearchExpression[1] == L'\0')
+ {
+ // Trivial accept -- no need to change predicate from the default
+ }
+ else
+ {
+ m_SearchExpression = SearchExpression;
+
+ m_Predicate = [this](LPCWSTR name) { return PrjFileNameMatch(name, m_SearchExpression.c_str()); };
+ }
+ }
+ else
+ {
+ if (SearchExpression[0])
+ {
+ // Look for specific name match (does this ever happen?)
+
+ m_SearchExpression = SearchExpression;
+
+ m_Predicate = [this](LPCWSTR name) { return PrjFileNameCompare(name, m_SearchExpression.c_str()) == 0; };
+ }
+ }
+ }
+ }
+
+ if (!m_Predicate)
+ m_Predicate = [](LPCWSTR) { return true; };
+
+ while (EnumLimit && m_InfoIterator != m_Infos.end())
+ {
+ auto& ThisNode = *m_InfoIterator;
+
+ auto& Name = ThisNode.first;
+ auto& Info = ThisNode.second;
+
+ if (m_Predicate(Name.c_str()))
+ {
+ hRes = PrjFillDirEntryBuffer(Name.c_str(), &Info, DirEntryBufferHandle);
+
+ if (hRes == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
+ return S_OK;
+
+ if (FAILED(hRes))
+ break;
+
+ --EnumLimit;
+ }
+
+ ++m_InfoIterator;
+ }
+
+ return hRes;
+ }
+
+private:
+ ProjfsProvider* m_Outer = nullptr;
+ const std::wstring m_Path;
+ const GUID m_EnumerationId;
+
+ // We need to maintain an ordered list of directory items since the
+ // ProjFS enumeration code gets confused otherwise and ends up producing
+ // multiple entries for the same file if there's a 'hydrated' version
+ // present.
+
+ struct FilenameLess
+ {
+ bool operator()(const std::wstring& Lhs, const std::wstring& Rhs) const { return PrjFileNameCompare(Lhs.c_str(), Rhs.c_str()) < 0; }
+ };
+
+ typedef std::map<std::wstring, PRJ_FILE_BASIC_INFO, FilenameLess> FileInfoMap_t;
+
+ FileInfoMap_t m_Infos;
+ FileInfoMap_t::iterator m_InfoIterator;
+
+ std::wstring m_SearchExpression;
+ std::function<bool(LPCWSTR name)> m_Predicate;
+};
+
+//////////////////////////////////////////////////////////////////////////
+// Callback forwarding functions
+//
+
+struct ProjfsProvider::Callbacks
+{
+ static HRESULT CALLBACK StartDirEnum(_In_ const PRJ_CALLBACK_DATA* CallbackData, _In_ const GUID* EnumerationId)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)->StartDirEnum(CallbackData, EnumerationId);
+ }
+
+ static HRESULT CALLBACK EndDirEnum(_In_ const PRJ_CALLBACK_DATA* CallbackData, _In_ LPCGUID EnumerationId)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)->EndDirEnum(CallbackData, EnumerationId);
+ }
+
+ static HRESULT CALLBACK GetDirEnum(_In_ const PRJ_CALLBACK_DATA* CallbackData,
+ _In_ LPCGUID EnumerationId,
+ _In_opt_z_ LPCWSTR SearchExpression,
+ _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)
+ ->GetDirEnum(CallbackData, EnumerationId, SearchExpression, DirEntryBufferHandle);
+ }
+
+ static HRESULT CALLBACK GetPlaceholderInformation(_In_ const PRJ_CALLBACK_DATA* CallbackData)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)->GetPlaceholderInformation(CallbackData);
+ }
+
+ static HRESULT CALLBACK GetFileStream(_In_ const PRJ_CALLBACK_DATA* CallbackData, _In_ UINT64 ByteOffset, _In_ UINT32 Length)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)->GetFileStream(CallbackData, ByteOffset, Length);
+ }
+
+ static HRESULT CALLBACK QueryFileName(_In_ const PRJ_CALLBACK_DATA* CallbackData)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)->QueryFileName(CallbackData);
+ }
+
+ static HRESULT CALLBACK NotifyOperation(_In_ const PRJ_CALLBACK_DATA* CallbackData,
+ _In_ BOOLEAN IsDirectory,
+ _In_ PRJ_NOTIFICATION NotificationType,
+ _In_opt_ LPCWSTR DestinationFileName,
+ _Inout_ PRJ_NOTIFICATION_PARAMETERS* OperationParameters)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)
+ ->NotifyOperation(CallbackData, IsDirectory, NotificationType, DestinationFileName, OperationParameters);
+ }
+
+ static VOID CALLBACK CancelCommand(_In_ const PRJ_CALLBACK_DATA* CallbackData)
+ {
+ return reinterpret_cast<ProjfsProvider*>(CallbackData->InstanceContext)->CancelCommand(CallbackData);
+ }
+};
+
+// {6EEB94E4-3EF3-4C1C-AF15-D7FF64C19A4F}
+static const GUID ProviderGuid = {0x6eeb94e4, 0x3ef3, 0x4c1c, {0xaf, 0x15, 0xd7, 0xff, 0x64, 0xc1, 0x9a, 0x4f}};
+
+void
+ProjfsProvider::DebugPrint(const char* FmtString, ...)
+{
+ va_list vl;
+ va_start(vl, FmtString);
+
+#if 0
+ vprintf(FmtString, vl);
+#endif
+
+ va_end(vl);
+}
+
+HRESULT
+ProjfsProvider::Initialize(std::filesystem::path RootPath, bool Clean)
+{
+ PRJ_PLACEHOLDER_VERSION_INFO Pvi = {};
+ Pvi.ContentID[0] = 1;
+
+ if (Clean && std::filesystem::exists(RootPath))
+ {
+ printf("Cleaning '%S'...", RootPath.c_str());
+
+ bool success = zen::DeleteDirectories(RootPath);
+
+ if (!success)
+ {
+ printf(" retrying...");
+
+ success = zen::DeleteDirectories(RootPath);
+
+ // Failed?
+ }
+
+ printf(" done!\n");
+ }
+
+ bool RootDirectoryCreated = false;
+
+retry:
+ if (!std::filesystem::exists(RootPath))
+ {
+ zen::CreateDirectories(RootPath);
+ }
+
+ {
+ HRESULT hRes = PrjMarkDirectoryAsPlaceholder(RootPath.c_str(), nullptr, &Pvi, &ProviderGuid);
+
+ if (FAILED(hRes))
+ {
+ if (hRes == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && !RootDirectoryCreated)
+ {
+ printf("Creating '%S'...", RootPath.c_str());
+
+ std::filesystem::create_directories(RootPath.c_str());
+
+ RootDirectoryCreated = true;
+
+ printf("done!\n");
+
+ goto retry;
+ }
+ else if (hRes == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
+ {
+ throw zen::WindowsException(hRes, "Failed to initialize root placeholder");
+ }
+
+ // Ignore error, problems will be reported below anyway
+ }
+ }
+
+ // Callbacks
+
+ PRJ_CALLBACKS cbs = {};
+
+ cbs.StartDirectoryEnumerationCallback = Callbacks::StartDirEnum;
+ cbs.EndDirectoryEnumerationCallback = Callbacks::EndDirEnum;
+ cbs.GetDirectoryEnumerationCallback = Callbacks::GetDirEnum;
+ cbs.GetPlaceholderInfoCallback = Callbacks::GetPlaceholderInformation;
+ cbs.GetFileDataCallback = Callbacks::GetFileStream;
+ cbs.QueryFileNameCallback = Callbacks::QueryFileName;
+ cbs.NotificationCallback = Callbacks::NotifyOperation;
+ cbs.CancelCommandCallback = Callbacks::CancelCommand;
+
+ // Parameters
+
+ const PRJ_NOTIFY_TYPES dwNotifications = PRJ_NOTIFY_FILE_OPENED | PRJ_NOTIFY_NEW_FILE_CREATED | PRJ_NOTIFY_FILE_OVERWRITTEN |
+ PRJ_NOTIFY_PRE_DELETE | PRJ_NOTIFY_PRE_RENAME | PRJ_NOTIFY_PRE_SET_HARDLINK |
+ PRJ_NOTIFY_FILE_RENAMED | PRJ_NOTIFY_HARDLINK_CREATED |
+ PRJ_NOTIFY_FILE_HANDLE_CLOSED_NO_MODIFICATION | PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_MODIFIED |
+ PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED | PRJ_NOTIFY_FILE_PRE_CONVERT_TO_FULL;
+
+ PRJ_NOTIFICATION_MAPPING Mappings[] = {{dwNotifications, L"root"}};
+
+ PRJ_STARTVIRTUALIZING_OPTIONS SvOptions = {};
+
+ SvOptions.Flags = PRJ_FLAG_NONE;
+ SvOptions.PoolThreadCount = 8;
+ SvOptions.ConcurrentThreadCount = 8;
+ SvOptions.NotificationMappings = Mappings;
+ SvOptions.NotificationMappingsCount = 1;
+
+ HRESULT hRes = PrjStartVirtualizing(RootPath.c_str(), &cbs, this, &SvOptions, &m_PrjContext);
+
+ if (SUCCEEDED(hRes))
+ {
+ // Create dummy 'root' directory for now until I figure out how to
+ // invalidate entire trees (ProjFS won't allow invalidation of the
+ // entire provider tree).
+
+ PRJ_PLACEHOLDER_INFO pli{};
+ pli.FileBasicInfo.IsDirectory = TRUE;
+ pli.FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
+ pli.VersionInfo = Pvi;
+
+ hRes = PrjWritePlaceholderInfo(m_PrjContext, L"root", &pli, sizeof pli);
+ }
+
+ if (SUCCEEDED(hRes))
+ {
+ spdlog::info("Successfully mounted snapshot at '{}'!", WideToUtf8(RootPath.c_str()));
+ }
+ else
+ {
+ spdlog::info("Failed mounting snapshot at '{}'!", WideToUtf8(RootPath.c_str()));
+ }
+
+ return hRes;
+}
+
+void
+ProjfsProvider::Cleanup()
+{
+ PrjStopVirtualizing(m_PrjContext);
+}
+
+HRESULT
+ProjfsProvider::ReadManifest(const char* ManifestSpec, const char* CasSpec)
+{
+ printf("Initializing from manifest '%s'\n", ManifestSpec);
+
+ m_Namespace.Initialize(ManifestSpec, CasSpec);
+
+ return S_OK;
+}
+
+HRESULT
+ProjfsProvider::StartDirEnum(const PRJ_CALLBACK_DATA* CallbackData, LPCGUID EnumerationId)
+{
+ zen::RwLock::ExclusiveLockScope _(m_Lock);
+
+ m_DirectoryEnumerators[*EnumerationId] = std::make_unique<DirectoryEnumeration>(this, EnumerationId, CallbackData->FilePathName);
+
+ return S_OK;
+}
+
+HRESULT
+ProjfsProvider::EndDirEnum(const PRJ_CALLBACK_DATA* CallbackData, LPCGUID EnumerationId)
+{
+ ZEN_UNUSED(CallbackData);
+ ZEN_UNUSED(EnumerationId);
+
+ zen::RwLock::ExclusiveLockScope _(m_Lock);
+
+ m_DirectoryEnumerators.erase(*EnumerationId);
+
+ return S_OK;
+}
+
+HRESULT
+ProjfsProvider::GetDirEnum(const PRJ_CALLBACK_DATA* CallbackData,
+ LPCGUID EnumerationId,
+ LPCWSTR SearchExpression,
+ PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle)
+{
+ DirectoryEnumeration* directoryEnumerator;
+
+ {
+ zen::RwLock::SharedLockScope _(m_Lock);
+
+ auto it = m_DirectoryEnumerators.find(*EnumerationId);
+
+ if (it == m_DirectoryEnumerators.end())
+ return E_FAIL; // No enumerator associated with specified GUID
+
+ directoryEnumerator = (*it).second.get();
+ }
+
+ return directoryEnumerator->HandleRequest(CallbackData, SearchExpression, DirEntryBufferHandle);
+}
+
+HRESULT
+ProjfsProvider::GetPlaceholderInformation(const PRJ_CALLBACK_DATA* CallbackData)
+{
+ ProjfsNamespace::LookupResult result = m_Namespace.LookupNode(CallbackData->FilePathName);
+
+ if (auto Leaf = result.LeafNode)
+ {
+ PRJ_PLACEHOLDER_INFO PlaceholderInfo = {};
+
+ LARGE_INTEGER FileTime;
+ FileTime.QuadPart = Leaf->FileModifiedTime;
+
+ PlaceholderInfo.FileBasicInfo.ChangeTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.CreationTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.LastAccessTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.LastWriteTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.FileSize = Leaf->FileSize;
+ PlaceholderInfo.FileBasicInfo.IsDirectory = 0;
+ PlaceholderInfo.FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+
+ HRESULT hRes = PrjWritePlaceholderInfo(m_PrjContext, CallbackData->FilePathName, &PlaceholderInfo, sizeof PlaceholderInfo);
+
+ return hRes;
+ }
+
+ if (auto node = result.TreeNode)
+ {
+ PRJ_PLACEHOLDER_INFO PlaceholderInfo = {};
+
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+
+ LARGE_INTEGER FileTime;
+ FileTime.QuadPart = UINT64(ft.dwHighDateTime) << 32 | ft.dwLowDateTime;
+
+ PlaceholderInfo.FileBasicInfo.ChangeTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.CreationTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.LastAccessTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.LastWriteTime = FileTime;
+ PlaceholderInfo.FileBasicInfo.IsDirectory = TRUE;
+ PlaceholderInfo.FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
+
+ HRESULT hRes = PrjWritePlaceholderInfo(m_PrjContext, CallbackData->FilePathName, &PlaceholderInfo, sizeof PlaceholderInfo);
+
+ return hRes;
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+}
+
+HRESULT
+ProjfsProvider::GetFileStream(const PRJ_CALLBACK_DATA* CallbackData, UINT64 ByteOffset, UINT32 Length)
+{
+ ProjfsNamespace::LookupResult result = m_Namespace.LookupNode(CallbackData->FilePathName);
+
+ if (const zen::LeafNode* leaf = result.LeafNode)
+ {
+ zen::CasStore& casStore = m_Namespace.CasStore();
+
+ const zen::IoHash& ChunkHash = leaf->ChunkHash;
+
+ zen::IoBuffer Chunk = casStore.FindChunk(ChunkHash);
+
+ if (!Chunk)
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+
+ if (m_GenerateFullFiles)
+ {
+ DWORD chunkSize = (DWORD)Chunk.Size();
+
+ zen::StringBuilder<66> b3string;
+ DebugPrint("GET FILE STREAM: %s -> %d '%S'\n", ChunkHash.ToHexString(b3string).c_str(), chunkSize, CallbackData->FilePathName);
+
+ // TODO: implement support for chunks > 4GB
+ ZEN_ASSERT(chunkSize == Chunk.Size());
+
+ HRESULT hRes = PrjWriteFileData(m_PrjContext, &CallbackData->DataStreamId, (PVOID)Chunk.Data(), 0, chunkSize);
+
+ return hRes;
+ }
+ else
+ {
+ HRESULT hRes = PrjWriteFileData(m_PrjContext,
+ &CallbackData->DataStreamId,
+ (PVOID)(reinterpret_cast<const uint8_t*>(Chunk.Data()) + ByteOffset),
+ ByteOffset,
+ Length);
+
+ return hRes;
+ }
+ }
+
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+}
+
+HRESULT
+ProjfsProvider::QueryFileName(const PRJ_CALLBACK_DATA* CallbackData)
+{
+ ProjfsNamespace::LookupResult result = m_Namespace.LookupNode(CallbackData->FilePathName);
+
+ if (result.LeafNode || result.TreeNode)
+ return S_OK;
+
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+}
+
+HRESULT
+ProjfsProvider::NotifyOperation(const PRJ_CALLBACK_DATA* CallbackData,
+ BOOLEAN IsDirectory,
+ PRJ_NOTIFICATION NotificationType,
+ LPCWSTR DestinationFileName,
+ PRJ_NOTIFICATION_PARAMETERS* OperationParameters)
+{
+ ZEN_UNUSED(DestinationFileName);
+
+ switch (NotificationType)
+ {
+ case PRJ_NOTIFICATION_FILE_OPENED:
+ {
+ auto& pc = OperationParameters->PostCreate;
+
+ DebugPrint("*** OPEN: %s %08x '%S'\n", IsDirectory ? "(DIR)" : "-FILE", pc.NotificationMask, CallbackData->FilePathName);
+ }
+ break;
+
+ case PRJ_NOTIFICATION_NEW_FILE_CREATED:
+ {
+ auto& pc = OperationParameters->PostCreate;
+
+ DebugPrint("*** NEW : %s %08x '%S'\n", IsDirectory ? "(DIR)" : "-FILE", pc.NotificationMask, CallbackData->FilePathName);
+ }
+ break;
+
+ case PRJ_NOTIFICATION_FILE_OVERWRITTEN:
+ {
+ auto& pc = OperationParameters->PostCreate;
+
+ DebugPrint("*** OVER: %s %08x '%S'\n", IsDirectory ? "(DIR)" : "-FILE", pc.NotificationMask, CallbackData->FilePathName);
+ }
+ break;
+
+ case PRJ_NOTIFICATION_PRE_DELETE:
+ {
+ if (wcsstr(CallbackData->FilePathName, L"en-us"))
+ DebugPrint("*** PRE DELETE '%S'\n", CallbackData->FilePathName);
+
+ DebugPrint("*** PRE DELETE '%S'\n", CallbackData->FilePathName);
+ }
+ break;
+
+ case PRJ_NOTIFICATION_PRE_RENAME:
+ DebugPrint("*** PRE RENAME '%S'\n", CallbackData->FilePathName);
+ break;
+
+ case PRJ_NOTIFICATION_PRE_SET_HARDLINK:
+ DebugPrint("*** PRE SET HARDLINK '%S'\n", CallbackData->FilePathName);
+ break;
+
+ case PRJ_NOTIFICATION_FILE_RENAMED:
+ DebugPrint("*** FILE RENAMED '%S'\n", CallbackData->FilePathName);
+ break;
+
+ case PRJ_NOTIFICATION_HARDLINK_CREATED:
+ DebugPrint("*** HARDLINK RENAMED '%S'\n", CallbackData->FilePathName);
+ break;
+
+ case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION:
+ DebugPrint("*** FILE CLOSED NO CHANGE '%S'\n", CallbackData->FilePathName);
+ break;
+
+ case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_MODIFIED:
+ {
+ // const auto& handleClose = OperationParameters->FileDeletedOnHandleClose;
+
+ DebugPrint("*** FILE CLOSED MODIFIED '%S'\n", CallbackData->FilePathName);
+ }
+ break;
+
+ case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED:
+ {
+ // const auto& handleClose = OperationParameters->FileDeletedOnHandleClose;
+
+ DebugPrint("*** FILE CLOSED DELETED '%S'\n", CallbackData->FilePathName);
+ }
+ break;
+
+ case PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL:
+ DebugPrint("*** FILE PRE CONVERT FULL '%S'\n", CallbackData->FilePathName);
+ break;
+ }
+
+ return S_OK;
+}
+
+void
+ProjfsProvider::CancelCommand(const PRJ_CALLBACK_DATA* CallbackData)
+{
+ ZEN_UNUSED(CallbackData);
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+struct Vfs::VfsImpl
+{
+ void Initialize() { m_PrjProvider.Initialize("E:\\VFS_Test", /* clean */ true); }
+ void Start() {}
+ void Stop() {}
+
+private:
+ ProjfsProvider m_PrjProvider;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+Vfs::Vfs() : m_Impl(new VfsImpl)
+{
+}
+
+Vfs::~Vfs()
+{
+}
+
+void
+Vfs::Initialize()
+{
+ m_Impl->Initialize();
+}
+
+void
+Vfs::Start()
+{
+}
+
+void
+Vfs::Stop()
+{
+}
+
+} // namespace zen