diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..44e904b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + + 2.0.0 + latest + enable + + + diff --git a/ProjectedFSLib.Managed.API/ApiHelper.cpp b/ProjectedFSLib.Managed.API/ApiHelper.cpp deleted file mode 100644 index 7c890db..0000000 --- a/ProjectedFSLib.Managed.API/ApiHelper.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "stdafx.h" -#include "ApiHelper.h" -#include "Utils.h" - -using namespace System; -using namespace System::Globalization; -using namespace System::IO; -using namespace Microsoft::Windows::ProjFS; - -ApiHelper::ApiHelper() : - supportedApi(ApiLevel::v1803) -{ - auto projFsLib = ::LoadLibraryW(L"ProjectedFSLib.dll"); - if (!projFsLib) - { - throw gcnew FileLoadException(String::Format(CultureInfo::InvariantCulture, "Could not load ProjectedFSLib.dll to set up entry points.")); - } - - if (::GetProcAddress(projFsLib, "PrjStartVirtualizing") != nullptr) - { - // We have the API introduced in Windows 10 version 1809. - this->supportedApi = ApiLevel::v1809; - - this->_PrjStartVirtualizing = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjStartVirtualizing")); - - this->_PrjStopVirtualizing = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjStopVirtualizing")); - - this->_PrjWriteFileData = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjWriteFileData")); - - this->_PrjWritePlaceholderInfo = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjWritePlaceholderInfo")); - - this->_PrjAllocateAlignedBuffer = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjAllocateAlignedBuffer")); - - this->_PrjFreeAlignedBuffer = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjFreeAlignedBuffer")); - - this->_PrjGetVirtualizationInstanceInfo = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjGetVirtualizationInstanceInfo")); - - this->_PrjUpdateFileIfNeeded = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjUpdateFileIfNeeded")); - - this->_PrjMarkDirectoryAsPlaceholder = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjMarkDirectoryAsPlaceholder")); - if (::GetProcAddress(projFsLib, "PrjWritePlaceholderInfo2") != nullptr) - { - // We have the API introduced in Windows 10 version 2004. - this->supportedApi = ApiLevel::v2004; - - this->_PrjWritePlaceholderInfo2 = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjWritePlaceholderInfo2")); - - this->_PrjFillDirEntryBuffer2 = reinterpret_cast(::GetProcAddress(projFsLib, "PrjFillDirEntryBuffer2")); - } - - ::FreeLibrary(projFsLib); - - if (!this->_PrjStartVirtualizing || - !this->_PrjStopVirtualizing || - !this->_PrjWriteFileData || - !this->_PrjWritePlaceholderInfo || - !this->_PrjAllocateAlignedBuffer || - !this->_PrjFreeAlignedBuffer || - !this->_PrjGetVirtualizationInstanceInfo || - !this->_PrjUpdateFileIfNeeded || - !this->_PrjMarkDirectoryAsPlaceholder) - { - throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture, - "Could not get a required entry point.")); - } - - if (this->supportedApi >= ApiLevel::v2004) - { - if (!this->_PrjWritePlaceholderInfo2 || - !this->_PrjFillDirEntryBuffer2) - { - throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture, - "Could not get a required entry point.")); - } - } - } - else if (::GetProcAddress(projFsLib, "PrjStartVirtualizationInstance") == nullptr) - { - // Something is wrong; we didn't find the 1809 API nor can we find the 1803 API even though - // we loaded ProjectedFSLib.dll. - - ::FreeLibrary(projFsLib); - - throw gcnew EntryPointNotFoundException(String::Format((CultureInfo::InvariantCulture, - "Cannot find ProjFS API."))); - } - else - { - // We have the beta API introduced in Windows 10 version 1803. - - this->_PrjStartVirtualizationInstance = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjStartVirtualizationInstance")); - - this->_PrjStartVirtualizationInstanceEx = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjStartVirtualizationInstanceEx")); - - this->_PrjStopVirtualizationInstance = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjStopVirtualizationInstance")); - - this->_PrjGetVirtualizationInstanceIdFromHandle = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjGetVirtualizationInstanceIdFromHandle")); - - this->_PrjConvertDirectoryToPlaceholder = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjConvertDirectoryToPlaceholder")); - - this->_PrjWritePlaceholderInformation = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjWritePlaceholderInformation")); - - this->_PrjUpdatePlaceholderIfNeeded = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjUpdatePlaceholderIfNeeded")); - - this->_PrjWriteFile = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjWriteFile")); - - this->_PrjCommandCallbacksInit = reinterpret_cast(::GetProcAddress(projFsLib, - "PrjCommandCallbacksInit")); - - ::FreeLibrary(projFsLib); - - if (!this->_PrjStartVirtualizationInstance || - !this->_PrjStartVirtualizationInstanceEx || - !this->_PrjStopVirtualizationInstance || - !this->_PrjGetVirtualizationInstanceIdFromHandle || - !this->_PrjConvertDirectoryToPlaceholder || - !this->_PrjWritePlaceholderInformation || - !this->_PrjUpdatePlaceholderIfNeeded || - !this->_PrjWriteFile || - !this->_PrjCommandCallbacksInit) - { - throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture, - "Could not get a required entry point.")); - } - } -} - -bool ApiHelper::UseBetaApi::get(void) -{ - return (this->supportedApi == ApiLevel::v1803); -} - -ApiLevel ApiHelper::SupportedApi::get(void) -{ - return this->supportedApi; -} diff --git a/ProjectedFSLib.Managed.API/ApiHelper.h b/ProjectedFSLib.Managed.API/ApiHelper.h deleted file mode 100644 index aea6108..0000000 --- a/ProjectedFSLib.Managed.API/ApiHelper.h +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Defines values describing the APIs available from this wrapper. - /// - enum class ApiLevel : short - { - /// Using Windows 10 version 1803 beta API. - v1803 = 1803, - - /// Using Windows 10 version 1809 API. - v1809 = 1809, - - /// Adding APIs introduced in Windows 10 version 2004. - v2004 = 2004, - }; -/// Helper class for using the correct native APIs in the managed layer. -/// -/// -/// The final ProjFS native APIs released in Windows 10 version 1809 differ from the now-deprecated -/// beta APIs released in Windows 10 version 1803. In 1809 the beta APIs are still exported from -/// ProjectedFSLib.dll, in case an experimental provider written against the native 1803 APIs is run -/// on 1809. -/// -/// -/// This managed API wrapper is meant to be usable on 1803 and later, so it is able to use the -/// beta 1803 native APIs and the final 1809 native APIs. Since the 1809 APIs are not present on -/// 1803, and because we intend to remove the beta 1803 APIs from a later version of Windows, we -/// dynamically load the native APIs here. If we didn't do that then trying to use this managed -/// wrapper on a version of Windows missing one or the other native API would result in the program -/// dying on startup with an unhandled System::IO::FileLoadException: "A procedure -/// imported by 'ProjectedFSLib.Managed.dll' could not be loaded." -/// -/// -/// It is likely that at some point after removing the beta 1803 native APIs from Windows we will -/// also remove support for them from this managed wrapper. -/// -/// -ref class ApiHelper { -internal: - ApiHelper(); - - property bool UseBetaApi - { - bool get(void); - } - - property ApiLevel SupportedApi - { - ApiLevel get(void); - } - -private: - -#pragma region Signatures for Windows 10 version 2004 APIs - - typedef HRESULT(__stdcall* t_PrjWritePlaceholderInfo2)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, - _In_ PCWSTR destinationFileName, - _In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo, - _In_ UINT32 placeholderInfoSize, - _In_opt_ const PRJ_EXTENDED_INFO* ExtendedInfo - ); - - typedef HRESULT(__stdcall* t_PrjFillDirEntryBuffer2) ( - _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle, - _In_ PCWSTR fileName, - _In_opt_ PRJ_FILE_BASIC_INFO* fileBasicInfo, - _In_opt_ PRJ_EXTENDED_INFO* extendedInfo - ); - -#pragma endregion - -#pragma region Signatures for Windows 10 version 1809 APIs - - typedef HRESULT (__stdcall* t_PrjStartVirtualizing)( - _In_ PCWSTR virtualizationRootPath, - _In_ const PRJ_CALLBACKS* callbacks, - _In_opt_ const void* instanceContext, - _In_opt_ const PRJ_STARTVIRTUALIZING_OPTIONS* options, - _Outptr_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT* namespaceVirtualizationContext - ); - - typedef void (__stdcall* t_PrjStopVirtualizing)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext - ); - - typedef HRESULT (__stdcall* t_PrjWriteFileData)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, - _In_ const GUID* dataStreamId, - _In_reads_bytes_(length) void* buffer, - _In_ UINT64 byteOffset, - _In_ UINT32 length - ); - - typedef HRESULT (__stdcall* t_PrjWritePlaceholderInfo)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, - _In_ PCWSTR destinationFileName, - _In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo, - _In_ UINT32 placeholderInfoSize - ); - - typedef void* (__stdcall* t_PrjAllocateAlignedBuffer)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, - _In_ size_t size - ); - - typedef void (__stdcall* t_PrjFreeAlignedBuffer)( - _In_ void* buffer - ); - - typedef HRESULT (__stdcall* t_PrjGetVirtualizationInstanceInfo)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, - _Out_ PRJ_VIRTUALIZATION_INSTANCE_INFO* virtualizationInstanceInfo - ); - - typedef HRESULT (__stdcall* t_PrjUpdateFileIfNeeded)( - _In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext, - _In_ PCWSTR destinationFileName, - _In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo, - _In_ UINT32 placeholderInfoSize, - _In_opt_ PRJ_UPDATE_TYPES updateFlags, - _Out_opt_ PRJ_UPDATE_FAILURE_CAUSES* failureReason - ); - - typedef HRESULT (__stdcall* t_PrjMarkDirectoryAsPlaceholder)( - _In_ PCWSTR rootPathName, - _In_opt_ PCWSTR targetPathName, - _In_opt_ const PRJ_PLACEHOLDER_VERSION_INFO* versionInfo, - _In_ const GUID* virtualizationInstanceID - ); - -#pragma endregion - -#pragma region Signatures for deprecated Windows 10 version 1803 - - typedef HRESULT (__stdcall* t_PrjStartVirtualizationInstance)( - _In_ LPCWSTR VirtualizationRootPath, - _In_ PPRJ_COMMAND_CALLBACKS Callbacks, - _In_opt_ DWORD Flags, - _In_opt_ DWORD GlobalNotificationMask, - _In_opt_ DWORD PoolThreadCount, - _In_opt_ DWORD ConcurrentThreadCount, - _In_opt_ PVOID InstanceContext, - _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle - ); - - typedef HRESULT (__stdcall* t_PrjStartVirtualizationInstanceEx)( - _In_ LPCWSTR VirtualizationRootPath, - _In_ PPRJ_COMMAND_CALLBACKS Callbacks, - _In_opt_ PVOID InstanceContext, - _In_opt_ PVIRTUALIZATION_INST_EXTENDED_PARAMETERS ExtendedParameters, - _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle - ); - - typedef HRESULT (__stdcall* t_PrjStopVirtualizationInstance)( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle - ); - - typedef HRESULT (__stdcall* t_PrjGetVirtualizationInstanceIdFromHandle)( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _Out_ LPGUID VirtualizationInstanceID - ); - - typedef HRESULT (__stdcall* t_PrjConvertDirectoryToPlaceholder)( - _In_ LPCWSTR RootPathName, - _In_ LPCWSTR TargetPathName, - _In_opt_ PRJ_PLACEHOLDER_VERSION_INFO* VersionInfo, - _In_opt_ DWORD Flags, - _In_ LPCGUID VirtualizationInstanceID - ); - - typedef HRESULT (__stdcall* t_PrjWritePlaceholderInformation)( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _In_ LPCWSTR DestinationFileName, - _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, - _In_ DWORD Length - ); - - typedef HRESULT (__stdcall* t_PrjUpdatePlaceholderIfNeeded)( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _In_ LPCWSTR DestinationFileName, - _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, - _In_ DWORD Length, - _In_opt_ DWORD UpdateFlags, - _Out_opt_ PDWORD FailureReason - ); - - typedef HRESULT (__stdcall* t_PrjWriteFile)( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _In_ const GUID* StreamId, - _In_reads_bytes_(Length) void* Buffer, - _In_ UINT64 ByteOffset, - _In_ UINT32 Length - ); - - typedef HRESULT (__stdcall* t_PrjCommandCallbacksInit)( - _In_ DWORD CallbacksSize, - _Out_writes_bytes_(CallbacksSize) PPRJ_COMMAND_CALLBACKS Callbacks - ); - -#pragma endregion - - ApiLevel supportedApi{ ApiLevel::v1803 }; - -internal: - - // 2004 API - t_PrjWritePlaceholderInfo2 _PrjWritePlaceholderInfo2 = nullptr; - t_PrjFillDirEntryBuffer2 _PrjFillDirEntryBuffer2 = nullptr; - - // 1809 API - t_PrjStartVirtualizing _PrjStartVirtualizing = nullptr; - t_PrjStopVirtualizing _PrjStopVirtualizing = nullptr; - t_PrjWriteFileData _PrjWriteFileData = nullptr; - t_PrjWritePlaceholderInfo _PrjWritePlaceholderInfo = nullptr; - t_PrjAllocateAlignedBuffer _PrjAllocateAlignedBuffer = nullptr; - t_PrjFreeAlignedBuffer _PrjFreeAlignedBuffer = nullptr; - t_PrjGetVirtualizationInstanceInfo _PrjGetVirtualizationInstanceInfo = nullptr; - t_PrjUpdateFileIfNeeded _PrjUpdateFileIfNeeded = nullptr; - t_PrjMarkDirectoryAsPlaceholder _PrjMarkDirectoryAsPlaceholder = nullptr; - - // 1803 API - t_PrjStartVirtualizationInstance _PrjStartVirtualizationInstance = nullptr; - t_PrjStartVirtualizationInstanceEx _PrjStartVirtualizationInstanceEx = nullptr; - t_PrjStopVirtualizationInstance _PrjStopVirtualizationInstance = nullptr; - t_PrjGetVirtualizationInstanceIdFromHandle _PrjGetVirtualizationInstanceIdFromHandle = nullptr; - t_PrjConvertDirectoryToPlaceholder _PrjConvertDirectoryToPlaceholder = nullptr; - t_PrjWritePlaceholderInformation _PrjWritePlaceholderInformation = nullptr; - t_PrjUpdatePlaceholderIfNeeded _PrjUpdatePlaceholderIfNeeded = nullptr; - t_PrjWriteFile _PrjWriteFile = nullptr; - t_PrjCommandCallbacksInit _PrjCommandCallbacksInit = nullptr; -}; - -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/AssemblyInfo.cpp b/ProjectedFSLib.Managed.API/AssemblyInfo.cpp deleted file mode 100644 index 63f0d47..0000000 Binary files a/ProjectedFSLib.Managed.API/AssemblyInfo.cpp and /dev/null differ diff --git a/ProjectedFSLib.Managed.API/CallbackDelegates.h b/ProjectedFSLib.Managed.API/CallbackDelegates.h deleted file mode 100644 index b9b139e..0000000 --- a/ProjectedFSLib.Managed.API/CallbackDelegates.h +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "DirectoryEnumerationResults.h" -#include "WriteBuffer.h" - -using namespace System::Runtime::InteropServices; - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - - /// Determines whether a given file path exists in the provider's store. - /// - /// - /// The provider sets its implementation of this delegate into the OnQueryFileName - /// property of ProjFS.VirtualizationInstance. - /// - /// - /// If the provider does not implement this callback, ProjFS will call the enumeration callbacks - /// when it needs to find out whether a file path exists in the provider�s store. - /// - /// - /// The path, relative to the virtualization root, of the file being queried. - /// - /// if exists in the provider's store. - /// if does not exist in the provider's store. - /// An appropriate error code if the provider fails the operation. - /// - public delegate HResult QueryFileNameCallback( - System::String^ relativePath); - - /// Informs the provider that an operation begun by an earlier invocation of a callback - /// is to be canceled. - /// - /// - /// The provider sets its implementation of this delegate into the OnCancelCommand - /// property of ProjFS.VirtualizationInstance. - /// - /// - /// ProjFS invokes this callback to indicate that the I/O that caused the earlier callback - /// to be invoked was canceled, either explicitly or because the thread it was issued on terminated. - /// - /// - /// Calling ProjFS.VirtualizationInstance.CompleteCommand for the - /// passed by this callback is not an error, however it is a no-op because the I/O that caused - /// the callback invocation identified by has already ended. - /// - /// - /// ProjFS will invoke this callback for a given only after - /// the callback to be canceled is invoked. However if the provider is configured to allow - /// more than one concurrently running worker thread, the cancellation and original invocation - /// may run concurrently. The provider must be able to handle this situation. - /// - /// - /// A provider that does not return to any of its callbacks does - /// not need to handle this callback. - /// - /// - /// A value that identifies the callback invocation to be canceled. - /// Corresponds to the parameter of callbacks whose processing - /// can be canceled. - public delegate void CancelCommandCallback( - int commandId); - - /// Indicates that a handle has been created to an existing file or directory. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyFileOpened - /// property of ProjFS.VirtualizationInstance. - /// - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// - /// - /// If the provider returns false, then the file system will cancel the open of the file and - /// return STATUS_ACCESS_DENIED to the caller trying to open the file. - /// - /// - /// The path, relative to the virtualization root, of the file or directory. - /// true if is for a directory, - /// false if is for a file. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// Upon return from this callback specifies a bitwise-OR of - /// values indicating the set of notifications the provider - /// wishes to receive for this file. - /// If the provider sets this value to 0, it is equivalent to specifying . - /// - /// - /// true if the provider wants to allow the opened file to be returned to the - /// caller, false otherwise. - /// - public delegate bool NotifyFileOpenedCallback( - System::String^ relativePath, - bool isDirectory, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName, - [Out] NotificationType% notificationMask); - - /// Indicates that a new file or directory has been created. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyNewFileCreated - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// - /// The path, relative to the virtualization root, of the file or directory. - /// true if is for a directory, - /// false if is for a file. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// Upon return from this callback specifies a bitwise-OR of - /// values indicating the set of notifications the provider - /// wishes to receive for this file. - /// If the provider sets this value to 0, it is equivalent to specifying . - /// - public delegate void NotifyNewFileCreatedCallback( - System::String^ relativePath, - bool isDirectory, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName, - [Out] NotificationType% notificationMask); - - /// Indicates that an existing file has been superseded or overwritten. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyFileOverwritten - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// - /// The path, relative to the virtualization root, of the file or directory. - /// true if is for a directory, - /// false if is for a file. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// Upon return from this callback specifies a bitwise-OR of - /// values indicating the set of notifications the provider - /// wishes to receive for this file. - /// If the provider sets this value to 0, it is equivalent to specifying . - /// - public delegate void NotifyFileOverwrittenCallback( - System::String^ relativePath, - bool isDirectory, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName, - [Out] NotificationType% notificationMask); - - /// Indicates that a file or directory is about to be deleted. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyPreDelete - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// If the provider returns false then the file system will return STATUS_CANNOT_DELETE - /// from the operation that triggered the delete, and the delete will not take place. - /// - /// The path, relative to the virtualization root, of the file or directory. - /// true if is for a directory, - /// false if is for a file. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// - /// true if the provider wants to allow the delete to happen, false if it wants - /// to prevent the delete. - /// - public delegate bool NotifyPreDeleteCallback( - System::String^ relativePath, - bool isDirectory, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - - /// Indicates that a file or directory is about to be renamed. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyPreRename - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// If both the and - /// parameters of this callback are non-empty strings, this indicates that the source and - /// destination of the rename are under the virtualization root. If the provider specified - /// different notification masks in the parameter of - /// ProjFS.VirtualizationInstance.StartVirtualizing for the source and destination - /// paths, then ProjFS will send this notification if the provider specified - /// when registering either the source or destination - /// paths. - /// If the provider returns false, then the file system will return STATUS_ACCESS_DENIED - /// from the rename operation, and the rename will not take effect. - /// - /// The path, relative to the virtualization root, of the file or directory - /// to be renamed. - /// This parameter will be "" to indicate that the rename will move the file or directory - /// from a location not under the virtualization root. In that case ProjFS will always send - /// this notification if the provider has implemented this callback, even if the provider did - /// not specify when registering the subtree containing - /// the destination path. - /// - /// - /// The path, relative to the virtualization root, to which the file - /// or directory will be renamed. - /// This parameter will be "" to indicate that the rename will move the file or directory - /// out of the virtualization instance. - /// - /// - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// - /// true if the provider wants to allow the rename to happen, false if it wants - /// to prevent the rename. - /// - public delegate bool NotifyPreRenameCallback( - System::String^ relativePath, - System::String^ destinationPath, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - - /// Indicates that a hard link is about to be created for the file. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyPreCreateHardlink - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// If both the and - /// parameters of this callback are non-empty strings, this indicates that the new hard link - /// will be created under the virtualization root for a file that is under the virtualization - /// root. If the provider specified different notification masks in the - /// parameter of ProjFS.VirtualizationInstance.StartVirtualizing - /// for the source and destination paths, then ProjFS will send this notification if the provider - /// specified when registering either the - /// source or destination paths. - /// If the provider returns false, then the file system will return STATUS_ACCESS_DENIED - /// from the hard link operation, and the hard link will not be created. - /// - /// The path, relative to the virtualization root, of the file or directory - /// for which the hard link is to be created. - /// This parameter will be "" to indicate that the hard link name will be created under - /// the virtualization root, i.e. a new hard link is being created under the virtualization - /// root to a file whose path is not under the virtualization root. - /// - /// The path, relative to the virtualization root, of the new hard - /// link name. - /// This parameter will be "" to indicate that the hard link name will be created in - /// a location not under the virtualization, i.e. a new hard link is being created in a location - /// not under the virtualization for a file is under the virtualization root. - /// - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// - /// true if the provider wants to allow the hard link operation to happen, false - /// if it wants to prevent the hard link operation. - /// - public delegate bool NotifyPreCreateHardlinkCallback( - System::String^ relativePath, - System::String^ destinationPath, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - - /// - /// Indicates that a file or directory has been renamed. The file or directory may have been moved - /// into the virtualization instance. - /// - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyFileRenamed - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// If both the and - /// parameters of this callback are non-empty strings, this indicates that the source and - /// destination of the rename were both under the virtualization root. If the provider specified - /// different notification masks in the parameter of - /// ProjFS.VirtualizationInstance.StartVirtualizing for the source and destination - /// paths, then ProjFS will send this notification if the provider specified - /// when registering either the source or destination - /// paths. - /// - /// The original path, relative to the virtualization root, of the file - /// or directory that was renamed. - /// This parameter will be "" to indicate that the rename moved the file or directory - /// from outside the virtualization instance. In that case ProjFS will always send this notification - /// if the provider has implemented this callback, even if the provider did not specify - /// when registering the subtree containing the destination path. - /// - /// - /// The path, relative to the virtualization root, to which the file - /// or directory was renamed. - /// This parameter will be "" to indicate that the rename moved the file or directory - /// out of the virtualization instance. - /// - /// - /// true if is for a directory, - /// false if is for a file. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// Upon return from this callback specifies a bitwise-OR of - /// values indicating the set of notifications the provider - /// wishes to receive for this file. - /// If the provider sets this value to 0, it is equivalent to specifying . - /// - public delegate void NotifyFileRenamedCallback( - System::String^ relativePath, - System::String^ destinationPath, - bool isDirectory, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName, - [Out] NotificationType% notificationMask); - - /// Indicates that a hard link has been created for the file. - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyHardlinkCreated - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// If both the and - /// parameters of this callback are non-empty strings, this indicates that the new hard link - /// was created under the virtualization root for a file that exists under the virtualization - /// root. If the provider specified different notification masks in the - /// parameter of ProjFS.VirtualizationInstance.StartVirtualizing for the source and - /// destination paths, then ProjFS will send this notification if the provider specified - /// when registering either the source or destination - /// paths. - /// - /// The path, relative to the virtualization root, of the file for - /// which the hard link was created. - /// This parameter will be "" to indicate that the hard link name was created under the - /// virtualization root, i.e. a new hard link was created under the virtualization to a file - /// that exists in a location not under the virtualization root. - /// - /// The path, relative to the virtualization root, of the new hard - /// link name. - /// This parameter will be "" to indicate that the hard link name was created in a - /// location not under the virtualization root, i.e. a new hard link was created in a location - /// not under the virtualization root for a file that is under the virtualization root. - /// - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - public delegate void NotifyHardlinkCreatedCallback( - System::String^ relativePath, - System::String^ destinationPath, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - - /// - /// Indicates that a handle has been closed on the file or directory, and that the file was not modified - /// while that handle was open, and that the file or directory was not deleted as part of closing the handle. - /// - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyFileHandleClosedNoModification - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// - /// The path, relative to the virtualization root, of the file or directory. - /// true if is for a directory, - /// false if is for a file. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - public delegate void NotifyFileHandleClosedNoModificationCallback( - System::String^ relativePath, - bool isDirectory, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - - /// - /// Indicates that a handle has been closed on the file or directory, and whether the file was modified - /// while that handle was open, or that the file or directory was deleted as part of closing the handle. - /// - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyFileHandleClosedFileModifiedOrDeleted - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// or when it started the virtualization instance. - /// - /// The path, relative to the virtualization root, of the file or directory. - /// true if is for a directory, - /// false if is for a file. - /// true if the file or directory was modified while the handle - /// was open, false otherwise. - /// true if the file or directory was deleted as part of closing - /// the handle, false otherwise. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - public delegate void NotifyFileHandleClosedFileModifiedOrDeletedCallback( - System::String^ relativePath, - bool isDirectory, - bool isFileModified, - bool isFileDeleted, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - - /// - /// Indicates that the file is about to be converted from a placeholder to a full file, i.e. its - /// contents are likely to be modified. - /// - /// - /// - /// The provider sets its implementation of this delegate into the OnNotifyFilePreConvertToFull - /// property of ProjFS.VirtualizationInstance. - /// - /// ProjFS will invoke this callback if the provider registered for - /// when it started the virtualization instance. - /// If the provider returns false, then the file system will return - /// STATUS_ACCESS_DENIED from the operation that triggered the conversion, and the placeholder - /// will not be converted to a full file. - /// - /// The path, relative to the virtualization root, of the file or directory. - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// - /// true if the provider wants to allow the file to be converted to full, false - /// if it wants to prevent the file from being converted to full. - /// - public delegate bool NotifyFilePreConvertToFullCallback( - System::String^ relativePath, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName); - -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/DirectoryEnumerationResults.h b/ProjectedFSLib.Managed.API/DirectoryEnumerationResults.h deleted file mode 100644 index fdd8762..0000000 --- a/ProjectedFSLib.Managed.API/DirectoryEnumerationResults.h +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "IDirectoryEnumerationResults.h" -#include "ApiHelper.h" - -using namespace System; -using namespace System::Globalization; -using namespace System::IO; - -namespace Microsoft { -namespace Windows { -namespace ProjFS { -///Helper class for providing the results of a directory enumeration. -/// -/// ProjFS passes an instance of this class to the provider in the -/// parameter of its implementation of a GetDirectoryEnumerationCallback delegate. The provider -/// calls one of its Add methods for each item in the enumeration -/// to add it to the result set. -/// -public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResults { -internal: - - DirectoryEnumerationResults(PRJ_DIR_ENTRY_BUFFER_HANDLE bufferHandle, ApiHelper^ apiHelper) : - m_dirEntryBufferHandle(bufferHandle), m_apiHelper(apiHelper) - { } - - // Provides access to the native handle to the directory entry buffer. - // Used internally by VirtualizationInstance::CompleteCommand(int, IDirectoryEnumerationResults^). - property PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle - { - PRJ_DIR_ENTRY_BUFFER_HANDLE get(void) - { - return m_dirEntryBufferHandle; - } - }; - -public: - - /// Adds one entry to a directory enumeration result. - /// - /// - /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider - /// calls this method for each matching file or directory in the enumeration. - /// - /// - /// If the provider calls this Add overload, then the timestamps reported to the caller - /// of the enumeration are the current system time. If the provider wants the caller to see other - /// timestamps, it must use the other Add overload. - /// - /// - /// If this method returns false, the provider returns and waits for - /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - /// the entry it was trying to add when it got false. - /// - /// - /// If the method returns false for the first file or directory in the enumeration, the - /// provider returns from the GetDirectoryEnumerationCallback - /// method. - /// - /// - /// The name of the file or directory. - /// The size of the file. - /// true if this item is a directory, false if it is a file. - /// - /// - /// true if the entry was successfully added to the enumeration buffer, false otherwise. - /// - /// - /// - /// is null or empty. - /// - virtual bool Add( - System::String^ fileName, - long long fileSize, - bool isDirectory) sealed - { - if (System::String::IsNullOrEmpty(fileName)) - { - throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, - "fileName cannot be empty.")); - } - - pin_ptr pFileName = PtrToStringChars(fileName); - - PRJ_FILE_BASIC_INFO basicInfo = { 0 }; - basicInfo.IsDirectory = isDirectory; - basicInfo.FileSize = fileSize; - - auto hr = ::PrjFillDirEntryBuffer(pFileName, - &basicInfo, - m_dirEntryBufferHandle); - - if (FAILED(hr)) - { - return false; - } - - return true; - } - - /// Adds one entry to a directory enumeration result. - /// - /// - /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider - /// calls this method for each matching file or directory in the enumeration. - /// - /// - /// If this method returns false, the provider returns and waits for - /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - /// the entry it was trying to add when it got false. - /// - /// - /// If the method returns false for the first file or directory in the enumeration, the - /// provider returns from the GetDirectoryEnumerationCallback - /// method. - /// - /// - /// The name of the file or directory. - /// The size of the file. - /// true if this item is a directory, false if it is a file. - /// The file attributes. - /// The time the file was created. - /// The time the file was last accessed. - /// The time the file was last written to. - /// The time the file was last changed. - /// - /// - /// true if the entry was successfully added to the enumeration buffer, false otherwise. - /// - /// - /// - /// is null or empty. - /// - virtual bool Add( - System::String^ fileName, - long long fileSize, - bool isDirectory, - System::IO::FileAttributes fileAttributes, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime) sealed - { - ValidateFileName(fileName); - - pin_ptr pFileName = PtrToStringChars(fileName); - PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize, - isDirectory, - fileAttributes, - creationTime, - lastAccessTime, - lastWriteTime, - changeTime); - - auto hr = ::PrjFillDirEntryBuffer(pFileName, - &basicInfo, - m_dirEntryBufferHandle); - - if FAILED(hr) - { - return false; - } - - return true; - } - - /// Adds one entry to a directory enumeration result. - /// - /// - /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider - /// calls this method for each matching file or directory in the enumeration. - /// - /// - /// If this method returns false, the provider returns and waits for - /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - /// the entry it was trying to add when it got false. - /// - /// - /// If the method returns false for the first file or directory in the enumeration, the - /// provider returns from the GetDirectoryEnumerationCallback - /// method. - /// - /// - /// The name of the file or directory. - /// The size of the file. - /// true if this item is a directory, false if it is a file. - /// The file attributes. - /// The time the file was created. - /// The time the file was last accessed. - /// The time the file was last written to. - /// The time the file was last changed. - /// Specifies the symlink target path if the file is a symlink. - /// - /// - /// true if the entry was successfully added to the enumeration buffer, false otherwise. - /// - /// - /// - /// is null or empty. - /// - virtual bool Add( - System::String^ fileName, - long long fileSize, - bool isDirectory, - System::IO::FileAttributes fileAttributes, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::String^ symlinkTargetOrNull) sealed - { - // This API is supported in Windows 10 version 2004 and above. - if ((symlinkTargetOrNull != nullptr) && - (m_apiHelper->SupportedApi < ApiLevel::v2004)) - { - throw gcnew NotImplementedException("PrjFillDirEntryBuffer2 is not supported in this version of Windows."); - } - - ValidateFileName(fileName); - - pin_ptr pFileName = PtrToStringChars(fileName); - PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize, - isDirectory, - fileAttributes, - creationTime, - lastAccessTime, - lastWriteTime, - changeTime); - - PRJ_EXTENDED_INFO extendedInfo = {}; - if (symlinkTargetOrNull != nullptr) - { - extendedInfo.InfoType = PRJ_EXT_INFO_TYPE_SYMLINK; - pin_ptr targetPath = PtrToStringChars(symlinkTargetOrNull); - extendedInfo.Symlink.TargetName = targetPath; - } - - HRESULT hr; - hr = m_apiHelper->_PrjFillDirEntryBuffer2(m_dirEntryBufferHandle, - pFileName, - &basicInfo, - (symlinkTargetOrNull != nullptr) ? &extendedInfo : nullptr); - - if FAILED(hr) - { - return false; - } - - return true; - } - -private: - - PRJ_DIR_ENTRY_BUFFER_HANDLE m_dirEntryBufferHandle; - ApiHelper^ m_apiHelper; - - void ValidateFileName(System::String^ fileName) - { - if (System::String::IsNullOrEmpty(fileName)) - { - throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, - "fileName cannot be empty.")); - } - } - - PRJ_FILE_BASIC_INFO BuildFileBasicInfo(long long fileSize, - bool isDirectory, - System::IO::FileAttributes fileAttributes, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime) - { - PRJ_FILE_BASIC_INFO basicInfo = { 0 }; - - if (creationTime != System::DateTime::MinValue) - { - basicInfo.CreationTime.QuadPart = creationTime.ToFileTime(); - } - - if (lastAccessTime != System::DateTime::MinValue) - { - basicInfo.LastAccessTime.QuadPart = lastAccessTime.ToFileTime(); - } - - if (lastWriteTime != System::DateTime::MinValue) - { - basicInfo.LastWriteTime.QuadPart = lastWriteTime.ToFileTime(); - } - - if (changeTime != System::DateTime::MinValue) - { - basicInfo.ChangeTime.QuadPart = changeTime.ToFileTime(); - } - - basicInfo.FileAttributes = static_cast(fileAttributes); - basicInfo.IsDirectory = isDirectory; - basicInfo.FileSize = fileSize; - - return basicInfo; - } -}; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/HResult.h b/ProjectedFSLib.Managed.API/HResult.h deleted file mode 100644 index 0d69842..0000000 --- a/ProjectedFSLib.Managed.API/HResult.h +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// HRESULT values that ProjFS may report to a provider, or that a provider may return to ProjFS. - /// - /// - /// - /// .NET methods normally do not return error codes, preferring to throw exceptions. For the most - /// part this API does not throw exceptions, preferring instead to return error codes. We do this - /// for few reasons: - /// - /// - /// - /// This API is a relatively thin wrapper around a native API that itself returns HRESULT codes. - /// This managed library would have to translate those error codes into exceptions to throw. - /// - /// - /// - /// - /// Errors that a provider returns are sent through the file system, back to the user who is - /// performing the I/O. If the provider callbacks threw exceptions, the managed library would - /// just have to catch them and turn them into HRESULT codes. - /// - /// - /// - /// - /// If the API methods described here threw exceptions, either the provider would have to catch - /// them and turn them into error codes to return from its callbacks, or it would allow those - /// exceptions to propagate and this managed library would still have to deal with them as - /// described in the preceding bullet. - /// - /// - /// - /// So rather than deal with the overhead of exceptions just to try to conform to .NET conventions, - /// this API largely dispenses with them and uses HRESULT codes. - /// - /// - /// Note that for the convenience of C# developers the VirtualizationInstance::CreateWriteBuffer - /// method does throw System::OutOfMemoryException if it cannot allocate the buffer. This - /// makes the method convenient to use with the using keyword. - /// - /// - /// Note that when HRESULT codes returned from the provider are sent to the file system, the ProjFS - /// library translates them into NTSTATUS codes. Because there is not a 1-to-1 mapping of HRESULT - /// codes to NTSTATUS codes, the set of HRESULT codes that a provider is allowed to return is - /// necessarily constrained. - /// - /// - /// A provider's IRequiredCallbacks method and On... delegate implementations may - /// return any HResult value returned from a VirtualizationInstance, as well as the - /// following HResult values: - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The remaining values in the HResult enum may be returned to a provider from ProjFS APIs - /// and are primarily intended to communicate information to the provider. As noted above, if - /// such a value is returned to a provider in its implementation of a callback or delegate, it may - /// return the value to ProjFS. - /// - /// - public enum class HResult : long - { - // In addition to any HResult value returned from a VirtualizationInstance method, a provider - // may return any of the following HResult values. - - ///Success. - Ok = S_OK, - - ///The data necessary to complete this operation is not yet available. - Pending = HRESULT_FROM_WIN32(ERROR_IO_PENDING), - - ///Ran out of memory. - OutOfMemory = E_OUTOFMEMORY, - - ///The data area passed to a system call is too small. - InsufficientBuffer = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), - - ///The system cannot find the file specified. - FileNotFound = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), - - ///The provider that supports file system virtualization is temporarily unavailable. - VirtualizationUnavaliable = HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_UNAVAILABLE), - - ///The provider is in an invalid state that prevents it from servicing the callback - ///(only use this if none of the other error codes is a better match). - InternalError = HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR), - - ////////////////////////////////////////////////////////////////////////// - // - // The following HResult values are intended for use by the ProjFS library only. They are - // for communicating information to the provider. - // - // ProjFS internally returns NTSTATUS codes to the I/O system. Because the mapping from NTSTATUS - // to Win32 error/HRESULT codes is not 1:1, ProjFS's user-mode library performs a reverse - // mapping for selected error codes. ProjFS performs such a reverse mapping for the codes - // listed above. Those listed below may not have a reverse mapping. Any code for which ProjFS - // does not have a reverse mapping will be returned to the I/O system as - // STATUS_INTERNAL_ERROR. - // - ////////////////////////////////////////////////////////////////////////// - - ///An attempt was made to perform an initialization operation when initialization - ///has already been completed. - AlreadyInitialized = HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED), - - ///Access is denied. - AccessDenied = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), - - ///An attempt has been made to remove a file or directory that cannot be deleted. - // There is not a Win32 error code that can reverse-map back to STATUS_CANNOT_DELETE. However - // the I/O system expects the file system to return that status code as an indication that a - // delete is not allowed. To ensure the I/O system gets the correct code we define it here - // by hand. This is equivalent to HRESULT_FROM_NT(STATUS_CANNOT_DELETE). Doing it this way - // saves us from having to do weird things with #include and #define. - CannotDelete = ((HRESULT) (0xc0000121 | 0x10000000)), - - ///The directory name is invalid (it may not be a directory). - Directory = HRESULT_FROM_WIN32(ERROR_DIRECTORY), - - ///The directory is not empty. - DirNotEmpty = HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY), - - ///Invalid handle (it may already be closed). - Handle = E_HANDLE, - - ///One or more arguments are invalid. - InvalidArg = E_INVALIDARG, - - ///The system cannot find the path specified. - PathNotFound = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), - - ///The object manager encountered a reparse point while retrieving an object. - ReparsePointEncountered = HRESULT_FROM_WIN32(ERROR_REPARSE_POINT_ENCOUNTERED), - - ///The virtualization operation is not allowed on the file in its current state. - VirtualizationInvalidOp = HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION), - }; -}}} // namespace Microsoft.Windows.ProjFS diff --git a/ProjectedFSLib.Managed.API/IDirectoryEnumerationResults.h b/ProjectedFSLib.Managed.API/IDirectoryEnumerationResults.h deleted file mode 100644 index 87eeb20..0000000 --- a/ProjectedFSLib.Managed.API/IDirectoryEnumerationResults.h +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - -/// -/// Interface to allow for easier unit testing of a virtualization provider. -/// -/// -/// This class defines the interface implemented by the Microsoft.Windows.ProjFS.DirectoryEnumerationResults -/// class. This interface class is provided for use by unit tests to mock up the interface to ProjFS. -/// -public interface class IDirectoryEnumerationResults -{ -public: - - /// - /// When overridden in a derived class, adds one entry to a directory enumeration result. - /// - /// - /// - /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider - /// calls this method for each matching file or directory in the enumeration. - /// - /// - /// If this method returns false, the provider returns HResult.Ok and waits for - /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - /// the entry it was trying to add when it got false. - /// - /// - /// If the function returns false for the first file or directory in the enumeration, the - /// provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback - /// method. - /// - /// - /// IMPORTANT: File and directory names passed to this method must be in the sort - /// specified by PrjFileNameCompare - /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), - /// or else names can be duplicated or missing from the enumeration results presented to the - /// process enumerating the filesystem. - /// - /// - /// The name of the file or directory. - /// The size of the file. - /// true if this item is a directory, false if it is a file. - /// - /// - /// true if the entry was successfully added to the enumeration buffer, false otherwise. - /// - /// - bool Add( - System::String^ fileName, - long long fileSize, - bool isDirectory - ); - - /// - /// When overridden in a derived class, adds one entry to a directory enumeration result. - /// - /// - /// - /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider - /// calls this method for each matching file or directory in the enumeration. - /// - /// - /// If this method returns false, the provider returns HResult.Ok and waits for - /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - /// the entry it was trying to add when it got false. - /// - /// - /// If the function returns false for the first file or directory in the enumeration, the - /// provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback - /// method. - /// - /// - /// IMPORTANT: File and directory names passed to this method must be in the sort - /// specified by PrjFileNameCompare - /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), - /// or else names can be duplicated or missing from the enumeration results presented to the - /// process enumerating the filesystem. - /// - /// - /// The name of the file or directory. - /// The size of the file. - /// true if this item is a directory, false if it is a file. - /// The file attributes. - /// Specifies the time that the file was created. - /// Specifies the time that the file was last accessed. - /// Specifies the time that the file was last written to. - /// Specifies the last time the file was changed. - /// - /// - /// true if the entry was successfully added to the enumeration buffer, false otherwise. - /// - /// - bool Add( - System::String^ fileName, - long long fileSize, - bool isDirectory, - System::IO::FileAttributes fileAttributes, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime - ); - - /// - /// When overridden in a derived class, adds one entry to a directory enumeration result. - /// - /// - /// - /// In its implementation of a GetDirectoryEnumerationCallback delegate the provider - /// calls this method for each matching file or directory in the enumeration. - /// - /// - /// If this method returns false, the provider returns HResult.Ok and waits for - /// the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - /// the entry it was trying to add when it got false. - /// - /// - /// If the function returns false for the first file or directory in the enumeration, the - /// provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback - /// method. - /// - /// - /// IMPORTANT: File and directory names passed to this method must be in the sort - /// specified by PrjFileNameCompare - /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), - /// or else names can be duplicated or missing from the enumeration results presented to the - /// process enumerating the filesystem. - /// - /// - /// The name of the file or directory. - /// The size of the file. - /// true if this item is a directory, false if it is a file. - /// The file attributes. - /// Specifies the time that the file was created. - /// Specifies the time that the file was last accessed. - /// Specifies the time that the file was last written to. - /// Specifies the last time the file was changed. - /// Specifies the symlink target path if the file is a symlink. - /// - /// - /// true if the entry was successfully added to the enumeration buffer, false otherwise. - /// - /// - bool Add( - System::String ^ fileName, - long long fileSize, - bool isDirectory, - System::IO::FileAttributes fileAttributes, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::String ^ symlinkTargetOrNull - ); -}; - -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/IRequiredCallbacks.h b/ProjectedFSLib.Managed.API/IRequiredCallbacks.h deleted file mode 100644 index 781f4fd..0000000 --- a/ProjectedFSLib.Managed.API/IRequiredCallbacks.h +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "HResult.h" -#include "DirectoryEnumerationResults.h" - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - -/// -/// Defines callbacks that a provider is required to implement. -/// -/// -/// -/// A provider must implement the methods in this class to supply basic file system functionality. -/// The provider passes a reference to its implementation in the Microsoft.Windows.ProjFS.StartVirtualizing -/// method. -/// -/// -public interface class IRequiredCallbacks { -public: - /// - /// Informs the provider that a directory enumeration is starting. - /// - /// - /// - /// - /// - /// ProjFS requests a directory enumeration from the provider by first invoking this callback, - /// then the callback one or more times, then - /// the callback. Because multiple enumerations - /// may occur in parallel in the same location, ProjFS uses the - /// argument to associate the callback invocations into a single enumeration, meaning that - /// a given set of calls to the enumeration callbacks will use the same value for - /// for the same session. - /// - /// - /// A value that uniquely identifies an invocation of the callback. - /// If the provider returns from this method, then it must pass - /// this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - /// finished processing this invocation of the callback. - /// - /// Identifies this enumeration session. - /// Identifies the directory to be enumerated. The path is specified - /// relative to the virtualization root. - /// - /// The PID of the process that triggered this callback. If this - /// information is not available, this will be 0. - /// The image file name corresponding to . - /// If is 0 this will be null. - /// - /// if the provider successfully completes the operation. - /// if the provider wishes to complete the operation at a later time. - /// An appropriate error code if the provider fails the operation. - /// - HResult StartDirectoryEnumerationCallback( - int commandId, - System::Guid enumerationId, - System::String^ relativePath, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName - ); - - /// - /// Requests directory enumeration information from the provider. - /// - /// - /// - /// - /// - /// ProjFS requests a directory enumeration from the provider by first invoking - /// , then this callback one or more times, - /// then the callback. Because multiple - /// enumerations may occur in parallel in the same location, ProjFS uses the - /// argument to associate the callback invocations into a - /// single enumeration, meaning that a given set of calls to the enumeration callbacks will - /// use the same value for for the same session. - /// - /// - /// The provider must store the value of across calls - /// to this callback. The provider replaces the value of - /// if in a subsequent invocation of the callback is true - /// - /// If no entries match the search expression specified in , - /// or if all the entries in the directory were added in a previous invocation of this callback, - /// the provider must return . - /// IMPORTANT: The provider must ensure file and directory names returned from this - /// callback are in the sort order specified by PrjFileNameCompare - /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), - /// or else names can be duplicated or missing from the enumeration results presented to the - /// process enumerating the filesystem. - /// - /// - /// A value that uniquely identifies an invocation of the callback. - /// If the provider returns from this method, then it must pass - /// this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - /// finished processing this invocation of the callback. - /// - /// Identifies this enumeration session. - /// - /// An optional string specifying a search expression. This parameter may be null. - /// The search expression may include wildcard characters. The provider should use the - /// ProjFS.Utils.DoesNameContainWildCards method routine to determine whether wildcards - /// are present in the search expression. The provider should use the ProjFS.Utils.IsFileNameMatch - /// method to determine whether a directory entry in its store matches the search expression. - /// If this parameter is not null, only files whose names match the search expression - /// should be included in the directory scan. - /// If this parameter is null, all entries in the directory must be included. - /// - /// - /// true if the scan is to start at the first entry in the directory. - /// false if resuming the scan from a previous call. - /// On the first invocation of this callback for an enumeration session the provider must - /// treat this as true, regardless of its value (i.e. all enumerations must start at the - /// first entry). On subsequent invocations of this callback the provider must honor this value. - /// - /// - /// Receives the results of the enumeration from the provider. - /// The provider uses one of the ::Add - /// methods of this object to provide the enumeration results. - /// If the provider returns from this method, then it must pass - /// this value to ProjFS.VirtualizationInstance.CompleteCommand to provide the - /// enumeration results. - /// IMPORTANT: File and directory names passed to this parameter must be in the sort - /// order specified by PrjFileNameCompare - /// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ), - /// or else names can be duplicated or missing from the enumeration results presented to the - /// process enumerating the filesystem. - /// - /// - /// if the provider successfully completes the operation. - /// if the provider wishes to complete the operation at a later time. - /// if .Add returned - /// for the first matching file or directory in the enumeration. - /// An appropriate error code if the provider fails the operation. - /// - HResult GetDirectoryEnumerationCallback( - int commandId, - System::Guid enumerationId, - System::String^ filterFileName, - bool restartScan, - IDirectoryEnumerationResults^ result - ); - - /// - /// Informs the provider that a directory enumeration is over. - /// - /// - /// - /// - /// - /// ProjFS requests a directory enumeration from the provider by first invoking - /// , then the - /// callback one or more times, then this - /// callback. Because multiple enumerations may occur in parallel in the same location, - /// ProjFS uses the argument to associate the callback - /// invocations into a single enumeration, meaning that a given set of calls to the enumeration - /// callbacks will use the same value for for the same session. - /// - /// - /// Identifies this enumeration session. - /// - /// if the provider successfully completes the operation. - /// An appropriate error code if the provider fails the operation. - /// - HResult EndDirectoryEnumerationCallback( - System::Guid enumerationId - ); - - /// Requests metadata information for a file or directory from the provider. - /// - /// ProjFS uses the information the provider provides in this callback to create a - /// placeholder for the requested item. - /// To handle this callback, the provider typically calls - /// ProjFS.VirtualizationInstance.WritePlaceholderInfo to give ProjFS the information - /// for the requested file name. Then the provider completes the callback. - /// - /// - /// A value that uniquely identifies an invocation of the callback. - /// If the provider returns from this method, then it must pass - /// this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - /// finished processing this invocation of the callback. - /// - /// The path, relative to the virtualization root, of the file for - /// which to return information. - /// The PID for the process that triggered this callback. If - /// this information is not available, this will be 0. - /// The image file name corresponding to - /// . If is 0 this - /// will be null. - /// - /// if the file exists in the provider's store and it successfully - /// gave the file's information to ProjFS. - /// if does not exist in the provider's store. - /// if the provider wishes to complete the operation at a later time. - /// An appropriate error code if the provider fails the operation. - /// - HResult GetPlaceholderInfoCallback( - int commandId, - System::String^ relativePath, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName - ); - - /// Requests the contents of a file's primary data stream. - /// - /// ProjFS uses the data the provider provides in this callback to convert the file into - /// a hydrated placeholder. - /// To handle this callback, the provider issues one or more calls to - /// ProjFS.VirtualizationInstance.WriteFile to give ProjFS the contents of the file's - /// primary data stream. Then the provider completes the callback. - /// - /// A value that uniquely identifies an invocation of the callback. - /// If the provider returns from this method, then it must pass - /// this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - /// finished processing this invocation of the callback. - /// - /// The path, relative to the virtualization root, of the file for - /// which to provide data. - /// Offset in bytes from the beginning of the file at which the provider - /// must start returning data. The provider must return file data starting at or before this - /// offset. - /// Number of bytes of file data requested. The provider must return at least - /// this many bytes of file data beginning at . - /// The unique value to associate with this file stream. The provider - /// must pass this value to ProjFS.VirtualizationInstance.WriteFile when providing - /// file data as part of handling this callback. - /// The value specified by the provider when - /// it created the placeholder for this file. See ProjFS.VirtualizationInstance.WritePlaceholderInfo. - /// The value specified by the provider when - /// it created the placeholder for this file. See ProjFS.VirtualizationInstance.WritePlaceholderInfo. - /// The PID for the process that triggered this callback. If - /// this information is not available, this will be 0. - /// The image file name corresponding to - /// . If is 0 this - /// will be null. - /// - /// if the provider successfully wrote all the requested data. - /// if the provider wishes to complete the operation at a later time. - /// An appropriate error code if the provider fails the operation. - /// - HResult GetFileDataCallback( - int commandId, - System::String^ relativePath, - unsigned long long byteOffset, - unsigned int length, - System::Guid dataStreamId, - array^ contentId, - array^ providerId, - unsigned int triggeringProcessId, - System::String^ triggeringProcessImageFileName - ); -}; - -}}} // namespace Microsoft.Windows.ProjFS diff --git a/ProjectedFSLib.Managed.API/IVirtualizationInstance.h b/ProjectedFSLib.Managed.API/IVirtualizationInstance.h deleted file mode 100644 index d591827..0000000 --- a/ProjectedFSLib.Managed.API/IVirtualizationInstance.h +++ /dev/null @@ -1,735 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "IRequiredCallbacks.h" -#include "CallbackDelegates.h" -#include "UpdateFailureCause.h" -#include "HResult.h" -#include "WriteBuffer.h" -#include "NotificationMapping.h" -#include "NotificationType.h" -#include "UpdateType.h" -#include "Utils.h" - -using namespace System::Runtime::InteropServices; - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Interface to allow for easier unit testing of a virtualization provider. - /// - /// - /// This class defines the interface implemented by the ProjFS.VirtualizationInstance class. - /// This interface class is provided for use by unit tests to mock up the interface to ProjFS. - /// - public interface class IVirtualizationInstance - { - public: - - /// - /// When overridden in a derived class, stores the provider's implementation of . - /// - /// - property QueryFileNameCallback^ OnQueryFileName - { - QueryFileNameCallback^ get(void); - - void set(QueryFileNameCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property CancelCommandCallback^ OnCancelCommand - { - CancelCommandCallback^ get(void); - - void set(CancelCommandCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyFileOpenedCallback^ OnNotifyFileOpened - { - NotifyFileOpenedCallback^ get(void); - - void set(NotifyFileOpenedCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyNewFileCreatedCallback^ OnNotifyNewFileCreated - { - NotifyNewFileCreatedCallback^ get(void); - - void set(NotifyNewFileCreatedCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyFileOverwrittenCallback^ OnNotifyFileOverwritten - { - NotifyFileOverwrittenCallback^ get(void); - - void set(NotifyFileOverwrittenCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyPreDeleteCallback^ OnNotifyPreDelete - { - NotifyPreDeleteCallback^ get(void); - - void set(NotifyPreDeleteCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyPreRenameCallback^ OnNotifyPreRename - { - NotifyPreRenameCallback^ get(void); - - void set(NotifyPreRenameCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyPreCreateHardlinkCallback^ OnNotifyPreCreateHardlink - { - NotifyPreCreateHardlinkCallback^ get(void); - - void set(NotifyPreCreateHardlinkCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyFileRenamedCallback^ OnNotifyFileRenamed - { - NotifyFileRenamedCallback^ get(void); - - void set(NotifyFileRenamedCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyHardlinkCreatedCallback^ OnNotifyHardlinkCreated - { - NotifyHardlinkCreatedCallback^ get(void); - - void set(NotifyHardlinkCreatedCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyFileHandleClosedNoModificationCallback^ OnNotifyFileHandleClosedNoModification - { - NotifyFileHandleClosedNoModificationCallback^ get(void); - - void set(NotifyFileHandleClosedNoModificationCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyFileHandleClosedFileModifiedOrDeletedCallback^ OnNotifyFileHandleClosedFileModifiedOrDeleted - { - NotifyFileHandleClosedFileModifiedOrDeletedCallback^ get(void); - - void set(NotifyFileHandleClosedFileModifiedOrDeletedCallback^ callbackDelegate); - }; - - /// When overridden in a derived class, stores the provider's implementation of . - /// - property NotifyFilePreConvertToFullCallback^ OnNotifyFilePreConvertToFull - { - NotifyFilePreConvertToFullCallback^ get(void); - - void set(NotifyFilePreConvertToFullCallback^ callbackDelegate); - }; - - /// - /// When overridden in a derived class, starts a ProjFS virtualization instance, making it - /// available to service I/O and invoke callbacks on the provider. - /// - /// - /// - /// The provider's implementation of the interface. - /// - /// - /// - /// if the virtualization instance started successfully. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if the virtualization root is an ancestor or descendant of an existing virtualization root. - /// if the virtualization instance is already running. - /// - /// - /// The sector alignment requirements of the volume could not be determined. See the Remarks section. - /// - HResult StartVirtualizing( - IRequiredCallbacks^ requiredCallbacks); - - /// - /// When overridden in a derived class, stops the virtualization instance, making it unavailable - /// to service I/O or invoke callbacks on the provider. - /// - /// - /// The virtualization instance is in an invalid state (it may already be stopped). - /// - void StopVirtualizing(); - - /// - /// When overridden in a derived class, purges the virtualization instance's negative path - /// cache, if it is active. - /// - /// - /// - /// If the negative path cache is active, then if the provider indicates that a file path does - /// not exist by returning from its implementation of - /// - /// then ProjFS will fail subsequent opens of that path without calling - /// again. - /// - /// - /// To resume receiving for - /// paths the provider has indicated do not exist, the provider must call this method. - /// - /// - /// - /// Returns the number of paths that were in the cache before it was purged. - /// - /// - /// if the the cache was successfully purged. - /// if a buffer could not be allocated to communicate with ProjFS. - /// - HResult ClearNegativePathCache( - [Out] unsigned int% totalEntryNumber); - - /// - /// When overridden in a derived class, sends file contents to ProjFS. - /// - /// - /// - /// The provider uses this method to provide the data requested when ProjFS calls the provider's - /// implementation of . - /// - /// - /// The provider calls to create an instance of - /// to contain the data to be written. The ensures that any alignment - /// requirements of the underlying storage device are met. - /// - /// - /// - /// Identifier for the data stream to write to. The provider must use the value of - /// passed in its - /// callback. - /// - /// - /// A created using that contains - /// the data to write. - /// - /// - /// Byte offset from the beginning of the file at which to write the data. - /// - /// - /// The number of bytes to write to the file. - /// - /// - /// if the data was successfully written. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is not specified, - /// is 0, or is greater than the - /// length of the file. - /// if does not - /// correspond to a placeholder that is expecting data to be provided. - /// - /// - HResult WriteFileData( - System::Guid dataStreamId, - IWriteBuffer^ buffer, - unsigned long long byteOffset, - unsigned long length); - - /// - /// When overridden in a derived class, enables a provider to delete a file or directory that - /// has been cached on the local file system. - /// - /// - /// - /// If the item is still in the provider's store, deleting it from the local file system changes - /// it to a virtual item. - /// - /// - /// This routine will fail if called on a file or directory that is already virtual. - /// - /// - /// If the file or directory to be deleted is in any state other than "placeholder", the - /// provider must specify an appropriate combination of values - /// in the parameter. This helps guard against accidental - /// loss of data. If the provider did not specify a combination of - /// values in the parameter that would allow the delete - /// to happen, the method fails with . - /// - /// - /// If a directory contains only tombstones, it may be deleted using this method and - /// specifying in . - /// If the directory contains non-tombstone files, then this method will return . - /// - /// - /// - /// The path, relative to the virtualization root, to the file or directory to delete. - /// - /// - /// - /// A combination of 0 or more values to control whether ProjFS - /// should allow the delete given the state of the file or directory on disk. See the documentation - /// of for a description of each flag and what it will allow. - /// - /// - /// If the item is a dirty placeholder, full file, or tombstone, and the provider does not - /// specify the appropriate flag(s), this routine will fail to delete the placeholder. - /// - /// - /// - /// If this method fails with , this receives a - /// value that describes the reason the delete failed. - /// - /// - /// if the delete succeeded. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// if cannot - /// be found. It may be for a virtual file or directory. - /// if contains - /// an intermediate component that cannot be found. The path may terminate beneath a - /// directory that has been replaced with a tombstone. - /// if is a - /// directory and is not empty. - /// if the input value of - /// does not allow the delete given the state of the file or directory on disk. The value - /// of indicates the cause of the failure. - /// - HResult DeleteFile( - System::String^ relativePath, - UpdateType updateFlags, - [Out] UpdateFailureCause% failureReason); - - /// - /// When overridden in a derived class, sends file or directory metadata to ProjFS. - /// - /// - /// - /// The provider uses this method to create a placeholder on disk. It does this when ProjFS - /// calls the provider's implementation of , - /// or the provider may use this method to proactively lay down a placeholder. - /// - /// - /// Note that the timestamps the provider specifies in the , - /// , , and - /// parameters may be any values the provider wishes. This allows the provider to preserve - /// the illusion of files and directories that already exist on the user's system even before they - /// are physically created on the user's disk. - /// - /// - /// - /// - /// The path, relative to the virtualization root, of the file or directory. - /// - /// - /// If the provider is processing a call to , - /// then this must be a match to the relativePath value passed in that call. The - /// provider should use the method to determine whether - /// the two names match. - /// - /// - /// For example, if specifies - /// dir1\dir1\FILE.TXT in relativePath, and the provider�s backing store contains - /// a file called File.txt in the dir1\dir2 directory, and - /// returns 0 when comparing the names FILE.TXT and File.txt, then the provider - /// specifies dir1\dir2\File.txt as the value of this parameter. - /// - /// - /// - /// The time the file was created. - /// - /// - /// The time the file was last accessed. - /// - /// - /// The time the file was last written to. - /// - /// - /// The time the file was last changed. - /// - /// - /// The file attributes. - /// - /// - /// The size of the file in bytes. - /// - /// - /// true if is for a directory, false otherwise. - /// - /// - /// - /// A content identifier, generated by the provider. ProjFS will pass this value back to the - /// provider when requesting file contents in the callback. - /// This allows the provider to distinguish between different versions of the same file, i.e. - /// different file contents and/or metadata for the same file path. - /// - /// - /// - /// - /// Optional provider-specific data. ProjFS will pass this value back to the provider - /// when requesting file contents in the callback. The - /// provider may use this value as its own unique identifier, for example as a version number - /// for the format of the value. - /// - /// - /// - /// if the placeholder information was successfully written. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// - HResult WritePlaceholderInfo( - System::String^ relativePath, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::IO::FileAttributes fileAttributes, - long long endOfFile, - bool isDirectory, - array^ contentId, - array^ providerId); - - /// - /// When overridden in a derived class, updates an item that has been cached on the local - /// file system. - /// - /// - /// - /// This routine cannot be called on a virtual file or directory. - /// - /// - /// If the file or directory to be updated is in any state other than "placeholder", the provider - /// must specify an appropriate combination of values in the - /// parameter. This helps guard against accidental loss of - /// data, since upon successful return from this routine the item becomes a placeholder with - /// the updated metadata. Any metadata that had been changed since the placeholder was created, - /// or any file data it contained is discarded. - /// - /// - /// Note that the timestamps the provider specifies in the , - /// , , and - /// parameters may be any values the provider wishes. This allows the provider to preserve - /// the illusion of files and directories that already exist on the user's system even before they - /// are physically created on the user's disk. - /// - /// - /// - /// The path, relative to the virtualization root, to the file or directory to updated. - /// - /// - /// The time the file was created. - /// - /// - /// The time the file was last accessed. - /// - /// - /// The time the file was last written to. - /// - /// - /// The time the file was last changed. - /// - /// - /// The file attributes. - /// - /// - /// The size of the file in bytes. - /// - /// - /// - /// A content identifier, generated by the provider. ProjFS will pass this value back to the - /// provider when requesting file contents in the callback. - /// This allows the provider to distinguish between different versions of the same file, i.e. - /// different file contents and/or metadata for the same file path. - /// - /// - /// If this parameter specifies a content identifier that is the same as the content identifier - /// already on the file or directory, the call succeeds and no update takes place. Otherwise, - /// if the call succeeds then the value of this parameter replaces the existing content identifier - /// on the file or directory. - /// - /// - /// - /// - /// Optional provider-specific data. ProjFS will pass this value back to the provider - /// when requesting file contents in the callback. The - /// provider may use this value as its own unique identifier, for example as a version number - /// for the format of the value. - /// - /// - /// - /// - /// A combination of 0 or more values to control whether ProjFS - /// should allow the update given the state of the file or directory on disk. See the documentation - /// of for a description of each flag and what it will allow. - /// - /// - /// If the item is a dirty placeholder, full file, or tombstone, and the provider does not - /// specify the appropriate flag(s), this routine will fail to update the item. - /// - /// - /// - /// If this method fails with , this receives a - /// value that describes the reason the update failed. - /// - /// - /// if the update succeeded. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// if cannot - /// be found. It may be for a virtual file or directory. - /// if contains - /// an intermediate component that cannot be found. The path may terminate beneath a - /// directory that has been replaced with a tombstone. - /// if is a - /// directory and is not empty. - /// if the input value of - /// does not allow the update given the state of the file or directory on disk. The value - /// of indicates the cause of the failure. - /// - HResult UpdateFileIfNeeded( - System::String^ relativePath, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::IO::FileAttributes fileAttributes, - long long endOfFile, - array^ contentId, - array^ providerId, - UpdateType updateFlags, - [Out] UpdateFailureCause% failureReason); - - /// - /// When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback. - /// - HResult CompleteCommand( - int commandId); - - /// - /// When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// The final status of the operation. See the descriptions for the callback delegates for - /// appropriate return codes. - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback. - /// - HResult CompleteCommand( - int commandId, - HResult completionResult); - - /// - /// When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// Used when completing . Receives - /// the results of the enumeration. - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback or - /// if is not a valid . - /// - HResult CompleteCommand( - int commandId, - IDirectoryEnumerationResults^ results); - - /// - /// When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// - /// Used when completing Microsoft.Windows.ProjFS.Notify* callbacks that have a - /// notificationMask parameter. Specifies a bitwise-OR of - /// values indicating the set of notifications the provider wishes to receive for this file. - /// - /// - /// If the provider sets this value to 0, it is equivalent to specifying - /// . - /// - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback - /// or if is not a valid combination of - /// values. - /// - HResult CompleteCommand( - int commandId, - NotificationType newNotificationMask); - - /// - /// When overridden in a derived class, creates a for use with - /// . - /// - /// - /// - /// The object ensures that any alignment requirements of the - /// underlying storage device are met when writing data with the - /// method. - /// - /// - /// - /// The size in bytes of the buffer required to write the data. - /// - /// - /// A that provides at least - /// bytes of capacity. - /// - /// - /// A buffer could not be allocated. - /// - IWriteBuffer^ CreateWriteBuffer( - unsigned int desiredBufferSize); - - /// - /// When overridden in a derived class, creates a for use with . - /// - /// - /// - /// The object ensures that any alignment requirements of the - /// underlying storage device are met when writing data with the - /// method. - /// - /// - /// This overload allows a provider to get sector-aligned values for the start offset and - /// length of the write. The provider uses and - /// to copy the correct data out of its backing store - /// into the and transfer it when calling . - /// - /// - /// - /// Byte offset from the beginning of the file at which the provider wants to write data. - /// - /// - /// The number of bytes to write to the file. - /// - /// - /// , aligned to the sector size of the storage device. The - /// provider uses this value as the byteOffset parameter to . - /// - /// - /// , aligned to the sector size of the storage device. The - /// provider uses this value as the length parameter to . - /// - /// - /// A that provides the needed capacity. - /// - /// - /// An error occurred retrieving the sector size from ProjFS. - /// - /// - /// A buffer could not be allocated. - /// - IWriteBuffer^ CreateWriteBuffer( - unsigned long long byteOffset, - unsigned int length, - [Out] unsigned long long% alignedByteOffset, - [Out] unsigned int% alignedLength); - - /// - /// When overridden in a derived class, converts an existing directory to a hydrated directory - /// placeholder. - /// - /// - /// Children of the directory are not affected. - /// - /// - /// The full path (i.e. not relative to the virtualization root) to the directory to convert - /// to a placeholder. - /// - /// - /// - /// A content identifier, generated by the provider. This value is used to distinguish between - /// different versions of the same file, for example different file contents and/or metadata - /// (e.g. timestamps) for the same file path. - /// - /// - /// - /// - /// Optional provider-specific data. The provider may use this value as its own unique identifier, - /// for example as a version number for the format of the value. - /// - /// - /// - /// if the conversion succeeded. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string - /// or if it is not a directory path. - /// if - /// is already a placeholder or some other kind of reparse point. - /// - HResult MarkDirectoryAsPlaceholder( - System::String^ targetDirectoryPath, - array^ contentId, - array^ providerId); - }; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/IWriteBuffer.h b/ProjectedFSLib.Managed.API/IWriteBuffer.h deleted file mode 100644 index 818079d..0000000 --- a/ProjectedFSLib.Managed.API/IWriteBuffer.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - -/// -/// Interface to allow for easier unit testing of a virtualization provider. -/// -/// -/// This class defines the interface implemented by the Microsoft.Windows.ProjFS.WriteBuffer -/// class. This interface class is provided for use by unit tests to mock up the interface to ProjFS. -/// -public interface class IWriteBuffer : System::IDisposable -{ -public: - - /// - /// When overridden in a derived class, gets the allocated length of the buffer. - /// - property long long Length - { - long long get(void); - }; - - /// - /// When overridden in a derived class, gets a - /// representing the internal buffer. - /// - property System::IO::UnmanagedMemoryStream^ Stream - { - System::IO::UnmanagedMemoryStream^ get(void); - }; - - /// - /// When overridden in a derived class, gets a pointer to the internal buffer. - /// - property System::IntPtr Pointer - { - System::IntPtr get(void); - } -}; - -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/NetCore/ProjectedFSLib.Managed.Netcore.vcxproj b/ProjectedFSLib.Managed.API/NetCore/ProjectedFSLib.Managed.Netcore.vcxproj deleted file mode 100644 index e0c58d8..0000000 --- a/ProjectedFSLib.Managed.API/NetCore/ProjectedFSLib.Managed.Netcore.vcxproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - {D29E5723-25E6-41C7-AEB9-099CDE30538A} - netcoreapp3.1 - NetCore - - - - - 4564 - /Zi %(AdditionalOptions) - - - /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) - - - - - - 4564 - /Zi %(AdditionalOptions) - - - /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) - - - - \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj b/ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj deleted file mode 100644 index 484d434..0000000 --- a/ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA} - v4.8 - true - - - - /Zi %(AdditionalOptions) - - - /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) - - - - - /Zi %(AdditionalOptions) - - - /profile /opt:ref /opt:icf /incremental:no %(AdditionalOptions) - - - - - - - - - \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj.filters b/ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj.filters deleted file mode 100644 index a08ff12..0000000 --- a/ProjectedFSLib.Managed.API/NetFramework/ProjectedFSLib.Managed.vcxproj.filters +++ /dev/null @@ -1,114 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Resource Files - - - - - Resource Files - - - - - Source Files - - - Source Files - - - \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/NotificationMapping.h b/ProjectedFSLib.Managed.API/NotificationMapping.h deleted file mode 100644 index 3242ac0..0000000 --- a/ProjectedFSLib.Managed.API/NotificationMapping.h +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Represents a path relative to a virtualization root and the notification bit mask that should apply to it. - /// - /// - /// - /// A object describes a "notification mapping", which is a pairing between a directory - /// (referred to as a "notification root") and a set of notifications, expressed as a bit mask, which - /// ProjFS should send for that directory and its descendants. - /// - /// - /// The provider passes zero or more objects to the - /// parameter of the VirtualizationInstance::StartVirtualizing method to configure - /// notifications for the virtualization root. - /// - /// - /// If the provider does not specify any notification mappings, ProjFS will default to sending the - /// notifications , , - /// and for all files and directories - /// in the virtualization instance. - /// - /// - /// The property holds the notification root. It is specified - /// relative to the virtualization root, with an empty string representing the virtualization root - /// itself. - /// - /// - /// If the provider specifies multiple notification mappings, and some are descendants of others, - /// the mappings must be specified in descending depth. Notification mappings at deeper levels - /// override higher-level mappings for their descendants. - /// - /// - /// For example, consider the following virtualization instance layout, with C:\VirtRoot as the - /// virtualization root: - /// - /// C:\VirtRoot - /// +--- baz - /// \--- foo - /// +--- subdir1 - /// \--- subdir2 - /// - /// The provider wants: - /// - /// - /// Notification of new file/directory creates for most of the virtualization instance - /// - /// - /// Notification of new file/directory creates, file opens, and file deletes for C:\VirtRoot\foo - /// - /// - /// No notifications for C:\VirtRoot\foo\subdir1 - /// - /// - /// The provider could describe this with the following pseudocode: - /// - /// List<NotificationMapping> notificationMappings = new List<NotificationMapping>() - /// { - /// // Configure default notifications - /// new NotificationMapping(NotificationType.NewFileCreated, - /// string.Empty), - /// // Configure notifications for C:\VirtRoot\foo - /// new NotificationMapping(NotificationType.NewFileCreated | NotificationType.FileOpened | NotificationType.FileHandleClosedFileDeleted, - /// "foo"), - /// // Configure notifications for C:\VirtRoot\foo\subdir1 - /// new NotificationMapping(NotificationType.None, - /// "foo\\subdir1"), - /// }; - /// - /// // Call VirtualizationRoot.StartVirtualizing() passing in the notificationMappings List. - /// - /// - /// - public ref class NotificationMapping - { - public: - /// - /// Initializes a new instance of the class with the - /// property set to the virtualization root (i.e. null) - /// and the property set to . - /// - NotificationMapping(); - - /// - /// Initializes a new instance of the class with the - /// specified property values. - /// - /// The set of notifications that ProjFS should return for the - /// virtualization root specified in . - /// The path to the notification root, relative to the virtualization - /// root. The virtualization root itself must be specified as an empty string. - /// - /// is . or begins with .\. - /// must be specified relative to the virtualization root, with the virtualization root itself - /// specified as an empty string. - /// - NotificationMapping(NotificationType notificationMask, System::String^ notificationRoot); - - /// - /// A bit vector of NotificationType values. - /// - /// - /// ProjFS will send to the provider the specified notifications for operations performed on - /// the directory specified by the property and its descendants. - /// - property NotificationType NotificationMask - { - NotificationType get(void); - void set(NotificationType mask); - } - - /// - /// A path to a directory, relative to the virtualization root. The virtualization root itself - /// must be specified as an empty string. - /// - /// - /// ProjFS will send to the provider the notifications specified in - /// for this directory and its descendants. - /// - /// - /// The notification root value is . or begins with .\. The notification root - /// must be specified relative to the virtualization root, with the virtualization root itself - /// specified as an empty string. - /// - property System::String^ NotificationRoot - { - System::String^ get(void); - void set(System::String^ root); - } - - private: - NotificationType notificationMask; - System::String^ notificationRoot; - }; - - inline NotificationMapping::NotificationMapping() - : notificationMask(NotificationType::None) - , notificationRoot(nullptr) - { - } - - inline NotificationMapping::NotificationMapping(NotificationType notificationMask, System::String^ notificationRoot) - : notificationMask(notificationMask) - , notificationRoot(notificationRoot) - { - if ((notificationRoot == ".") || - notificationRoot->StartsWith(".\\")) - { - throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, - "notificationRoot cannot be \".\" or begin with \".\\\"")); - } - } - - inline NotificationType NotificationMapping::NotificationMask::get(void) - { - return this->notificationMask; - } - - inline void NotificationMapping::NotificationMask::set(NotificationType mask) - { - this->notificationMask = mask; - } - - inline System::String^ NotificationMapping::NotificationRoot::get(void) - { - return this->notificationRoot; - } - - inline void NotificationMapping::NotificationRoot::set(System::String^ root) - { - if ((root == ".") || - root->StartsWith(".\\")) - { - throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture, - "The notification root path cannot be \".\" or begin with \".\\\"")); - } - this->notificationRoot = root; - } -}}} // namespace Microsoft.Windows.ProjFS diff --git a/ProjectedFSLib.Managed.API/NotificationType.h b/ProjectedFSLib.Managed.API/NotificationType.h deleted file mode 100644 index 9700fc5..0000000 --- a/ProjectedFSLib.Managed.API/NotificationType.h +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Defines values for file system operation notifications ProjFS can send to a provider. - /// - /// - /// - /// ProjFS can send notifications of file system activity to a provider. When the provider - /// starts a virtualization instance it specifies which notifications it wishes to receive. - /// It may also specify a new set of notifications for a file when it is created or renamed. - /// The provider must set implementations of Notify...Callback delegates in the OnNotify... - /// properties of ProjFS.VirtualizationInstance in order to receive the notifications - /// for which it registers. - /// - /// - /// ProjFS sends notifications for files and directories managed by an active virtualization - /// instance. That is, ProjFS will send notifications for the virtualization root and its - /// descendants. Symbolic links and junctions within the virtualization root are not traversed - /// when determining what constitutes a descendant of the virtualization root. - /// - /// - /// ProjFS sends notifications only for the primary data stream of a file. ProjFS does not - /// send notifications for operations on alternate data streams. - /// - /// - /// ProjFS does not send notifications for an inactive virtualization instance. A virtualization - /// instance is inactive if any one of the following is true: - /// - /// - /// - /// The provider has not yet started it by calling ProjFS.VirtualizationInstance.StartVirtualizing. - /// - /// - /// - /// - /// The provider has stopped the instance by calling ProjFS.VirtualizationInstance.StopVirtualizing. - /// - /// - /// - /// - /// The provider process has exited. - /// - /// - /// - /// - /// - /// The provider may specify which notifications it wishes to receive when starting a virtualization - /// instance, or in response to a notification that allows a new notification mask to be set. - /// The provider specifies a default set of notifications that it wants ProjFS to send for the - /// virtualization instance when it starts the instance. The provider specifies the default - /// notifications via the parameter of the - /// ProjFS.VirtualizationInstance constructor, which may specify different notification - /// masks for different subtrees of the virtualization instance. - /// - /// - /// The provider may choose to supply a different notification mask in response to a notification - /// of file open, create, overwrite, or rename. ProjFS will continue to send these notifications - /// for the given file until all handles to the file are closed. After that it will revert - /// to the default set of notifications. Naturally if the default set of notifications does - /// not specify that ProjFS should notify for open, create, etc., the provider will not get - /// the opportunity to specify a different mask for those operations. - /// - /// - [System::FlagsAttribute] - public enum class NotificationType : unsigned long - { - /// - /// Indicates that the provider does not want any notifications. This value overrides all others. - /// - None = PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFileOpened callback when a handle is created to an existing file or directory. - /// - FileOpened = PRJ_NOTIFY_FILE_OPENED, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyNewFileCreated callback when a new file or directory is created. - /// - NewFileCreated = PRJ_NOTIFY_NEW_FILE_CREATED, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFileOverwritten callback when an existing file is superseded or overwritten. - /// - FileOverwritten = PRJ_NOTIFY_FILE_OVERWRITTEN, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyPreDelete callback when a file or directory is about to be deleted. - /// - PreDelete = PRJ_NOTIFY_PRE_DELETE, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyPreRename callback when a file or directory is about to be renamed. - /// - PreRename = PRJ_NOTIFY_PRE_RENAME, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyPreCreateHardlink callback when a hard link is about to be created for a file. - /// - PreCreateHardlink = PRJ_NOTIFY_PRE_SET_HARDLINK, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFileRenamed callback when a file or directory has been renamed. - /// - FileRenamed = PRJ_NOTIFY_FILE_RENAMED, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyHardlinkCreated callback when a hard link has been created for a file. - /// - HardlinkCreated = PRJ_NOTIFY_HARDLINK_CREATED, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedNoModification callback when a handle is closed on a file or directory - /// and the closing handle neither modified nor deleted it. - /// - FileHandleClosedNoModification = PRJ_NOTIFY_FILE_HANDLE_CLOSED_NO_MODIFICATION, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedFileModifiedOrDeleted callback when a handle is closed on a file or - /// directory and the closing handle was used to modify it. - /// - FileHandleClosedFileModified = PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_MODIFIED, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedFileModifiedOrDeleted callback when a handle is closed on a file or - /// directory and it is deleted as part of closing the handle. - /// - FileHandleClosedFileDeleted = PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED, - - /// - /// Indicates that ProjFS should call the provider's OnNotifyFilePreConvertToFull callback when it is about to convert a placeholder to a full file. - /// - FilePreConvertToFull = PRJ_NOTIFY_FILE_PRE_CONVERT_TO_FULL, - - /// - /// This value is not used when calling the VirtualizationInstance constructor. It - /// is only returned from OnNotify... callbacks that have a - /// parameter, and indicates that the provider wants to continue to receive the notifications - /// it registered for when starting the virtualization instance. - /// - UseExistingMask = static_cast>(PRJ_NOTIFY_USE_EXISTING_MASK) - }; -}}} // namespace Microsoft.Windows.ProjFS diff --git a/ProjectedFSLib.Managed.API/OnDiskFileState.h b/ProjectedFSLib.Managed.API/OnDiskFileState.h deleted file mode 100644 index 8574f8d..0000000 --- a/ProjectedFSLib.Managed.API/OnDiskFileState.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// Defines values describing the on-disk state of a virtualized file. - /// - /// - /// The state is used to managed deleted files. When - /// a directory is enumerated ProjFS merges the set of local items (placeholders, full files, - /// etc.) with the set of virtual items projected by the provider's IRequiredCallbacks::GetDirectoryEnumerationCallback - /// method. If an item appears in both the local and projected sets, the local item takes precedence. - /// If a file does not exist there is no local state, so it would appear in the enumeration. - /// However if that item had been deleted, having it appear in the enumeration would be unexpected. - /// ProjFS deals with this by replacing a deleted item with a special hidden placeholder called - /// a "tombstone". This has the following effects: - /// - /// - /// Enumerations do not reveal the item. - /// - /// - /// File opens that expect the item to exist fail with e.g. "file not found". - /// - /// - /// File creates that expect to succeed only if the item does not exist succeed; - /// ProjFS removes the tombstone as part of the operation. - /// - /// - /// - /// - /// To illustrate the on-disk states consider the following sequence, given a ProjFS provider - /// that has a single file "foo.txt" located in the virtualization root C:\root. - /// - /// - /// An app enumerates C:\root. It sees the virtual file "foo.txt". Since the - /// file has not yet been accessed, the file does not exist on disk. - /// - /// - /// The app opens a handle to C:\root\foo.txt. ProjFS tells the provider to - /// create a placeholder for it. The file's state is now - /// - /// - /// The app reads the content of the file. The provider provides the file - /// content to ProjFS and it is cached to C:\root\foo.txt. The file's state is - /// now | . - /// - /// - /// The app updates the Last Modified timestamp. The file's state is now - /// | | . - /// - /// - /// The app writes some new data to the file. C:\root\foo.txt's state - /// is now . - /// - /// - /// The app deletes C:\root\foo.txt. ProjFS replaces the file with a tombstone, - /// so its state is now . - /// Now when the app enumerates C:\root it does not see foo.txt. If it tries to open the - /// file, the open fails with HResult.FileNotFound. - /// - /// - /// - /// - [System::FlagsAttribute] - public enum class OnDiskFileState : unsigned long - { - /// - /// The item's content (primary data stream) is not present on the disk. The item's metadata - /// (name, size, timestamps, attributes, etc.) is cached on the disk. - /// - Placeholder = PRJ_FILE_STATE_PLACEHOLDER, - - /// - /// The item's content and metadata have been cached to the disk. Also referred to as a - /// "partial file/directory". - /// - HydratedPlaceholder = PRJ_FILE_STATE_HYDRATED_PLACEHOLDER, - - /// - /// The item's metadata has been locally modified and is no longer a cache of its state in - /// the provider's store. Note that creating or deleting a file or directory under a placeholder - /// directory causes that placeholder directory to become dirty. - /// - DirtyPlaceholder = PRJ_FILE_STATE_DIRTY_PLACEHOLDER, - - /// - /// The item's content (primary data stream) has been modified. The file is no longer a cache - /// of its state in the provider's store. Files that have been created on the local file - /// system (i.e.that do not exist in the provider's store at all) are also considered to be - /// full files. - /// - Full = PRJ_FILE_STATE_FULL, - - /// - /// A special hidden placeholder that represents an item that has been deleted from the local - /// file system. - /// - Tombstone = PRJ_FILE_STATE_TOMBSTONE - }; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/ProjectedFSLib.Managed.props b/ProjectedFSLib.Managed.API/ProjectedFSLib.Managed.props deleted file mode 100644 index 41ad821..0000000 --- a/ProjectedFSLib.Managed.API/ProjectedFSLib.Managed.props +++ /dev/null @@ -1,148 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - ManagedCProj - Microsoft.Windows.ProjFS - 10.0 - - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - Unicode - Spectre - - - - - - - - - - - - - - - true - MixedRecommendedRules.ruleset - true - ProjectedFSLib.Managed - C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041\um;$(IncludePath) - - - false - MixedRecommendedRules.ruleset - false - ProjectedFSLib.Managed - C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um;$(IncludePath) - - - - stdcpp17 - true - true - Level4 - true - true - C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um;$(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) - - - ProjectedFSLib.lib;%(AdditionalDependencies) - C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64;%(AdditionalLibraryDirectories) - - - $(ProjectDir)\..\Scripts\CreateVersionHeader.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) && $(ProjectDir)\..\Scripts\CreateCliAssemblyVersion.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) - - - $(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) - - - - - stdcpp17 - true - true - Level4 - true - false - true - C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um;$(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) - - - ProjectedFSLib.lib;%(AdditionalDependencies) - C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64;%(AdditionalLibraryDirectories) - - - $(ProjectDir)\..\Scripts\CreateVersionHeader.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) && $(ProjectDir)\..\Scripts\CreateCliAssemblyVersion.bat $(ProjectName) $(ProjFSManagedVersion) $(SolutionDir) - - - $(BuildOutputDir)\$(ProjectName);%(AdditionalIncludeDirectories) - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/Resource.h b/ProjectedFSLib.Managed.API/Resource.h deleted file mode 100644 index 8284703..0000000 Binary files a/ProjectedFSLib.Managed.API/Resource.h and /dev/null differ diff --git a/ProjectedFSLib.Managed.API/UpdateFailureCause.h b/ProjectedFSLib.Managed.API/UpdateFailureCause.h deleted file mode 100644 index 5b85341..0000000 --- a/ProjectedFSLib.Managed.API/UpdateFailureCause.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Defines values that describe why an attempt to update or delete a file in a virtualization - /// root has failed. - /// - /// - /// These values are used in the output parameter of - /// ProjFS.VirtualizationInstance.UpdateFileIfNeeded and ProjFS.VirtualizationInstance.DeleteFile. - /// These are set if the API returns HResult.VirtualizationInvalidOp because the file state - /// does not allow the operation with the value(s) passed to the API. - /// - [System::FlagsAttribute] - public enum class UpdateFailureCause : unsigned long - { - /// - /// The update did not fail. - /// - NoFailure = PRJ_UPDATE_FAILURE_CAUSE_NONE, - - /// - /// The item was a dirty placeholder (hydrated or not), and the provider did not specify - /// UpdateType.AllowDirtyMetadata. - /// - DirtyMetadata = PRJ_UPDATE_FAILURE_CAUSE_DIRTY_METADATA, - - /// - /// The item was a full file and the provider did not specify UpdateType.AllowDirtyData. - /// - DirtyData = PRJ_UPDATE_FAILURE_CAUSE_DIRTY_DATA, - - /// - /// The item was a tombstone and the provider did not specify UpdateType.AllowTombstone. - /// - Tombstone = PRJ_UPDATE_FAILURE_CAUSE_TOMBSTONE, - - /// - /// The item had the DOS read-only bit set and the provider did not specify UpdateType.AllowReadOnly. - /// - ReadOnly = PRJ_UPDATE_FAILURE_CAUSE_READ_ONLY - }; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/UpdateType.h b/ProjectedFSLib.Managed.API/UpdateType.h deleted file mode 100644 index 8f0f1ab..0000000 --- a/ProjectedFSLib.Managed.API/UpdateType.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// Defines values describing when to allow a virtualized file to be deleted or updated. - /// - /// - /// These values are used in the input parameter of - /// ProjFS.VirtualizationInstance.UpdateFileIfNeeded and ProjFS.VirtualizationInstance.DeleteFile. - /// The flags control whether ProjFS should allow the update given the state of the file or directory on disk. - /// - /// - /// See the documentation for ProjFS.OnDiskFileState for a description of possible file - /// and directory states in ProjFS. - /// - /// - [System::FlagsAttribute] - public enum class UpdateType : unsigned long - { - /// - /// ProjFS will allow the update if the item is a placeholder or a dirty placeholder (whether hydrated or not). - /// - AllowDirtyMetadata = PRJ_UPDATE_ALLOW_DIRTY_METADATA, - - /// - /// ProjFS will allow the update if the item is a placeholder or is a full file. - /// - AllowDirtyData = PRJ_UPDATE_ALLOW_DIRTY_DATA, - - /// - /// ProjFS will allow the update if the item is a placeholder or is a tombstone. - /// - AllowTombstone = PRJ_UPDATE_ALLOW_TOMBSTONE, - - /// - /// ProjFS will allow the update regardless of whether the DOS read-only bit is set on the item. - /// - AllowReadOnly = PRJ_UPDATE_ALLOW_READ_ONLY - }; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/Utils.cpp b/ProjectedFSLib.Managed.API/Utils.cpp deleted file mode 100644 index c63830a..0000000 --- a/ProjectedFSLib.Managed.API/Utils.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "stdafx.h" -#include "Utils.h" - -using namespace System; -using namespace Microsoft::Windows::ProjFS; - -bool Utils::TryGetOnDiskFileState(String^ fullPath, [Out] OnDiskFileState% fileState) -{ - pin_ptr path = PtrToStringChars(fullPath); - PRJ_FILE_STATE localFileState; - if (FAILED(::PrjGetOnDiskFileState(path, &localFileState))) - { - return false; - } - - fileState = static_cast(localFileState); - - return true; -} - -bool Utils::IsFileNameMatch(String^ fileNameToCheck, String^ pattern) -{ - pin_ptr pFileNameToCheck = PtrToStringChars(fileNameToCheck); - pin_ptr pPattern = PtrToStringChars(pattern); - - return ::PrjFileNameMatch(pFileNameToCheck, pPattern); -} - -int Utils::FileNameCompare(String^ fileName1, String^ fileName2) -{ - pin_ptr pFileName1 = PtrToStringChars(fileName1); - pin_ptr pFileName2 = PtrToStringChars(fileName2); - - return ::PrjFileNameCompare(pFileName1, pFileName2); -} - -bool Utils::DoesNameContainWildCards(String^ fileName) -{ - pin_ptr pFileName = PtrToStringChars(fileName); - - return ::PrjDoesNameContainWildCards(pFileName); -} diff --git a/ProjectedFSLib.Managed.API/Utils.h b/ProjectedFSLib.Managed.API/Utils.h deleted file mode 100644 index e619325..0000000 --- a/ProjectedFSLib.Managed.API/Utils.h +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "OnDiskFileState.h" -#include "HResult.h" - -using namespace System::Runtime::InteropServices; - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Provides utility methods for ProjFS providers. - /// - public ref class Utils abstract sealed - { - public: - /// - /// Returns the on-disk state of the specified file or directory. - /// - /// - /// - /// This routine tells the caller what the ProjFS caching state is of the specified file or - /// directory. For example, the caller can use this routine to determine whether the given item - /// is a placeholder or full file. - /// - /// - /// A running provider should be cautious if using this routine on files or directories within - /// one of its virtualization instances, as it may cause callbacks to be invoked in the provider. - /// Depending on the design of the provider this may lead to deadlocks. - /// - /// - /// Full path of the file or the directory. - /// On successful return contains a bitwise-OR of - /// values describing the file state. - /// - /// false if does not exist. - /// - static bool TryGetOnDiskFileState( - System::String^ fullPath, - [Out] OnDiskFileState% fileState); - - /// - /// Determines whether a file name string matches a pattern, potentially containing - /// wildcard characters, according to the rules used by the file system. - /// - /// - /// A provider should use this routine in its implementation of the GetDirectoryEnumerationCallback - /// delegate to determine whether a name it its backing store matches the search expression - /// from the filterFileName parameter of the GetDirectoryEnumerationCallback - /// delegate. - /// - /// The file name to check against . - /// The pattern for which to search. - /// true if matches , - /// false otherwise. - static bool IsFileNameMatch( - System::String^ fileNameToCheck, - System::String^ pattern); - - /// - /// Compares two file names and returns a value that indicates their relative collation order. - /// - /// - /// The provider may use this routine to determine how to sort file names in the same order - /// that the file system does. - /// - /// The first name to compare. - /// The second name to compare. - /// - /// A negative number if is before in collation order. - /// 0 if is equal to . - /// A positive number if is after in collation order. - /// - static int FileNameCompare( - System::String^ fileName1, - System::String^ fileName2); - - /// Determines whether a string contains any wildcard characters. - /// - /// - /// This routine checks for the wildcard characters recognized by the file system. These - /// wildcards are sent by programs such as the cmd.exe command interpreter. - /// - /// - /// - /// - /// Character - /// Meaning - /// - /// - /// * - /// Matches 0 or more characters. - /// - /// - /// ? - /// Matches exactly one character. - /// - /// - /// DOS_DOT (") - /// Matches either a ".", or zero characters beyond the name string. - /// - /// - /// DOS_STAR (<) - /// Matches 0 or more characters until encountering and matching the final "." in the name. - /// - /// - /// DOS_QM (>) - /// Matches any single character, or upon encountering a period or end of name string, advances the expression - /// to the end of the set of contiguous DOS_QMs. - /// - /// - /// - /// - /// A string to check for wildcard characters. - /// true if contains any wildcard characters, - /// false otherwise. - static bool DoesNameContainWildCards( - System::String^ fileName); - }; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/VirtualizationInstance.cpp b/ProjectedFSLib.Managed.API/VirtualizationInstance.cpp deleted file mode 100644 index 3aa5ef3..0000000 --- a/ProjectedFSLib.Managed.API/VirtualizationInstance.cpp +++ /dev/null @@ -1,1814 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "stdafx.h" -#include "VirtualizationInstance.h" -#include "Utils.h" - -using namespace System; -using namespace System::ComponentModel; -using namespace System::Globalization; -using namespace System::IO; -using namespace Microsoft::Windows::ProjFS; - -namespace { -#pragma region Prototypes - -_Function_class_(PRJ_START_DIRECTORY_ENUMERATION_CB) -HRESULT PrjStartDirectoryEnumerationCB( - _In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LPCGUID enumerationId); - -_Function_class_(PRJ_END_DIRECTORY_ENUMERATION_CB) -HRESULT PrjEndDirectoryEnumerationCB( - _In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LPCGUID enumerationId); - -_Function_class_(PRJ_GET_DIRECTORY_ENUMERATION_CB) -HRESULT PrjGetDirectoryEnumerationCB( - _In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LPCGUID enumerationId, - _In_opt_z_ LPCWSTR searchExpression, - _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle); - -_Function_class_(PRJ_QUERY_FILE_NAME_CB) -HRESULT PrjQueryFileNameCB( - _In_ PRJ_CALLBACK_DATA* callbackData); - -_Function_class_(PRJ_GET_PLACEHOLDER_INFO_CB) -HRESULT PrjGetPlaceholderInfoCB( - _In_ const PRJ_CALLBACK_DATA* callbackData); - -_Function_class_(PRJ_GET_FILE_DATA_CB) -HRESULT PrjGetFileDataCB( - _In_ const PRJ_CALLBACK_DATA* callbackData, - _In_ UINT64 byteOffset, - _In_ UINT32 length); - -_Function_class_(PRJ_NOTIFICATION_CB) -HRESULT PrjNotificationCB( - _In_ const PRJ_CALLBACK_DATA* callbackData, - _In_ BOOLEAN isDirectory, - _In_ PRJ_NOTIFICATION notification, - _In_opt_ PCWSTR destinationFileName, - _Inout_ PRJ_NOTIFICATION_PARAMETERS* operationParameters); - -_Function_class_(PRJ_CANCEL_COMMAND_CB) -void PrjCancelCommandCB( - _In_ PRJ_CALLBACK_DATA* callbackData); - -#pragma region Windows 10 1803 support - -HRESULT PrjGetPlaceholderInformationCB( - _In_ PRJ_CALLBACK_DATA* callbackData, - _In_ DWORD desiredAccess, - _In_ DWORD shareMode, - _In_ DWORD createDisposition, - _In_ DWORD createOptions, - _In_ LPCWSTR destinationFileName); - -HRESULT PrjGetFileStreamCB( - _In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LARGE_INTEGER byteOffset, - _In_ DWORD length); - -HRESULT PrjNotifyOperationCB( - _In_ PRJ_CALLBACK_DATA* callbackData, - _In_ BOOLEAN isDirectory, - _In_ PRJ_NOTIFICATION_TYPE notificationType, - _In_opt_ LPCWSTR destinationFileName, - _Inout_ PRJ_OPERATION_PARAMETERS* operationParameters); - -std::shared_ptr CreatePlaceholderInformation( - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - bool directory, - array^ contentId, - array^ providerId); - -#pragma endregion - -#pragma region Utility routines - -array^ MarshalPlaceholderId(UCHAR* sourceId); - -void CopyPlaceholderId(UCHAR* destinationId, array^ contentId); - -bool IsPowerOf2(unsigned long num); - -Guid GUIDtoGuid(const GUID& guid); - -std::shared_ptr CreatePlaceholderInfo( - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - bool directory, - array^ contentId, - array^ providerId); - -String^ GetTriggeringProcessNameSafe(const PRJ_CALLBACK_DATA* callbackData); - -#pragma endregion - -#pragma endregion - -// Converts a strongly typed enum to its underlying type -template -constexpr std::underlying_type_t CastToUnderlyingType(T e) noexcept -{ - return static_cast>(e); -} - -// Converts a Win32-derived HRESULT back to a Win32 error code. Note that the general WIN32_FROM_HRESULT -// is not possible to make. See https://blogs.msdn.microsoft.com/oldnewthing/20061103-07/?p=29133 -_Success_(return) -bool Win32FromHRESULT( - _In_ HRESULT hr, - _Out_ DWORD* win32Error -) -{ - // If the high word is 0x8007, then we have a Win32 error HRESULT. - if ((hr & 0xFFFF0000) == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0)) - { - // Could have come from many values, but we choose this one - *win32Error = HRESULT_CODE(hr); - return true; - } - - if (hr == S_OK) - { - *win32Error = HRESULT_CODE(hr); - return true; - } - - // Otherwise, we got a value we can't convert. - return false; -} -} - -VirtualizationInstance::VirtualizationInstance( - String^ virtualizationRootPath, - unsigned int poolThreadCount, - unsigned int concurrentThreadCount, - bool enableNegativePathCache, - System::Collections::Generic::IReadOnlyCollection^ notificationMappings -) - : m_virtualizationRootPath(virtualizationRootPath) - , m_poolThreadCount(poolThreadCount) - , m_concurrentThreadCount(concurrentThreadCount) - , m_enableNegativePathCache(enableNegativePathCache) - , m_notificationMappings(notificationMappings) - , m_bytesPerSector(0) - , m_writeBufferAlignmentRequirement(0) - , m_virtualizationContext(nullptr) -{ - bool markAsRoot = false; - - // This will throw a FileLoadException if ProjectedFSLib.dll is not available. - m_apiHelper = gcnew ApiHelper(); - - // We need the root path in a form usable by native code. - pin_ptr rootPath = PtrToStringChars(m_virtualizationRootPath); - - DirectoryInfo^ dirInfo = gcnew DirectoryInfo(m_virtualizationRootPath); - System::Guid virtualizationInstanceID; - if (!dirInfo->Exists) - { - // Generate a new instance ID and create the root. We'll mark it later. - virtualizationInstanceID = Guid::NewGuid(); - dirInfo->Create(); - - markAsRoot = true; - } - else - { - // Open the directory and query for a ProjFS reparse point. - std::unique_ptr rootHandle(::CreateFile(rootPath, - FILE_READ_ATTRIBUTES, - FILE_SHARE_WRITE | FILE_SHARE_READ, - 0, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, - nullptr)); - - if (rootHandle.get() == INVALID_HANDLE_VALUE) - { - auto lastError = ::GetLastError(); - throw gcnew Win32Exception(lastError, String::Format(CultureInfo::InvariantCulture, - "Failed to open root directory {0}.", - m_virtualizationRootPath)); - } - - std::vector buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE, 0); - REPARSE_DATA_BUFFER* reparseBuffer = reinterpret_cast(&buffer[0]); - - auto querySuccess = ::DeviceIoControl(rootHandle.get(), - FSCTL_GET_REPARSE_POINT, - nullptr, - 0, - reparseBuffer, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE, - nullptr, - nullptr); - - if (!querySuccess) - { - auto lastError = ::GetLastError(); - - if (lastError == ERROR_NOT_A_REPARSE_POINT) - { - // If this directory doesn't have a reparse point on it we'll need to mark it as a - // root. We need an ID for that. - virtualizationInstanceID = Guid::NewGuid(); - markAsRoot = true; - } - else - { - throw gcnew Win32Exception(lastError, String::Format(CultureInfo::InvariantCulture, - "Failed to query for ProjFS reparse point on {0}.", - m_virtualizationRootPath)); - } - } - - // If we did find a reparse point, see if it is one of ours. - if (!markAsRoot) - { - if (reparseBuffer->ReparseTag != IO_REPARSE_TAG_PROJFS) - { - throw gcnew Win32Exception(ERROR_REPARSE_TAG_MISMATCH, - String::Format(CultureInfo::InvariantCulture, - "Root directory {0} already has a different reparse point.", - m_virtualizationRootPath)); - } - } - } - - // If we created the root or found one without a ProjFS reparse point, we'll mark it as the root. - if (markAsRoot) - { - HResult markResult = this->MarkDirectoryAsVirtualizationRoot(m_virtualizationRootPath, - virtualizationInstanceID); - - if (markResult != HResult::Ok) - { - DWORD error; - if (!Win32FromHRESULT(CastToUnderlyingType(markResult), &error)) - { - // This should not happen. The ProjFS APIs always return HRESULTs that can be - // expressed as Win32 error codes. - error = ERROR_INTERNAL_ERROR; - } - - throw gcnew Win32Exception(error, String::Format(CultureInfo::InvariantCulture, - "Failed to mark directory {0} as virtualization root.", - m_virtualizationRootPath)); - } - } -} - -#pragma region Callback properties - -QueryFileNameCallback^ VirtualizationInstance::OnQueryFileName::get(void) -{ - return m_queryFileNameCallback; -} - -void VirtualizationInstance::OnQueryFileName::set(QueryFileNameCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_queryFileNameCallback = callbackDelegate; -} - -CancelCommandCallback^ VirtualizationInstance::OnCancelCommand::get(void) -{ - return m_cancelCommandCallback; -} - -void VirtualizationInstance::OnCancelCommand::set(CancelCommandCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_cancelCommandCallback = callbackDelegate; -} - -NotifyFileOpenedCallback^ VirtualizationInstance::OnNotifyFileOpened::get(void) -{ - return m_notifyFileOpenedCallback; -} - -void VirtualizationInstance::OnNotifyFileOpened::set(NotifyFileOpenedCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyFileOpenedCallback = callbackDelegate; -} - -NotifyNewFileCreatedCallback^ VirtualizationInstance::OnNotifyNewFileCreated::get(void) -{ - return m_notifyNewFileCreatedCallback; -} - -void VirtualizationInstance::OnNotifyNewFileCreated::set(NotifyNewFileCreatedCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyNewFileCreatedCallback = callbackDelegate; -} - -NotifyFileOverwrittenCallback^ VirtualizationInstance::OnNotifyFileOverwritten::get(void) -{ - return m_notifyFileOverwrittenCallback; -} - -void VirtualizationInstance::OnNotifyFileOverwritten::set(NotifyFileOverwrittenCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyFileOverwrittenCallback = callbackDelegate; -} - -NotifyPreDeleteCallback^ VirtualizationInstance::OnNotifyPreDelete::get(void) -{ - return m_notifyPreDeleteCallback; -} - -void VirtualizationInstance::OnNotifyPreDelete::set(NotifyPreDeleteCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyPreDeleteCallback = callbackDelegate; -} - -NotifyPreRenameCallback^ VirtualizationInstance::OnNotifyPreRename::get(void) -{ - return m_notifyPreRenameCallback; -} - -void VirtualizationInstance::OnNotifyPreRename::set(NotifyPreRenameCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyPreRenameCallback = callbackDelegate; -} - -NotifyPreCreateHardlinkCallback^ VirtualizationInstance::OnNotifyPreCreateHardlink::get(void) -{ - return m_notifyPreCreateHardlinkCallback; -} - -void VirtualizationInstance::OnNotifyPreCreateHardlink::set(NotifyPreCreateHardlinkCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyPreCreateHardlinkCallback = callbackDelegate; -} - -NotifyFileRenamedCallback^ VirtualizationInstance::OnNotifyFileRenamed::get(void) -{ - return m_notifyFileRenamedCallback; -} - -void VirtualizationInstance::OnNotifyFileRenamed::set(NotifyFileRenamedCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyFileRenamedCallback = callbackDelegate; -} - -NotifyHardlinkCreatedCallback^ VirtualizationInstance::OnNotifyHardlinkCreated::get(void) -{ - return m_notifyHardlinkCreatedCallback; -} - -void VirtualizationInstance::OnNotifyHardlinkCreated::set(NotifyHardlinkCreatedCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyHardlinkCreatedCallback = callbackDelegate; -} - -NotifyFileHandleClosedNoModificationCallback^ VirtualizationInstance::OnNotifyFileHandleClosedNoModification::get(void) -{ - return m_notifyFileHandleClosedNoModificationCallback; -} - -void VirtualizationInstance::OnNotifyFileHandleClosedNoModification::set(NotifyFileHandleClosedNoModificationCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyFileHandleClosedNoModificationCallback = callbackDelegate; -} - -NotifyFileHandleClosedFileModifiedOrDeletedCallback^ VirtualizationInstance::OnNotifyFileHandleClosedFileModifiedOrDeleted::get(void) -{ - return m_notifyFileHandleClosedFileModifiedOrDeletedCallback; -} - -void VirtualizationInstance::OnNotifyFileHandleClosedFileModifiedOrDeleted::set(NotifyFileHandleClosedFileModifiedOrDeletedCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyFileHandleClosedFileModifiedOrDeletedCallback = callbackDelegate; -} - -NotifyFilePreConvertToFullCallback^ VirtualizationInstance::OnNotifyFilePreConvertToFull::get(void) -{ - return m_notifyFilePreConvertToFullCallback; -} - -void VirtualizationInstance::OnNotifyFilePreConvertToFull::set(NotifyFilePreConvertToFullCallback^ callbackDelegate) -{ - ConfirmNotStarted(); - m_notifyFilePreConvertToFullCallback = callbackDelegate; -} - -IRequiredCallbacks^ VirtualizationInstance::RequiredCallbacks::get(void) -{ - return m_requiredCallbacks; -} - -ApiHelper^ VirtualizationInstance::ApiHelperObject::get(void) -{ - return m_apiHelper; -} - -#pragma endregion - -#pragma region Other properties - -Guid VirtualizationInstance::VirtualizationInstanceId::get(void) -{ - ConfirmStarted(); - return m_virtualizationInstanceID; -} - -int VirtualizationInstance::PlaceholderIdLength::get(void) -{ - return PRJ_PLACEHOLDER_ID_LENGTH; -} - -#pragma endregion - -#pragma region Public method implementations - -HResult VirtualizationInstance::StartVirtualizing(IRequiredCallbacks^ requiredCallbacks) -{ - if (m_virtualizationContextGc != nullptr) - { - return HResult::AlreadyInitialized; - } - - // Store the provider's implementation of the required callbacks. - m_requiredCallbacks = requiredCallbacks; - - // Create a handle to this VirtualizationInstance object so that it can be passed to the native - // lib as the InstanceContext parameter to PrjStartVirtualizing(). - m_virtualizationContextGc = new gcroot(); - *(m_virtualizationContextGc) = this; - - HRESULT startHr = S_OK; - if (m_apiHelper->UseBetaApi) - { - // Query the file system for sector alignment info that CreateWriteBuffer() will need. - FindBytesPerSectorAndAlignment(); - - PRJ_COMMAND_CALLBACKS callbacks; - m_apiHelper->_PrjCommandCallbacksInit(sizeof(callbacks), &callbacks); - - // Set the native wrappers for the required callbacks. - callbacks.PrjStartDirectoryEnumeration = reinterpret_cast(PrjStartDirectoryEnumerationCB); - callbacks.PrjEndDirectoryEnumeration = reinterpret_cast(PrjEndDirectoryEnumerationCB); - callbacks.PrjGetDirectoryEnumeration = reinterpret_cast(PrjGetDirectoryEnumerationCB); - callbacks.PrjGetPlaceholderInformation = reinterpret_cast(PrjGetPlaceholderInformationCB); - callbacks.PrjGetFileStream = reinterpret_cast(PrjGetFileStreamCB); - - // Set native wrappers for the optional callbacks if the provider has an implementation for them. - - if (OnQueryFileName != nullptr) - { - callbacks.PrjQueryFileName = reinterpret_cast(PrjQueryFileNameCB); - } - - if (OnCancelCommand != nullptr) - { - callbacks.PrjCancelCommand = reinterpret_cast(PrjCancelCommandCB); - } - - // We set the native wrapper for the notify callback if the provider set at least one OnNotify* property. - if ((OnNotifyFileOpened != nullptr) || - (OnNotifyNewFileCreated != nullptr) || - (OnNotifyFileOverwritten != nullptr) || - (OnNotifyPreDelete != nullptr) || - (OnNotifyPreRename != nullptr) || - (OnNotifyPreCreateHardlink != nullptr) || - (OnNotifyFileRenamed != nullptr) || - (OnNotifyHardlinkCreated != nullptr) || - (OnNotifyFileHandleClosedNoModification != nullptr) || - (OnNotifyFileHandleClosedFileModifiedOrDeleted != nullptr) || - (OnNotifyFilePreConvertToFull != nullptr)) - { - callbacks.PrjNotifyOperation = reinterpret_cast(PrjNotifyOperationCB); - } - - pin_ptr rootPath = PtrToStringChars(m_virtualizationRootPath); - - // Use a temp location to avoid e0158. - pin_ptr tempHandle = &(m_virtualizationContext); - auto instanceHandle = reinterpret_cast(tempHandle); - - VIRTUALIZATION_INST_EXTENDED_PARAMETERS extendedParameters; - memset(&extendedParameters, 0, sizeof(VIRTUALIZATION_INST_EXTENDED_PARAMETERS)); - - extendedParameters.Size = sizeof(VIRTUALIZATION_INST_EXTENDED_PARAMETERS); - extendedParameters.Flags = m_enableNegativePathCache ? PRJ_FLAG_INSTANCE_NEGATIVE_PATH_CACHE : 0; - extendedParameters.PoolThreadCount = m_poolThreadCount; - extendedParameters.ConcurrentThreadCount = m_concurrentThreadCount; - - if (m_notificationMappings->Count > 0) - { - std::unique_ptr nativeNotificationMappings; - // "Pinning pointers can only be declared as non-static local variables on the stack." - // https://docs.microsoft.com/en-us/cpp/windows/pin-ptr-cpp-cli - // And so lets keep a copy of the paths alive rather than trying to pin the incoming strings - std::vector notificationPaths; - - notificationPaths.reserve(m_notificationMappings->Count); - nativeNotificationMappings.reset(new PRJ_NOTIFICATION_MAPPING[m_notificationMappings->Count]); - - int index = 0; - for each(NotificationMapping^ mapping in m_notificationMappings) - { - nativeNotificationMappings[index].NotificationBitMask = static_cast(mapping->NotificationMask); - - pin_ptr path = PtrToStringChars(mapping->NotificationRoot); - notificationPaths.push_back(std::wstring(path)); - nativeNotificationMappings[index].NotificationRoot = notificationPaths.rbegin()->c_str(); - ++index; - } - - extendedParameters.NotificationMappings = nativeNotificationMappings.get(); - extendedParameters.NumNotificationMappingsCount = m_notificationMappings->Count; - - startHr = m_apiHelper->_PrjStartVirtualizationInstanceEx(rootPath, - &callbacks, - m_virtualizationContextGc, - &extendedParameters, - instanceHandle); - } - else - { - // The caller didn't provide any notification mappings. Use the non-Ex Start routine to get - // ProjFS to supply the default notification mask. - startHr = m_apiHelper->_PrjStartVirtualizationInstance(rootPath, - &callbacks, - extendedParameters.Flags, - 0, // ProjFS will default to PRJ_DEFAULT_NOTIFICATION_MASK defined in prjlibp.h - extendedParameters.PoolThreadCount, - extendedParameters.ConcurrentThreadCount, - m_virtualizationContextGc, - instanceHandle); - } - } - else - { - PRJ_CALLBACKS callbacks; - memset(&callbacks, 0, sizeof(PRJ_CALLBACKS)); - - // Set native wrappers for the required callbacks. - callbacks.StartDirectoryEnumerationCallback = reinterpret_cast(PrjStartDirectoryEnumerationCB); - callbacks.EndDirectoryEnumerationCallback = reinterpret_cast(PrjEndDirectoryEnumerationCB); - callbacks.GetDirectoryEnumerationCallback = reinterpret_cast(PrjGetDirectoryEnumerationCB); - callbacks.GetPlaceholderInfoCallback = reinterpret_cast(PrjGetPlaceholderInfoCB); - callbacks.GetFileDataCallback = reinterpret_cast(PrjGetFileDataCB); - - // Set native wrappers for the optional callbacks if the provider has an implementation for them. - - if (OnQueryFileName != nullptr) - { - callbacks.QueryFileNameCallback = reinterpret_cast(PrjQueryFileNameCB); - } - - if (OnCancelCommand != nullptr) - { - callbacks.CancelCommandCallback = reinterpret_cast(PrjCancelCommandCB); - } - - // We set the native wrapper for the notification callback if the provider set at least one OnNotify* property. - if ((OnNotifyFileOpened != nullptr) || - (OnNotifyNewFileCreated != nullptr) || - (OnNotifyFileOverwritten != nullptr) || - (OnNotifyPreDelete != nullptr) || - (OnNotifyPreRename != nullptr) || - (OnNotifyPreCreateHardlink != nullptr) || - (OnNotifyFileRenamed != nullptr) || - (OnNotifyHardlinkCreated != nullptr) || - (OnNotifyFileHandleClosedNoModification != nullptr) || - (OnNotifyFileHandleClosedFileModifiedOrDeleted != nullptr) || - (OnNotifyFilePreConvertToFull != nullptr)) - { - callbacks.NotificationCallback = reinterpret_cast(PrjNotificationCB); - } - - pin_ptr rootPath = PtrToStringChars(m_virtualizationRootPath); - pin_ptr namespaceCtx = &(m_virtualizationContext); - - PRJ_STARTVIRTUALIZING_OPTIONS startOptions; - memset(&startOptions, 0, sizeof(PRJ_STARTVIRTUALIZING_OPTIONS)); - - startOptions.Flags = m_enableNegativePathCache ? PRJ_FLAG_USE_NEGATIVE_PATH_CACHE : PRJ_FLAG_NONE; - startOptions.PoolThreadCount = m_poolThreadCount; - startOptions.ConcurrentThreadCount = m_concurrentThreadCount; - - // These need to stay in scope until we call PrjStartVirtualizing - std::unique_ptr nativeNotificationMappings; - std::vector notificationPaths; - - if (m_notificationMappings->Count > 0) - { - // "Pinning pointers can only be declared as non-static local variables on the stack." - // https://docs.microsoft.com/en-us/cpp/windows/pin-ptr-cpp-cli - // And so lets keep a copy of the paths alive rather than trying to pin the incoming strings - - notificationPaths.reserve(m_notificationMappings->Count); - nativeNotificationMappings.reset(new PRJ_NOTIFICATION_MAPPING[m_notificationMappings->Count]); - - int index = 0; - for each(NotificationMapping^ mapping in m_notificationMappings) - { - nativeNotificationMappings[index].NotificationBitMask = static_cast(mapping->NotificationMask); - - pin_ptr path = PtrToStringChars(mapping->NotificationRoot); - notificationPaths.push_back(std::wstring(path)); - nativeNotificationMappings[index].NotificationRoot = notificationPaths.rbegin()->c_str(); - ++index; - } - - startOptions.NotificationMappings = nativeNotificationMappings.get(); - startOptions.NotificationMappingsCount = m_notificationMappings->Count; - } - else - { - // ProjFS will supply a default notification mask for the root if there are no mappings. - startOptions.NotificationMappingsCount = 0; - } - - startHr = m_apiHelper->_PrjStartVirtualizing(rootPath, - &callbacks, - m_virtualizationContextGc, - &startOptions, - namespaceCtx); - } - - if (FAILED(startHr)) - { - return static_cast(startHr); - } - - // Store the virtualization instance ID. - GUID instanceId = {}; - Guid^ instanceIDRef; - HRESULT getInfoHr = S_OK; - if (m_apiHelper->UseBetaApi) - { - getInfoHr = m_apiHelper->_PrjGetVirtualizationInstanceIdFromHandle(reinterpret_cast(m_virtualizationContext), - &instanceId); - } - else - { - PRJ_VIRTUALIZATION_INSTANCE_INFO instanceInfo = {}; - getInfoHr = m_apiHelper->_PrjGetVirtualizationInstanceInfo(m_virtualizationContext, - &instanceInfo); - instanceId = instanceInfo.InstanceID; - } - - // If we couldn't get the instance ID, shut the instance down and return error (this is super - // unlikely; it could only happen if somehow the instance successfully started above, but its - // info is corrupt). - if (FAILED(getInfoHr)) - { - StopVirtualizing(); - return static_cast(getInfoHr); - } - - instanceIDRef = gcnew Guid(instanceId.Data1, - instanceId.Data2, - instanceId.Data3, - instanceId.Data4[0], - instanceId.Data4[1], - instanceId.Data4[2], - instanceId.Data4[3], - instanceId.Data4[4], - instanceId.Data4[5], - instanceId.Data4[6], - instanceId.Data4[7]); - - m_virtualizationInstanceID = *instanceIDRef; - - return HResult::Ok; -} - -void VirtualizationInstance::StopVirtualizing() -{ - HRESULT hr = S_OK; - if (m_apiHelper->UseBetaApi) - { - hr = m_apiHelper->_PrjStopVirtualizationInstance(m_virtualizationContext); - } - else - { - try - { - // The underlying native API throws a structured exception if the instance is in an invalid - // state. - m_apiHelper->_PrjStopVirtualizing(m_virtualizationContext); - } - catch (...) - { - // Catch the structured exception and remember that we had a failure. - hr = E_FAIL; - } - } - - if (SUCCEEDED(hr)) - { - m_virtualizationContext = nullptr; - - delete m_virtualizationContextGc; - m_virtualizationContextGc = nullptr; - } - else - { - // Since this is a resource-releasing routine we don't return an error code. Instead we throw. - throw gcnew InvalidOperationException("Virtualization instance in invalid state."); - } -} - -HResult VirtualizationInstance::ClearNegativePathCache([Out] unsigned int% totalEntryNumber) -{ - UINT32 entryCount = 0; - HResult result = static_cast(::PrjClearNegativePathCache(m_virtualizationContext, - &entryCount)); - totalEntryNumber = entryCount; - - return result; -} - -HResult VirtualizationInstance::WriteFileData( - Guid dataStreamId, - IWriteBuffer^ buffer, - unsigned long long byteOffset, - unsigned long length) -{ - if (buffer == nullptr) - { - return HResult::InvalidArg; - } - - array^ guidData = dataStreamId.ToByteArray(); - pin_ptr data = &(guidData[0]); - if (m_apiHelper->UseBetaApi) - { - return static_cast(m_apiHelper->_PrjWriteFile(reinterpret_cast(m_virtualizationContext), - reinterpret_cast(data), - buffer->Pointer.ToPointer(), - byteOffset, - length)); - } - else - { - return static_cast(m_apiHelper->_PrjWriteFileData(m_virtualizationContext, - reinterpret_cast(data), - buffer->Pointer.ToPointer(), - byteOffset, - length)); - } -} - -HResult VirtualizationInstance::DeleteFile( - String^ relativePath, - UpdateType updateFlags, - [Out] UpdateFailureCause% failureReason) -{ - pin_ptr path = PtrToStringChars(relativePath); - PRJ_UPDATE_FAILURE_CAUSES deleteFailureReason = PRJ_UPDATE_FAILURE_CAUSE_NONE; - HResult result = static_cast(::PrjDeleteFile(m_virtualizationContext, - path, - static_cast(updateFlags), - &deleteFailureReason)); - failureReason = static_cast(deleteFailureReason); - return result; -} - -HResult VirtualizationInstance::WritePlaceholderInfo(String^ relativePath, - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - bool isDirectory, - array^ contentId, - array^ providerId) -{ - if (relativePath == nullptr) - { - return HResult::InvalidArg; - } - - if (m_apiHelper->UseBetaApi) - { - std::shared_ptr fileInformation = CreatePlaceholderInformation(creationTime, - lastAccessTime, - lastWriteTime, - changeTime, - fileAttributes, - isDirectory ? 0 : endOfFile, - isDirectory, - contentId, - providerId); - - pin_ptr path = PtrToStringChars(relativePath); - return static_cast(m_apiHelper->_PrjWritePlaceholderInformation(reinterpret_cast(m_virtualizationContext), - path, - fileInformation.get(), - sizeof(PRJ_PLACEHOLDER_INFORMATION))); - } - else - { - std::shared_ptr placeholderInfo = CreatePlaceholderInfo(creationTime, - lastAccessTime, - lastWriteTime, - changeTime, - fileAttributes, - isDirectory ? 0 : endOfFile, - isDirectory, - contentId, - providerId); - - pin_ptr path = PtrToStringChars(relativePath); - return static_cast(m_apiHelper->_PrjWritePlaceholderInfo(m_virtualizationContext, - path, - placeholderInfo.get(), - sizeof(PRJ_PLACEHOLDER_INFO))); - } -} - - -HResult VirtualizationInstance::WritePlaceholderInfo2( - String^ relativePath, - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - bool isDirectory, - String^ symlinkTargetOrNull, - array^ contentId, - array^ providerId) -{ - // This API is supported in Windows 10 version 2004 and above. - if (m_apiHelper->SupportedApi < ApiLevel::v2004) - { - throw gcnew NotImplementedException("PrjWritePlaceholderInfo2 is not supported in this version of Windows."); - } - - if (relativePath == nullptr) - { - return HResult::InvalidArg; - } - - std::shared_ptr placeholderInfo = CreatePlaceholderInfo(creationTime, - lastAccessTime, - lastWriteTime, - changeTime, - fileAttributes, - isDirectory ? 0 : endOfFile, - isDirectory, - contentId, - providerId); - - pin_ptr path = PtrToStringChars(relativePath); - - if (symlinkTargetOrNull != nullptr) - { - PRJ_EXTENDED_INFO extendedInfo = {}; - - extendedInfo.InfoType = PRJ_EXT_INFO_TYPE_SYMLINK; - pin_ptr targetPath = PtrToStringChars(symlinkTargetOrNull); - extendedInfo.Symlink.TargetName = targetPath; - - return static_cast(m_apiHelper->_PrjWritePlaceholderInfo2(m_virtualizationContext, - path, - placeholderInfo.get(), - sizeof(PRJ_PLACEHOLDER_INFO), - &extendedInfo)); - } - else - { - return static_cast(m_apiHelper->_PrjWritePlaceholderInfo(m_virtualizationContext, - path, - placeholderInfo.get(), - sizeof(PRJ_PLACEHOLDER_INFO))); - } -} - -HResult VirtualizationInstance::UpdateFileIfNeeded(String^ relativePath, - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - array^ contentId, - array^ providerId, - UpdateType updateFlags, - [Out] UpdateFailureCause% failureReason) -{ - HResult result; - if (m_apiHelper->UseBetaApi) - { - std::shared_ptr fileInformation = CreatePlaceholderInformation( - creationTime, - lastAccessTime, - lastWriteTime, - changeTime, - fileAttributes, - endOfFile, - false, // directory - contentId, - providerId); - - unsigned long updateFailureReason = 0; - pin_ptr path = PtrToStringChars(relativePath); - result = static_cast(m_apiHelper->_PrjUpdatePlaceholderIfNeeded(reinterpret_cast(m_virtualizationContext), - path, - fileInformation.get(), - FIELD_OFFSET(PRJ_PLACEHOLDER_INFORMATION, VariableData), // We have written no variable data - CastToUnderlyingType(updateFlags), - &updateFailureReason)); - - failureReason = static_cast(updateFailureReason); - } - else - { - std::shared_ptr placeholderInfo = CreatePlaceholderInfo(creationTime, - lastAccessTime, - lastWriteTime, - changeTime, - fileAttributes, - endOfFile, - false, // directory - contentId, - providerId); - - PRJ_UPDATE_FAILURE_CAUSES updateFailureCause = PRJ_UPDATE_FAILURE_CAUSE_NONE; - pin_ptr path = PtrToStringChars(relativePath); - result = static_cast(m_apiHelper->_PrjUpdateFileIfNeeded(m_virtualizationContext, - path, - placeholderInfo.get(), - sizeof(PRJ_PLACEHOLDER_INFO), - static_cast(updateFlags), - &updateFailureCause)); - - failureReason = static_cast(updateFailureCause); - } - - return result; -} - -HResult VirtualizationInstance::CompleteCommand(int commandId) -{ - return CompleteCommand(commandId, HResult::Ok); -} - -HResult VirtualizationInstance::CompleteCommand( - int commandId, - HResult completionResult) -{ - return static_cast(::PrjCompleteCommand( - m_virtualizationContext, - commandId, - static_cast(completionResult), - nullptr)); -} - -HResult VirtualizationInstance::CompleteCommand( - int commandId, - IDirectoryEnumerationResults^ results) -{ - PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS extendedParams = { }; - - extendedParams.CommandType = PRJ_COMPLETE_COMMAND_TYPE_ENUMERATION; - // results has to be a concrete DirectoryEnumerationResults. - extendedParams.Enumeration.DirEntryBufferHandle = safe_cast(results)->DirEntryBufferHandle; - - return static_cast(::PrjCompleteCommand( - m_virtualizationContext, - commandId, - static_cast(HResult::Ok), - &extendedParams)); -} - -HResult VirtualizationInstance::CompleteCommand( - int commandId, - NotificationType newNotificationMask) -{ - PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS extendedParams = { }; - - extendedParams.CommandType = PRJ_COMPLETE_COMMAND_TYPE_NOTIFICATION; - extendedParams.Notification.NotificationMask = static_cast(newNotificationMask); - - return static_cast(::PrjCompleteCommand( - m_virtualizationContext, - commandId, - static_cast(HResult::Ok), - &extendedParams)); -} - -IWriteBuffer^ VirtualizationInstance::CreateWriteBuffer( - unsigned int desiredBufferSize) -{ - WriteBuffer^ buffer; - - if (m_apiHelper->UseBetaApi) - { - if (desiredBufferSize < m_bytesPerSector) - { - desiredBufferSize = m_bytesPerSector; - } - else - { - unsigned int bufferRemainder = desiredBufferSize % m_bytesPerSector; - if (bufferRemainder != 0) - { - // Round up to nearest multiple of m_bytesPerSector - desiredBufferSize += (m_bytesPerSector - bufferRemainder); - } - } - - buffer = gcnew WriteBuffer(desiredBufferSize, m_writeBufferAlignmentRequirement); - } - else - { - // On Windows 10 version 1809 and above the alignment requirements are stored in the - // namespace virtualization context. - buffer = gcnew WriteBuffer(desiredBufferSize, - m_virtualizationContext, - m_apiHelper); - } - - return buffer; -} - -IWriteBuffer^ VirtualizationInstance::CreateWriteBuffer( - unsigned long long byteOffset, - unsigned int length, - [Out] unsigned long long% alignedByteOffset, - [Out] unsigned int% alignedLength) -{ - // Get the sector size so we can compute the aligned versions of byteOffset and length to return - // to the user. If we're on Windows 10 version 1803 the sector size is stored on the class. - // Otherwise it's available from the namespace virtualization context. - unsigned long bytesPerSector; - if (m_apiHelper->UseBetaApi) - { - bytesPerSector = m_bytesPerSector; - } - else - { - PRJ_VIRTUALIZATION_INSTANCE_INFO instanceInfo = {}; - auto result = m_apiHelper->_PrjGetVirtualizationInstanceInfo(m_virtualizationContext, - &instanceInfo); - - if (FAILED(result)) - { - DWORD error; - if (!Win32FromHRESULT(result, &error)) - { - // This should not happen. The ProjFS APIs always return HRESULTs that can be - // expressed as Win32 error codes. - error = ERROR_INTERNAL_ERROR; - } - - throw gcnew Win32Exception(error, String::Format(CultureInfo::InvariantCulture, - "Failed to retrieve virtualization instance info for directory {0}.", - m_virtualizationRootPath)); - - } - - bytesPerSector = instanceInfo.WriteAlignment; - } - - // alignedByteOffset is byteOffset, rounded down to the nearest bytesPerSector boundary. - alignedByteOffset = byteOffset & (0 - static_cast(bytesPerSector)); - - // alignedLength is the end offset of the requested range, rounded up to the nearest bytesPerSector - // boundary. - unsigned long long rangeEndOffset = byteOffset + static_cast(length); - unsigned long long alignedRangeEndOffset = (rangeEndOffset + (bytesPerSector - 1)) & (0 - bytesPerSector); - alignedLength = static_cast(alignedRangeEndOffset - alignedByteOffset); - - // Now that we've got the adjusted length, create the buffer itself. - return CreateWriteBuffer(alignedLength); -} - -HResult VirtualizationInstance::MarkDirectoryAsPlaceholder( - String^ targetDirectoryPath, - array^ contentId, - array^ providerId) -{ - GUID virtualizationInstanceId; - HRESULT hr = S_OK; - - if (m_apiHelper->UseBetaApi) - { - hr = m_apiHelper->_PrjGetVirtualizationInstanceIdFromHandle(reinterpret_cast(m_virtualizationContext), - &virtualizationInstanceId); - - if (hr == S_OK) - { - PRJ_PLACEHOLDER_VERSION_INFO versionInfo; - memset(&versionInfo, 0, sizeof(PRJ_PLACEHOLDER_VERSION_INFO)); - CopyPlaceholderId(versionInfo.ProviderID, providerId); - CopyPlaceholderId(versionInfo.ContentID, contentId); - - pin_ptr rootPath = PtrToStringChars(m_virtualizationRootPath); - pin_ptr targetPath = PtrToStringChars(targetDirectoryPath); - hr = m_apiHelper->_PrjConvertDirectoryToPlaceholder(rootPath, - targetPath, - &versionInfo, - 0, - &virtualizationInstanceId); - } - } - else - { - PRJ_VIRTUALIZATION_INSTANCE_INFO instanceInfo; - hr = m_apiHelper->_PrjGetVirtualizationInstanceInfo(m_virtualizationContext, - &instanceInfo); - - if (SUCCEEDED(hr)) - { - PRJ_PLACEHOLDER_VERSION_INFO versionInfo; - memset(&versionInfo, 0, sizeof(PRJ_PLACEHOLDER_VERSION_INFO)); - CopyPlaceholderId(versionInfo.ProviderID, providerId); - CopyPlaceholderId(versionInfo.ContentID, contentId); - - pin_ptr rootPath = PtrToStringChars(m_virtualizationRootPath); - pin_ptr targetPath = PtrToStringChars(targetDirectoryPath); - hr = m_apiHelper->_PrjMarkDirectoryAsPlaceholder(rootPath, - targetPath, - &versionInfo, - &instanceInfo.InstanceID); - } - } - - return static_cast(hr); -} - -//static -HResult VirtualizationInstance::MarkDirectoryAsVirtualizationRoot( - String^ rootPath, - Guid virtualizationInstanceGuid) -{ - PRJ_PLACEHOLDER_VERSION_INFO versionInfo; - memset(&versionInfo, 0, sizeof(PRJ_PLACEHOLDER_VERSION_INFO)); - - // We need our own ApiHelper because this is a static method. - ApiHelper^ apiHelper = gcnew ApiHelper(); - - array^ guidArray = virtualizationInstanceGuid.ToByteArray(); - pin_ptr guidData = &(guidArray[0]); - pin_ptr root = PtrToStringChars(rootPath); - if (apiHelper->UseBetaApi) - { - return static_cast(apiHelper->_PrjConvertDirectoryToPlaceholder(root, - L"", - &versionInfo, - PRJ_FLAG_VIRTUALIZATION_ROOT, - (GUID*) guidData)); - } - else - { - return static_cast(apiHelper->_PrjMarkDirectoryAsPlaceholder(root, - nullptr, - &versionInfo, - reinterpret_cast(guidData))); - } -} - -#pragma endregion - -#pragma region Private method implementations - -void VirtualizationInstance::ConfirmStarted() -{ - if (!m_virtualizationContext) - { - throw gcnew InvalidOperationException("Operation invalid before virtualization instance is started"); - } -} - -void VirtualizationInstance::ConfirmNotStarted() -{ - if (m_virtualizationContext) - { - throw gcnew InvalidOperationException("Operation invalid after virtualization instance is started"); - } -} - -void VirtualizationInstance::FindBytesPerSectorAndAlignment() -{ - WCHAR volumePath[MAX_PATH]; - pin_ptr rootPath = PtrToStringChars(m_virtualizationRootPath); - if (!GetVolumePathName(rootPath, - volumePath, - ARRAYSIZE(volumePath))) - { - DWORD lastError = ::GetLastError(); - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Failed to get volume path name, Error: {0}", - lastError)); - } - - WCHAR volumeName[VOLUME_PATH_LENGTH + 1]; - if (!GetVolumeNameForVolumeMountPoint(volumePath, - volumeName, - ARRAYSIZE(volumeName))) - { - DWORD lastError = ::GetLastError(); - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Failed to get volume name for volume mount point: {0}, Error: {1}", - gcnew String(volumeName), - lastError)); - } - - if (wcslen(volumeName) != VOLUME_PATH_LENGTH || volumeName[VOLUME_PATH_LENGTH - 1] != L'\\') - { - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Volume name {0} is not in expected format", - gcnew String(volumeName))); - } - - HANDLE rootHandle = CreateFile(volumeName, - 0, - 0, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); - if (rootHandle == INVALID_HANDLE_VALUE) - { - DWORD lastError = ::GetLastError(); - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Failed to get handle to {0}, Error: {1}", m_virtualizationRootPath, - lastError)); - } - - FILE_STORAGE_INFO storageInfo = {}; - if (!GetFileInformationByHandleEx(rootHandle, - FileStorageInfo, - &storageInfo, - sizeof(storageInfo))) - { - DWORD lastError = ::GetLastError(); - CloseHandle(rootHandle); - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Failed to query sector size of volume, Error: {0}", - lastError)); - } - - FILE_ALIGNMENT_INFO alignmentInfo = {}; - if (!GetFileInformationByHandleEx(rootHandle, - FileAlignmentInfo, - &alignmentInfo, - sizeof(alignmentInfo))) - { - DWORD lastError = ::GetLastError(); - CloseHandle(rootHandle); - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Failed to query device alignment, Error: {0}", - lastError)); - } - - m_bytesPerSector = storageInfo.LogicalBytesPerSector; - - // AlignmentRequirement returns the required alignment minus 1 - // https://msdn.microsoft.com/en-us/library/cc232065.aspx - // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/initializing-a-device-object - m_writeBufferAlignmentRequirement = alignmentInfo.AlignmentRequirement + 1; - - CloseHandle(rootHandle); - - if (!IsPowerOf2(m_writeBufferAlignmentRequirement)) - { - throw gcnew IOException(String::Format(CultureInfo::InvariantCulture, - "Failed to determine write buffer alignment requirement: {0} is not a power of 2", - m_writeBufferAlignmentRequirement)); - } -} - -#pragma endregion - -namespace { -#pragma region C callbacks - -_Function_class_(PRJ_START_DIRECTORY_ENUMERATION_CB) -HRESULT PrjStartDirectoryEnumerationCB(_In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LPCGUID enumerationId) -{ - - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - return static_cast(pVirtualizationInstanceObj->RequiredCallbacks->StartDirectoryEnumerationCallback( - callbackData->CommandId, - GUIDtoGuid(*enumerationId), - gcnew String(callbackData->FilePathName), - callbackData->TriggeringProcessId, - (callbackData->TriggeringProcessImageFileName != NULL) ? gcnew String(callbackData->TriggeringProcessImageFileName) : String::Empty)); - } - - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -_Function_class_(PRJ_END_DIRECTORY_ENUMERATION_CB) -HRESULT PrjEndDirectoryEnumerationCB(_In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LPCGUID enumerationId) -{ - - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - return static_cast(pVirtualizationInstanceObj->RequiredCallbacks->EndDirectoryEnumerationCallback( - GUIDtoGuid(*enumerationId))); - } - - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -_Function_class_(PRJ_GET_DIRECTORY_ENUMERATION_CB) -HRESULT PrjGetDirectoryEnumerationCB(_In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LPCGUID enumerationId, - _In_opt_z_ LPCWSTR searchExpression, - _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle) -{ - - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - IDirectoryEnumerationResults^ enumerationData = gcnew DirectoryEnumerationResults(dirEntryBufferHandle, pVirtualizationInstanceObj->ApiHelperObject); - return static_cast(pVirtualizationInstanceObj->RequiredCallbacks->GetDirectoryEnumerationCallback( - callbackData->CommandId, - GUIDtoGuid(*enumerationId), - (searchExpression != NULL) ? gcnew String(searchExpression) : nullptr, - (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN), - enumerationData)); - } - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -_Function_class_(PRJ_GET_PLACEHOLDER_INFO_CB) -HRESULT PrjGetPlaceholderInfoCB(_In_ const PRJ_CALLBACK_DATA* callbackData) -{ - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - return static_cast(pVirtualizationInstanceObj->RequiredCallbacks->GetPlaceholderInfoCallback( - callbackData->CommandId, - gcnew String(callbackData->FilePathName), - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData))); - } - - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -_Function_class_(PRJ_GET_FILE_DATA_CB) -HRESULT PrjGetFileDataCB(_In_ const PRJ_CALLBACK_DATA* callbackData, - _In_ UINT64 byteOffset, - _In_ UINT32 length) -{ - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - return static_cast(pVirtualizationInstanceObj->RequiredCallbacks->GetFileDataCallback( - callbackData->CommandId, - gcnew String(callbackData->FilePathName), - byteOffset, - length, - GUIDtoGuid(callbackData->DataStreamId), - (callbackData->VersionInfo != NULL) ? MarshalPlaceholderId(callbackData->VersionInfo->ContentID) : nullptr, - (callbackData->VersionInfo != NULL) ? MarshalPlaceholderId(callbackData->VersionInfo->ProviderID) : nullptr, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData))); - } - - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -_Function_class_(PRJ_QUERY_FILE_NAME_CB) -HRESULT PrjQueryFileNameCB(_In_ PRJ_CALLBACK_DATA* callbackData) -{ - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - if (pVirtualizationInstanceObj->OnQueryFileName != nullptr) - { - return static_cast(pVirtualizationInstanceObj->OnQueryFileName(gcnew String(callbackData->FilePathName))); - } - } - - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -_Function_class_(PRJ_CANCEL_COMMAND_CB) -void PrjCancelCommandCB(_In_ PRJ_CALLBACK_DATA* callbackData) -{ - if (callbackData->InstanceContext != NULL) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - if (pVirtualizationInstanceObj->OnCancelCommand != nullptr) - { - pVirtualizationInstanceObj->OnCancelCommand(callbackData->CommandId); - } - } -} - -_Function_class_(PRJ_NOTIFICATION_CB) -HRESULT PrjNotificationCB(_In_ const PRJ_CALLBACK_DATA* callbackData, - _In_ BOOLEAN isDirectory, - _In_ PRJ_NOTIFICATION notification, - _In_opt_ PCWSTR destinationFileName, - _Inout_ PRJ_NOTIFICATION_PARAMETERS* notificationParameters) -{ - if (callbackData->InstanceContext != nullptr) - { - gcroot& pVirtualizationInstanceObj = *((gcroot*)callbackData->InstanceContext); - - // Our pre-operation callback handlers return HResult, most of the post-operation callback - // handlers have void return type. Declare this for the ones that have a return code. - HResult notificationResult = HResult::Ok; - - switch (notification) - { - case PRJ_NOTIFICATION_FILE_OPENED: - if (pVirtualizationInstanceObj->OnNotifyFileOpened != nullptr) - { - NotificationType notificationMask; - - // The provider can deny the open by returning false. - if (!pVirtualizationInstanceObj->OnNotifyFileOpened(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData), - notificationMask)) - { - notificationResult = HResult::AccessDenied; - } - else - { - notificationParameters->PostCreate.NotificationMask = static_cast(notificationMask); - } - } - break; - - case PRJ_NOTIFICATION_NEW_FILE_CREATED: - if (pVirtualizationInstanceObj->OnNotifyNewFileCreated != nullptr) - { - NotificationType notificationMask; - pVirtualizationInstanceObj->OnNotifyNewFileCreated(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData), - notificationMask); - - notificationParameters->PostCreate.NotificationMask = static_cast(notificationMask); - } - break; - - case PRJ_NOTIFICATION_FILE_OVERWRITTEN: - if (pVirtualizationInstanceObj->OnNotifyFileOverwritten != nullptr) - { - NotificationType notificationMask; - pVirtualizationInstanceObj->OnNotifyFileOverwritten(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData), - notificationMask); - - notificationParameters->PostCreate.NotificationMask = static_cast(notificationMask); - } - break; - - case PRJ_NOTIFICATION_PRE_DELETE: - if (pVirtualizationInstanceObj->OnNotifyPreDelete != nullptr) - { - if (!pVirtualizationInstanceObj->OnNotifyPreDelete(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData))) - { - notificationResult = HResult::CannotDelete; - } - } - break; - - case PRJ_NOTIFICATION_PRE_RENAME: - if (pVirtualizationInstanceObj->OnNotifyPreRename != nullptr) - { - if (!pVirtualizationInstanceObj->OnNotifyPreRename(gcnew String(callbackData->FilePathName), - gcnew String(destinationFileName), - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData))) - { - notificationResult = HResult::AccessDenied; - } - } - break; - - case PRJ_NOTIFICATION_PRE_SET_HARDLINK: - if (pVirtualizationInstanceObj->OnNotifyPreCreateHardlink != nullptr) - { - if (!pVirtualizationInstanceObj->OnNotifyPreCreateHardlink(gcnew String(callbackData->FilePathName), - gcnew String(destinationFileName), - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData))) - { - notificationResult = HResult::AccessDenied; - } - } - break; - - case PRJ_NOTIFICATION_FILE_RENAMED: - if (pVirtualizationInstanceObj->OnNotifyFileRenamed != nullptr) - { - NotificationType notificationMask; - pVirtualizationInstanceObj->OnNotifyFileRenamed(gcnew String(callbackData->FilePathName), - gcnew String(destinationFileName), - isDirectory != FALSE, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData), - notificationMask); - - notificationParameters->FileRenamed.NotificationMask = static_cast(notificationMask); - } - break; - - case PRJ_NOTIFICATION_HARDLINK_CREATED: - if (pVirtualizationInstanceObj->OnNotifyHardlinkCreated != nullptr) - { - pVirtualizationInstanceObj->OnNotifyHardlinkCreated(gcnew String(callbackData->FilePathName), - gcnew String(destinationFileName), - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData)); - } - break; - - case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION: - if (pVirtualizationInstanceObj->OnNotifyFileHandleClosedNoModification != nullptr) - { - pVirtualizationInstanceObj->OnNotifyFileHandleClosedNoModification(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData)); - } - break; - - case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_MODIFIED: - if (pVirtualizationInstanceObj->OnNotifyFileHandleClosedFileModifiedOrDeleted != nullptr) - { - pVirtualizationInstanceObj->OnNotifyFileHandleClosedFileModifiedOrDeleted(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - true, // isFileModified - false, // isFileDeleted - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData)); - } - break; - - case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED: - if (pVirtualizationInstanceObj->OnNotifyFileHandleClosedFileModifiedOrDeleted != nullptr) - { - pVirtualizationInstanceObj->OnNotifyFileHandleClosedFileModifiedOrDeleted(gcnew String(callbackData->FilePathName), - isDirectory != FALSE, - notificationParameters->FileDeletedOnHandleClose.IsFileModified != FALSE, // isFileModified - true, // isFileDeleted - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData)); - } - break; - - case PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL: - if (pVirtualizationInstanceObj->OnNotifyFilePreConvertToFull != nullptr) - { - if (!pVirtualizationInstanceObj->OnNotifyFilePreConvertToFull(gcnew String(callbackData->FilePathName), - callbackData->TriggeringProcessId, - GetTriggeringProcessNameSafe(callbackData))) - { - notificationResult = HResult::AccessDenied; - } - } - break; - - default: - // Unexpected notification type - break; - } - - return static_cast(notificationResult); - } - - return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR); -} - -#pragma region Windows 10 1803 support - -HRESULT PrjGetPlaceholderInformationCB(_In_ PRJ_CALLBACK_DATA* callbackData, - _In_ DWORD desiredAccess, - _In_ DWORD shareMode, - _In_ DWORD createDisposition, - _In_ DWORD createOptions, - _In_ LPCWSTR destinationFileName) -{ - UNREFERENCED_PARAMETER(desiredAccess); - UNREFERENCED_PARAMETER(shareMode); - UNREFERENCED_PARAMETER(createDisposition); - UNREFERENCED_PARAMETER(createOptions); - UNREFERENCED_PARAMETER(destinationFileName); - - return PrjGetPlaceholderInfoCB(callbackData); -} - -HRESULT PrjGetFileStreamCB(_In_ PRJ_CALLBACK_DATA* callbackData, - _In_ LARGE_INTEGER byteOffset, - _In_ DWORD length) -{ - return PrjGetFileDataCB(callbackData, byteOffset.QuadPart, length); -} - -HRESULT PrjNotifyOperationCB(_In_ PRJ_CALLBACK_DATA* callbackData, - _In_ BOOLEAN isDirectory, - _In_ PRJ_NOTIFICATION_TYPE notificationType, - _In_opt_ LPCWSTR destinationFileName, - _Inout_ PRJ_OPERATION_PARAMETERS* operationParameters) -{ - HRESULT hr; - PRJ_NOTIFICATION_PARAMETERS notificationParameters = {}; - - // Transfer input parameters to 1803-style parameter structure. - switch (notificationType) - { - case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED: - notificationParameters.FileDeletedOnHandleClose.IsFileModified = operationParameters->FileDeletedOnHandleClose.IsFileModified; - break; - } - - hr = PrjNotificationCB(callbackData, isDirectory, notificationType, destinationFileName, ¬ificationParameters); - - // Transfer output parameters from 1803-style parameter structure. - switch (notificationType) - { - case PRJ_NOTIFICATION_FILE_OPENED: - case PRJ_NOTIFICATION_NEW_FILE_CREATED: - case PRJ_NOTIFICATION_FILE_OVERWRITTEN: - - operationParameters->PostCreate.NotificationMask = notificationParameters.PostCreate.NotificationMask; - break; - - case PRJ_NOTIFICATION_FILE_RENAMED: - - operationParameters->FileRenamed.NotificationMask = notificationParameters.FileRenamed.NotificationMask; - break; - } - - return hr; -} - -#pragma endregion - -#pragma endregion - -#pragma region Helper methods - -inline array^ MarshalPlaceholderId(UCHAR* sourceId) -{ - array^ marshalledId = gcnew array(PRJ_PLACEHOLDER_ID_LENGTH); - pin_ptr pinnedId = &marshalledId[0]; - memcpy(pinnedId, sourceId, PRJ_PLACEHOLDER_ID_LENGTH); - return marshalledId; -} - -inline void CopyPlaceholderId(UCHAR* destinationId, array^ sourceId) -{ - if (sourceId != nullptr && sourceId->Length > 0) - { - pin_ptr pinnedId = &sourceId[0]; - memcpy( - destinationId, - pinnedId, - min(sourceId->Length * sizeof(byte), PRJ_PLACEHOLDER_ID_LENGTH)); - } -} - -inline bool IsPowerOf2(unsigned long num) -{ - return (num & (num - 1)) == 0; -} - -inline Guid GUIDtoGuid(const GUID& guid) -{ - return Guid( - guid.Data1, - guid.Data2, - guid.Data3, - guid.Data4[0], - guid.Data4[1], - guid.Data4[2], - guid.Data4[3], - guid.Data4[4], - guid.Data4[5], - guid.Data4[6], - guid.Data4[7]); -} - -inline std::shared_ptr CreatePlaceholderInformation( - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - bool directory, - array^ contentId, - array^ providerId) -{ - std::shared_ptr fileInformation(static_cast(malloc(sizeof(PRJ_PLACEHOLDER_INFORMATION))), free); - fileInformation->Size = sizeof(PRJ_PLACEHOLDER_INFORMATION); - - memset(&fileInformation->FileBasicInfo, 0, sizeof(PRJ_FILE_BASIC_INFO)); - fileInformation->FileBasicInfo.FileSize = endOfFile; - fileInformation->FileBasicInfo.IsDirectory = directory; - fileInformation->FileBasicInfo.CreationTime.QuadPart = creationTime.ToFileTime(); - fileInformation->FileBasicInfo.LastAccessTime.QuadPart = lastAccessTime.ToFileTime(); - fileInformation->FileBasicInfo.LastWriteTime.QuadPart = lastWriteTime.ToFileTime(); - fileInformation->FileBasicInfo.ChangeTime.QuadPart = changeTime.ToFileTime(); - fileInformation->FileBasicInfo.FileAttributes = static_cast(fileAttributes); - - fileInformation->EaInformation.EaBufferSize = 0; - fileInformation->EaInformation.OffsetToFirstEa = static_cast(-1); - - fileInformation->SecurityInformation.SecurityBufferSize = 0; - fileInformation->SecurityInformation.OffsetToSecurityDescriptor = static_cast(-1); - - fileInformation->StreamsInformation.StreamsInfoBufferSize = 0; - fileInformation->StreamsInformation.OffsetToFirstStreamInfo = static_cast(-1); - - memset(&fileInformation->VersionInfo, 0, sizeof(PRJ_PLACEHOLDER_VERSION_INFO)); - CopyPlaceholderId(fileInformation->VersionInfo.ProviderID, providerId); - CopyPlaceholderId(fileInformation->VersionInfo.ContentID, contentId); - - return fileInformation; -} - -inline std::shared_ptr CreatePlaceholderInfo( - DateTime creationTime, - DateTime lastAccessTime, - DateTime lastWriteTime, - DateTime changeTime, - FileAttributes fileAttributes, - long long endOfFile, - bool directory, - array^ contentId, - array^ providerId) -{ - std::shared_ptr placeholderInfo(static_cast(calloc(1, - sizeof(PRJ_PLACEHOLDER_INFO))), - free); - - placeholderInfo->FileBasicInfo.IsDirectory = directory; - placeholderInfo->FileBasicInfo.FileSize = endOfFile; - placeholderInfo->FileBasicInfo.CreationTime.QuadPart = creationTime.ToFileTime(); - placeholderInfo->FileBasicInfo.LastAccessTime.QuadPart = lastAccessTime.ToFileTime(); - placeholderInfo->FileBasicInfo.LastWriteTime.QuadPart = lastWriteTime.ToFileTime(); - placeholderInfo->FileBasicInfo.ChangeTime.QuadPart = changeTime.ToFileTime(); - placeholderInfo->FileBasicInfo.FileAttributes = static_cast(fileAttributes); - - CopyPlaceholderId(placeholderInfo->VersionInfo.ProviderID, providerId); - CopyPlaceholderId(placeholderInfo->VersionInfo.ContentID, contentId); - - return placeholderInfo; -} - -inline String^ GetTriggeringProcessNameSafe(const PRJ_CALLBACK_DATA* callbackData) -{ - return (callbackData->TriggeringProcessImageFileName != NULL) - ? gcnew String(callbackData->TriggeringProcessImageFileName) - : String::Empty; -} -#pragma endregion -} \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/VirtualizationInstance.h b/ProjectedFSLib.Managed.API/VirtualizationInstance.h deleted file mode 100644 index 4ab079e..0000000 --- a/ProjectedFSLib.Managed.API/VirtualizationInstance.h +++ /dev/null @@ -1,1273 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "IVirtualizationInstance.h" -#include "ApiHelper.h" - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Provides methods and callbacks that allow a provider to interact with a virtualization instance. - /// - /// - /// - /// The provider creates one instance of this class for each virtualization root that it manages. - /// The provider uses this class's properties and methods to receive and respond to callbacks from - /// ProjFS for its virtualization instance, and to send commands that control the virtualization - /// instance's state. - /// - /// - public ref class VirtualizationInstance : public IVirtualizationInstance - { - public: - - /// - /// Initializes an object that manages communication between a provider and ProjFS. - /// - /// - /// - /// If doesn't already exist, this constructor - /// will create it and mark it as the virtualization root. The constructor will generate - /// a GUID to serve as the virtualization instance ID. - /// - /// - /// If does exist, this constructor will check - /// for a ProjFS reparse point. If the reparse point does not exist, virtualizationRootPath - /// will be marked as the virtualization root. If it has a different reparse point then - /// the constructor will throw a for - /// ERROR_REPARSE_TAG_MISMATCH. - /// - /// - /// For providers that create their virtualization root separately from instantiating the - /// VirtualizationInstance class, the static method - /// is provided. - /// - /// - /// - /// The full path to the virtualization root directory. If this directory does not already - /// exist, it will be created. See the Remarks section for further details. - /// - /// - /// - /// The number of threads the provider will have available to process callbacks from ProjFS. - /// - /// - /// If the provider specifies 0, ProjFS will use a default value of 2 * . - /// - /// - /// - /// - /// The maximum number of threads the provider wants to run concurrently to process callbacks - /// from ProjFS. - /// - /// - /// If the provider specifies 0, ProjFS will use a default value equal to the number of - /// CPU cores in the system. - /// - /// - /// - /// - /// If true, specifies that the virtualization instance should maintain a "negative - /// path cache". If the negative path cache is active, then if the provider indicates - /// that a file path does not exist by returning from its - /// implementation of , then ProjFS will - /// fail subsequent opens of that path without calling - /// again. - /// - /// - /// To resume receiving for paths the provider has - /// indicated do not exist, the provider must call . - /// - /// - /// - /// - /// A collection of zero or more objects that describe the - /// notifications the provider wishes to receive for the virtualization root. - /// - /// - /// If the collection is empty, ProjFS will send the notifications , - /// , and - /// for all files and directories under the virtualization root. - /// - /// - /// - /// The native ProjFS library (ProjectedFSLib.dll) is not available. - /// - /// - /// An expected entry point cannot be found in ProjectedFSLib.dll. - /// - /// - /// An error occurred in setting up the virtualization root. - /// - VirtualizationInstance( - System::String^ virtualizationRootPath, - unsigned int poolThreadCount, - unsigned int concurrentThreadCount, - bool enableNegativePathCache, - System::Collections::Generic::IReadOnlyCollection^ notificationMappings - ); - -#pragma region Callback properties - - /// - /// Stores the provider's implementation of . - /// - /// - /// The provider must set this property prior to calling . - virtual property QueryFileNameCallback^ OnQueryFileName - { - QueryFileNameCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(QueryFileNameCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// - /// If the provider wishes to support asynchronous processing of callbacks (that is, if it - /// intends to return from any of its callbacks), then the provider - /// must set this property prior to calling . - /// - /// - /// If the provider does not wish to support asynchronous processing of callbacks, then it - /// is not required to provide an implementation of this callback. - /// - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property CancelCommandCallback^ OnCancelCommand - { - CancelCommandCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(CancelCommandCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file has been opened. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyFileOpenedCallback^ OnNotifyFileOpened - { - NotifyFileOpenedCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyFileOpenedCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a new file has been created. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyNewFileCreatedCallback^ OnNotifyNewFileCreated - { - NotifyNewFileCreatedCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyNewFileCreatedCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file has been superseded or overwritten. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyFileOverwrittenCallback^ OnNotifyFileOverwritten - { - NotifyFileOverwrittenCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyFileOverwrittenCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file is about to be deleted. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyPreDeleteCallback^ OnNotifyPreDelete - { - NotifyPreDeleteCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyPreDeleteCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file is about to be renamed. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyPreRenameCallback^ OnNotifyPreRename - { - NotifyPreRenameCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyPreRenameCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a hard link is about to be created for a file. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyPreCreateHardlinkCallback^ OnNotifyPreCreateHardlink - { - NotifyPreCreateHardlinkCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyPreCreateHardlinkCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file has been renamed. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyFileRenamedCallback^ OnNotifyFileRenamed - { - NotifyFileRenamedCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyFileRenamedCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a hard link has been created for a file. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyHardlinkCreatedCallback^ OnNotifyHardlinkCreated - { - NotifyHardlinkCreatedCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyHardlinkCreatedCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file handle has been closed and the file has not been modified. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyFileHandleClosedNoModificationCallback^ OnNotifyFileHandleClosedNoModification - { - NotifyFileHandleClosedNoModificationCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyFileHandleClosedNoModificationCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file handle has been closed on a modified file, or the file was deleted as a result - /// of closing the handle. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyFileHandleClosedFileModifiedOrDeletedCallback^ OnNotifyFileHandleClosedFileModifiedOrDeleted - { - NotifyFileHandleClosedFileModifiedOrDeletedCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyFileHandleClosedFileModifiedOrDeletedCallback^ callbackDelegate) sealed; - }; - - /// Stores the provider's implementation of . - /// - /// - /// The provider is not required to provide an implementation of this callback. - /// If it does not provide this callback, the provider will not receive notifications when - /// a file is about to be converted from a placeholder to a full file. - /// If the provider does implement this callback, then it must set this property prior to - /// calling . - /// - virtual property NotifyFilePreConvertToFullCallback^ OnNotifyFilePreConvertToFull - { - NotifyFilePreConvertToFullCallback^ get(void) sealed; - - /// - /// The provider has already called . - /// - void set(NotifyFilePreConvertToFullCallback^ callbackDelegate) sealed; - }; - - /// Retrieves the interface. - virtual property IRequiredCallbacks^ RequiredCallbacks - { - IRequiredCallbacks^ get(void) sealed; - }; - -#pragma endregion - -#pragma region Other properties - - /// Allows the provider to retrieve the virtualization instance GUID. - /// - /// A virtualization instance is identified with a GUID. If the provider did not generate - /// and store a GUID itself using the method, - /// then the VirtualizationInstance class generates one for it. Either way, the provider - /// can retrieve the GUID via this property. - /// - virtual property System::Guid VirtualizationInstanceId - { - System::Guid get(void) sealed; - }; - - /// Returns the maximum allowed length of a placeholder's contentID or provider ID. - /// - /// See or for more information. - /// - virtual property int PlaceholderIdLength - { - int get(void) sealed; - }; - -#pragma endregion - -#pragma region Public methods - - /// - /// Starts the virtualization instance, making it available to service I/O and invoke callbacks - /// on the provider. - /// - /// - /// - /// If the provider has implemented any optional callback delegates, it must set their - /// implementations into the On... properties prior to calling this method. - /// - /// - /// On Windows 10 version 1803 this method attempts to determine the sector alignment - /// requirements of the underlying storage device and stores that information internally - /// in the instance. This information is required - /// by the method to ensure that it can return data in - /// the method when the original reader is using unbuffered - /// I/O. If this method cannot determine the sector alignment requirements of the - /// underlying storage device, it will throw a - /// exception. - /// - /// - /// On Windows 10 version 1809 and later versions the alignment requirements are determined - /// by the system. - /// - /// - /// - /// - /// The provider's implementation of the interface. - /// - /// - /// - /// if the virtualization instance started successfully. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if the virtualization root is an ancestor or descendant of an existing virtualization root. - /// if the virtualization instance is already running. - /// - /// - /// The sector alignment requirements of the volume could not be determined. See the Remarks section. - /// - virtual HResult StartVirtualizing( - IRequiredCallbacks^ requiredCallbacks) sealed; - - /// - /// Stops the virtualization instance, making it unavailable to service I/O or invoke callbacks - /// on the provider. - /// - /// - /// The virtualization instance is in an invalid state (it may already be stopped). - /// - virtual void StopVirtualizing() sealed; - - /// - /// Purges the virtualization instance's negative path cache, if it is active. - /// - /// - /// - /// If the negative path cache is active, then if the provider indicates that a file path does - /// not exist by returning from its implementation of - /// - /// then ProjFS will fail subsequent opens of that path without calling - /// again. - /// - /// - /// To resume receiving for - /// paths the provider has indicated do not exist, the provider must call this method. - /// - /// - /// - /// Returns the number of paths that were in the cache before it was purged. - /// - /// - /// if the the cache was successfully purged. - /// if a buffer could not be allocated to communicate with ProjFS. - /// - virtual HResult ClearNegativePathCache( - [Out] unsigned int% totalEntryNumber) sealed; - - /// - /// Sends file contents to ProjFS. - /// - /// - /// - /// The provider uses this method to provide the data requested when ProjFS calls the provider's - /// implementation of . - /// - /// - /// The provider calls to create an instance of - /// to contain the data to be written. The ensures that any alignment - /// requirements of the underlying storage device are met. - /// - /// - /// - /// Identifier for the data stream to write to. The provider must use the value of - /// passed in its - /// callback. - /// - /// - /// A created using that contains - /// the data to write. - /// - /// - /// Byte offset from the beginning of the file at which to write the data. - /// - /// - /// The number of bytes to write to the file. - /// - /// - /// if the data was successfully written. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is not specified, - /// is 0, or is greater than the - /// length of the file. - /// if does not - /// correspond to a placeholder that is expecting data to be provided. - /// - /// - virtual HResult WriteFileData( - System::Guid dataStreamId, - IWriteBuffer^ buffer, - unsigned long long byteOffset, - unsigned long length) sealed; - - /// - /// Enables a provider to delete a file or directory that has been cached on the local file system. - /// - /// - /// - /// If the item is still in the provider's store, deleting it from the local file system changes - /// it to a virtual item. - /// - /// - /// This routine will fail if called on a file or directory that is already virtual. - /// - /// - /// If the file or directory to be deleted is in any state other than "placeholder", the - /// provider must specify an appropriate combination of values - /// in the parameter. This helps guard against accidental - /// loss of data. If the provider did not specify a combination of - /// values in the parameter that would allow the delete - /// to happen, the method fails with . - /// - /// - /// If a directory contains only tombstones, it may be deleted using this method and - /// specifying in . - /// If the directory contains non-tombstone files, then this method will return . - /// - /// - /// - /// The path, relative to the virtualization root, to the file or directory to delete. - /// - /// - /// - /// A combination of 0 or more values to control whether ProjFS - /// should allow the delete given the state of the file or directory on disk. See the documentation - /// of for a description of each flag and what it will allow. - /// - /// - /// If the item is a dirty placeholder, full file, or tombstone, and the provider does not - /// specify the appropriate flag(s), this routine will fail to delete the placeholder. - /// - /// - /// - /// If this method fails with , this receives a - /// value that describes the reason the delete failed. - /// - /// - /// if the delete succeeded. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// if cannot - /// be found. It may be for a virtual file or directory. - /// if contains - /// an intermediate component that cannot be found. The path may terminate beneath a - /// directory that has been replaced with a tombstone. - /// if is a - /// directory and is not empty. - /// if the input value of - /// does not allow the delete given the state of the file or directory on disk. The value - /// of indicates the cause of the failure. - /// - virtual HResult DeleteFile( - System::String^ relativePath, - UpdateType updateFlags, - [Out] UpdateFailureCause% failureReason) sealed; - - /// - /// Sends file or directory metadata to ProjFS. - /// - /// - /// - /// The provider uses this method to create a placeholder on disk. It does this when ProjFS - /// calls the provider's implementation of , - /// or the provider may use this method to proactively lay down a placeholder. - /// - /// - /// Note that the timestamps the provider specifies in the , - /// , , and - /// parameters may be any values the provider wishes. This allows the provider to preserve - /// the illusion of files and directories that already exist on the user's system even before they - /// are physically created on the user's disk. - /// - /// - /// - /// - /// The path, relative to the virtualization root, of the file or directory. - /// - /// - /// If the provider is processing a call to , - /// then this must be a match to the relativePath value passed in that call. The - /// provider should use the method to determine whether - /// the two names match. - /// - /// - /// For example, if specifies - /// dir1\dir1\FILE.TXT in relativePath, and the provider�s backing store contains - /// a file called File.txt in the dir1\dir2 directory, and - /// returns 0 when comparing the names FILE.TXT and File.txt, then the provider - /// specifies dir1\dir2\File.txt as the value of this parameter. - /// - /// - /// - /// The time the file was created. - /// - /// - /// The time the file was last accessed. - /// - /// - /// The time the file was last written to. - /// - /// - /// The time the file was last changed. - /// - /// - /// The file attributes. - /// - /// - /// The size of the file in bytes. - /// - /// - /// true if is for a directory, false otherwise. - /// - /// - /// - /// A content identifier, generated by the provider. ProjFS will pass this value back to the - /// provider when requesting file contents in the callback. - /// This allows the provider to distinguish between different versions of the same file, i.e. - /// different file contents and/or metadata for the same file path. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// - /// Optional provider-specific data. ProjFS will pass this value back to the provider - /// when requesting file contents in the callback. The - /// provider may use this value as its own unique identifier, for example as a version number - /// for the format of the value. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// if the placeholder information was successfully written. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// - virtual HResult WritePlaceholderInfo( - System::String^ relativePath, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::IO::FileAttributes fileAttributes, - long long endOfFile, - bool isDirectory, - array^ contentId, - array^ providerId) sealed; - - /// - /// Sends file or directory metadata to ProjFS. - /// - /// - /// - /// The provider uses this method to create a placeholder on disk. It does this when ProjFS - /// calls the provider's implementation of , - /// or the provider may use this method to proactively lay down a placeholder. - /// - /// - /// Note that the timestamps the provider specifies in the , - /// , , and - /// parameters may be any values the provider wishes. This allows the provider to preserve - /// the illusion of files and directories that already exist on the user's system even before they - /// are physically created on the user's disk. - /// - /// - /// - /// - /// The path, relative to the virtualization root, of the file or directory. - /// - /// - /// If the provider is processing a call to , - /// then this must be a match to the relativePath value passed in that call. The - /// provider should use the method to determine whether - /// the two names match. - /// - /// - /// For example, if specifies - /// dir1\dir1\FILE.TXT in relativePath, and the provider�s backing store contains - /// a file called File.txt in the dir1\dir2 directory, and - /// returns 0 when comparing the names FILE.TXT and File.txt, then the provider - /// specifies dir1\dir2\File.txt as the value of this parameter. - /// - /// - /// - /// The time the file was created. - /// - /// - /// The time the file was last accessed. - /// - /// - /// The time the file was last written to. - /// - /// - /// The time the file was last changed. - /// - /// - /// The file attributes. - /// - /// - /// The size of the file in bytes. - /// - /// - /// true if is for a directory, false otherwise. - /// - /// - /// - /// Symlink target for extended info. If its value is null, then this method will have the same behavior as . - /// - /// - /// - /// - /// A content identifier, generated by the provider. ProjFS will pass this value back to the - /// provider when requesting file contents in the callback. - /// This allows the provider to distinguish between different versions of the same file, i.e. - /// different file contents and/or metadata for the same file path. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// - /// Optional provider-specific data. ProjFS will pass this value back to the provider - /// when requesting file contents in the callback. The - /// provider may use this value as its own unique identifier, for example as a version number - /// for the format of the value. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// if the placeholder information was successfully written. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// - virtual HResult WritePlaceholderInfo2( - System::String^ relativePath, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::IO::FileAttributes fileAttributes, - long long endOfFile, - bool isDirectory, - System::String^ symlinkTargetOrNull, - array^ contentId, - array^ providerId) sealed; - - /// - /// Updates an item that has been cached on the local file system. - /// - /// - /// - /// This routine cannot be called on a virtual file or directory. - /// - /// - /// If the file or directory to be updated is in any state other than "placeholder", the provider - /// must specify an appropriate combination of values in the - /// parameter. This helps guard against accidental loss of - /// data, since upon successful return from this routine the item becomes a placeholder with - /// the updated metadata. Any metadata that had been changed since the placeholder was created, - /// or any file data it contained is discarded. - /// - /// - /// Note that the timestamps the provider specifies in the , - /// , , and - /// parameters may be any values the provider wishes. This allows the provider to preserve - /// the illusion of files and directories that already exist on the user's system even before they - /// are physically created on the user's disk. - /// - /// - /// - /// The path, relative to the virtualization root, to the file or directory to updated. - /// - /// - /// The time the file was created. - /// - /// - /// The time the file was last accessed. - /// - /// - /// The time the file was last written to. - /// - /// - /// The time the file was last changed. - /// - /// - /// The file attributes. - /// - /// - /// The size of the file in bytes. - /// - /// - /// - /// A content identifier, generated by the provider. ProjFS will pass this value back to the - /// provider when requesting file contents in the callback. - /// This allows the provider to distinguish between different versions of the same file, i.e. - /// different file contents and/or metadata for the same file path. - /// - /// - /// If this parameter specifies a content identifier that is the same as the content identifier - /// already on the file or directory, the call succeeds and no update takes place. Otherwise, - /// if the call succeeds then the value of this parameter replaces the existing content identifier - /// on the file or directory. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// - /// Optional provider-specific data. ProjFS will pass this value back to the provider - /// when requesting file contents in the callback. The - /// provider may use this value as its own unique identifier, for example as a version number - /// for the format of the value. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// - /// A combination of 0 or more values to control whether ProjFS - /// should allow the update given the state of the file or directory on disk. See the documentation - /// of for a description of each flag and what it will allow. - /// - /// - /// If the item is a dirty placeholder, full file, or tombstone, and the provider does not - /// specify the appropriate flag(s), this routine will fail to update the item. - /// - /// - /// - /// If this method fails with , this receives a - /// value that describes the reason the update failed. - /// - /// - /// if the update succeeded. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string. - /// if cannot - /// be found. It may be for a virtual file or directory. - /// if contains - /// an intermediate component that cannot be found. The path may terminate beneath a - /// directory that has been replaced with a tombstone. - /// if is a - /// directory and is not empty. - /// if the input value of - /// does not allow the update given the state of the file or directory on disk. The value - /// of indicates the cause of the failure. - /// - virtual HResult UpdateFileIfNeeded( - System::String^ relativePath, - System::DateTime creationTime, - System::DateTime lastAccessTime, - System::DateTime lastWriteTime, - System::DateTime changeTime, - System::IO::FileAttributes fileAttributes, - long long endOfFile, - array^ contentId, - array^ providerId, - UpdateType updateFlags, - [Out] UpdateFailureCause% failureReason) sealed; - - /// - /// Signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback. - /// - virtual HResult CompleteCommand( - int commandId) sealed; - - /// - /// Signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// The final status of the operation. See the descriptions for the callback delegates for - /// appropriate return codes. - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback. - /// - virtual HResult CompleteCommand( - int commandId, - HResult completionResult) sealed; - - /// - /// Signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// Used when completing . Receives - /// the results of the enumeration. - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback or - /// if is not a valid . - /// - virtual HResult CompleteCommand( - int commandId, - IDirectoryEnumerationResults^ results) sealed; - - /// - /// Signals to ProjFS that the provider has completed processing a callback from which it - /// previously returned . - /// - /// - /// If the provider calls this method for the passed by the - /// callback it is not an error, however it is a no-op - /// because the I/O that caused the callback invocation identified by - /// has already ended. - /// - /// - /// A value that uniquely identifies the callback invocation to complete. This value must be - /// equal to the value of the parameter of the callback from - /// which the provider previously returned . - /// - /// - /// - /// Used when completing Microsoft.Windows.ProjFS.Notify* callbacks that have a - /// notificationMask parameter. Specifies a bitwise-OR of - /// values indicating the set of notifications the provider wishes to receive for this file. - /// - /// - /// If the provider sets this value to 0, it is equivalent to specifying - /// . - /// - /// - /// - /// if completion succeeded. - /// if does not specify a pended callback - /// or if is not a valid combination of - /// values. - /// - virtual HResult CompleteCommand( - int commandId, - NotificationType newNotificationMask) sealed; - - /// - /// Creates a for use with . - /// - /// - /// - /// The object ensures that any alignment requirements of the - /// underlying storage device are met when writing data with the - /// method. - /// - /// - /// Note that unlike most methods on , this method - /// throws rather than return . This makes it convenient to use - /// in constructions like a using clause. - /// - /// - /// - /// The size in bytes of the buffer required to write the data. - /// - /// - /// A that provides at least - /// bytes of capacity. - /// - /// - /// A buffer could not be allocated. - /// - virtual IWriteBuffer^ CreateWriteBuffer( - unsigned int desiredBufferSize) sealed; - - /// - /// Creates a for use with . - /// - /// - /// - /// The object ensures that any alignment requirements of the - /// underlying storage device are met when writing data with the - /// method. - /// - /// - /// This overload allows a provider to get sector-aligned values for the start offset and - /// length of the write. The provider uses and - /// to copy the correct data out of its backing store - /// into the and transfer it when calling . - /// - /// - /// Note that unlike most methods on , this method - /// throws rather than return . This makes it convenient to use - /// in constructions like a using clause. - /// - /// - /// - /// Byte offset from the beginning of the file at which the provider wants to write data. - /// - /// - /// The number of bytes to write to the file. - /// - /// - /// , aligned to the sector size of the storage device. The - /// provider uses this value as the byteOffset parameter to . - /// - /// - /// , aligned to the sector size of the storage device. The - /// provider uses this value as the length parameter to . - /// - /// - /// A that provides the needed capacity. - /// - /// - /// An error occurred retrieving the sector size from ProjFS. - /// - /// - /// A buffer could not be allocated. - /// - virtual IWriteBuffer^ CreateWriteBuffer( - unsigned long long byteOffset, - unsigned int length, - [Out] unsigned long long% alignedByteOffset, - [Out] unsigned int% alignedLength) sealed; - - /// - /// Converts an existing directory to a hydrated directory placeholder. - /// - /// - /// Children of the directory are not affected. - /// - /// - /// The full path (i.e. not relative to the virtualization root) to the directory to convert - /// to a placeholder. - /// - /// - /// - /// A content identifier, generated by the provider. This value is used to distinguish between - /// different versions of the same file, for example different file contents and/or metadata - /// (e.g. timestamps) for the same file path. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// - /// Optional provider-specific data. The provider may use this value as its own unique identifier, - /// for example as a version number for the format of the value. - /// - /// - /// This value must be at most bytes in size. Any data - /// beyond that length will be discarded. - /// - /// - /// - /// if the conversion succeeded. - /// if a buffer could not be allocated to communicate with ProjFS. - /// if is an empty string - /// or if it is not a directory path. - /// if - /// is already a placeholder or some other kind of reparse point. - /// - virtual HResult MarkDirectoryAsPlaceholder( - System::String^ targetDirectoryPath, - array^ contentId, - array^ providerId) sealed; - - /// - /// Marks an existing directory as the provider's virtualization root. - /// - /// - /// A provider may wish to designate its virtualization root before it is ready or able to - /// instantiate the class. In that case it may use this - /// method to designate the root. The provider must generate a GUID to identify the virtualization - /// instance and pass it in . The - /// constructor will use that - /// value to identify the provider to ProjFS. - /// - /// - /// The full path to the directory to mark as the virtualization root. - /// - /// - /// A GUID generated by the provider. ProjFS uses this value to internally identify the provider. - /// - /// - /// if the conversion succeeded. - /// if is an empty string. - /// if does not specify a directory. - /// if is already a placeholder or some other kind of reparse point. - /// if is an ancestor or descendant of an existing virtualization root. - /// - static HResult MarkDirectoryAsVirtualizationRoot( - System::String^ rootPath, - System::Guid virtualizationInstanceGuid); - -#pragma endregion - - internal: - - virtual property ApiHelper^ ApiHelperObject - { - ApiHelper^ get(void) sealed; - }; - - private: - -#pragma region Private methods - - void ConfirmStarted(); - void ConfirmNotStarted(); - void FindBytesPerSectorAndAlignment(); - -#pragma endregion - -#pragma region Callback property storage - - IRequiredCallbacks^ m_requiredCallbacks; - - QueryFileNameCallback^ m_queryFileNameCallback; - CancelCommandCallback^ m_cancelCommandCallback; - - NotifyFileOpenedCallback^ m_notifyFileOpenedCallback; - NotifyNewFileCreatedCallback^ m_notifyNewFileCreatedCallback; - NotifyFileOverwrittenCallback^ m_notifyFileOverwrittenCallback; - NotifyPreDeleteCallback^ m_notifyPreDeleteCallback; - NotifyPreRenameCallback^ m_notifyPreRenameCallback; - NotifyPreCreateHardlinkCallback^ m_notifyPreCreateHardlinkCallback; - NotifyFileRenamedCallback^ m_notifyFileRenamedCallback; - NotifyHardlinkCreatedCallback^ m_notifyHardlinkCreatedCallback; - NotifyFileHandleClosedNoModificationCallback^ m_notifyFileHandleClosedNoModificationCallback; - NotifyFileHandleClosedFileModifiedOrDeletedCallback^ m_notifyFileHandleClosedFileModifiedOrDeletedCallback; - NotifyFilePreConvertToFullCallback^ m_notifyFilePreConvertToFullCallback; - -#pragma endregion - -#pragma region Member data variables - - // Values provided by the constructor. - System::String^ m_virtualizationRootPath; - unsigned int m_poolThreadCount; - unsigned int m_concurrentThreadCount; - bool m_enableNegativePathCache; - System::Collections::Generic::IReadOnlyCollection^ m_notificationMappings; - - // We keep a GC handle to the VirtualizationInstance object to pass through ProjFS as the - // instance context. - gcroot* m_virtualizationContextGc = nullptr; - - // Variables to support aligned I/O in Windows 10 version 1803. - unsigned long m_bytesPerSector; - unsigned long m_writeBufferAlignmentRequirement; - - PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT m_virtualizationContext; - System::Guid m_virtualizationInstanceID; - ApiHelper^ m_apiHelper; - -#pragma endregion - }; - -#pragma region Support structures - - // Support for smart HANDLE - struct HFileDeleter { - typedef HANDLE pointer; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) - { - ::CloseHandle(h); - } - } - }; - - // #include'ing the header that defines this is tricky, so we just have the definition here. - typedef struct _REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - - _Field_size_bytes_(ReparseDataLength) - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - } SymbolicLinkReparseBuffer; - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPointReparseBuffer; - struct { - UCHAR DataBuffer[1]; - } GenericReparseBuffer; - } DUMMYUNIONNAME; - } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; - -#pragma endregion -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/WriteBuffer.cpp b/ProjectedFSLib.Managed.API/WriteBuffer.cpp deleted file mode 100644 index 762b793..0000000 --- a/ProjectedFSLib.Managed.API/WriteBuffer.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "stdafx.h" -#include "WriteBuffer.h" - -using namespace System; -using namespace System::IO; -using namespace Microsoft::Windows::ProjFS; - -WriteBuffer::WriteBuffer( - unsigned long bufferSize, - unsigned long alignment) -{ - this->buffer = (unsigned char*) _aligned_malloc(bufferSize, alignment); - if (this->buffer == nullptr) - { - throw gcnew OutOfMemoryException("Unable to allocate WriteBuffer"); - } - - this->namespaceCtx = nullptr; - this->stream = gcnew UnmanagedMemoryStream(buffer, bufferSize, bufferSize, FileAccess::Write); -} - -WriteBuffer::WriteBuffer( - unsigned long bufferSize, - PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceCtx, - ApiHelper^ apiHelper) -{ - this->buffer = (unsigned char*) apiHelper->_PrjAllocateAlignedBuffer(namespaceCtx, bufferSize); - if (this->buffer == nullptr) - { - throw gcnew OutOfMemoryException("Unable to allocate WriteBuffer"); - } - - this->namespaceCtx = namespaceCtx; - this->apiHelper = apiHelper; - this->stream = gcnew UnmanagedMemoryStream(buffer, bufferSize, bufferSize, FileAccess::Write); -} - -WriteBuffer::~WriteBuffer() -{ - delete this->stream; - this->!WriteBuffer(); -} - -WriteBuffer::!WriteBuffer() -{ - if (this->namespaceCtx) - { - this->apiHelper->_PrjFreeAlignedBuffer(this->buffer); - } - else - { - _aligned_free(this->buffer); - } -} - -long long WriteBuffer::Length::get(void) -{ - return this->stream->Length; -} - -UnmanagedMemoryStream^ WriteBuffer::Stream::get(void) -{ - return this->stream; -} - -IntPtr WriteBuffer::Pointer::get(void) -{ - return IntPtr(this->buffer); -} diff --git a/ProjectedFSLib.Managed.API/WriteBuffer.h b/ProjectedFSLib.Managed.API/WriteBuffer.h deleted file mode 100644 index ff4221b..0000000 --- a/ProjectedFSLib.Managed.API/WriteBuffer.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "IWriteBuffer.h" -#include "ApiHelper.h" - -#pragma once - -namespace Microsoft { -namespace Windows { -namespace ProjFS { - /// - /// Helper class to ensure correct alignment when providing file contents for a placeholder. - /// - /// - /// - /// The provider does not instantiate this class directly. It uses the - /// ProjFS.VirtualizationInstance.CreateWriteBuffer method to obtain a properly initialized - /// instance of this class. - /// - /// - /// The ProjFS.VirtualizationInstance.WriteFileData method requires a data buffer containing - /// file data for a placeholder so that ProjFS can convert the placeholder to a hydrated placeholder - /// (see ProjFS.OnDiskFileState for a discussion of file states). Internally ProjFS uses - /// the user's FILE_OBJECT to write this data to the file. Because the user may have opened the - /// file for unbuffered I/O, and unbuffered I/O imposes certain alignment requirements, this - /// class is provided to abstract out those details. - /// - /// - /// When the provider starts its virtualization instance, the VirtualizationInstance class - /// queries the alignment requirements of the underlying physical storage device and uses this - /// information to return a properly-initialized instance of this class from its CreateWriteBuffer - /// method. - /// - /// - public ref class WriteBuffer : IWriteBuffer - { - internal: - WriteBuffer( - unsigned long bufferSize, - unsigned long alignment); - - WriteBuffer( - unsigned long bufferSize, - PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceCtx, - ApiHelper^ apiHelper); - - public: - ~WriteBuffer(); - - /// - /// Gets the allocated length of the buffer. - /// - virtual property long long Length - { - long long get(void) sealed; - }; - - /// - /// Gets a representing the internal buffer. - /// - virtual property System::IO::UnmanagedMemoryStream^ Stream - { - System::IO::UnmanagedMemoryStream^ get(void) sealed; - }; - - /// - /// Gets a pointer to the internal buffer. - /// - virtual property System::IntPtr Pointer - { - System::IntPtr get(void) sealed; - } - - protected: - /// - /// Frees the internal buffer. - /// - !WriteBuffer(); - - private: - System::IO::UnmanagedMemoryStream^ stream; - unsigned char* buffer; - PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceCtx; - ApiHelper^ apiHelper; - }; -}}} // namespace Microsoft.Windows.ProjFS \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/app.ico b/ProjectedFSLib.Managed.API/app.ico deleted file mode 100644 index 789d7cc..0000000 Binary files a/ProjectedFSLib.Managed.API/app.ico and /dev/null differ diff --git a/ProjectedFSLib.Managed.API/app.rc b/ProjectedFSLib.Managed.API/app.rc deleted file mode 100644 index a1fe863..0000000 Binary files a/ProjectedFSLib.Managed.API/app.rc and /dev/null differ diff --git a/ProjectedFSLib.Managed.API/prjlib_deprecated.h b/ProjectedFSLib.Managed.API/prjlib_deprecated.h deleted file mode 100644 index 1054131..0000000 --- a/ProjectedFSLib.Managed.API/prjlib_deprecated.h +++ /dev/null @@ -1,281 +0,0 @@ -/*++ - -Copyright (c) Microsoft Corporation. -Licensed under the MIT license. - - -Module Name: - - prjlib_deprecated.h - -Abstract: - - This module defines pre-release ProjFS constants, data structures, and functions that were deprecated - in Windows 10 version 1809. We keep their definitions here to separate them from the final public - API which is available in the Windows 10 SDK. To use the final public API #include ProjectedFSLib.h - and link against ProjectedFSLib.lib. - - The structures and APIs declared in this file will be removed from a future version of Windows. - ---*/ - -#ifndef PRJLIB_DEPRECATED_H -#define PRJLIB_DEPRECATED_H - -#if _MSC_VER > 1000 -#pragma once -#endif - -#pragma warning(disable:4201) // nameless struct/union - -#include - -#define STDAPI_DEPRECATED(_MSG) EXTERN_C __declspec(deprecated(_MSG)) HRESULT STDAPICALLTYPE - -#pragma region Desktop Family -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - -#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS4) - -#pragma region Common structures - -typedef PRJ_NOTIFICATION PRJ_NOTIFICATION_TYPE; - -typedef HANDLE PRJ_VIRTUALIZATIONINSTANCE_HANDLE; - -#pragma endregion - -#pragma region Virtualization instance APIs - -// -// Forward definitions. -// - -typedef struct _PRJ_COMMAND_CALLBACKS PRJ_COMMAND_CALLBACKS, *PPRJ_COMMAND_CALLBACKS; - -STDAPI_DEPRECATED("Use PrjStartVirtualizing instead of PrjStartVirtualizationInstance.") -PrjStartVirtualizationInstance ( - _In_ LPCWSTR VirtualizationRootPath, - _In_ PPRJ_COMMAND_CALLBACKS Callbacks, - _In_opt_ DWORD Flags, - _In_opt_ DWORD GlobalNotificationMask, - _In_opt_ DWORD PoolThreadCount, - _In_opt_ DWORD ConcurrentThreadCount, - _In_opt_ PVOID InstanceContext, - _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle - ); - -typedef struct _VIRTUALIZATION_INST_EXTENDED_PARAMETERS -{ - DWORD Size; - DWORD Flags; - DWORD PoolThreadCount; - DWORD ConcurrentThreadCount; - PRJ_NOTIFICATION_MAPPING* NotificationMappings; - DWORD NumNotificationMappingsCount; -} VIRTUALIZATION_INST_EXTENDED_PARAMETERS, *PVIRTUALIZATION_INST_EXTENDED_PARAMETERS; - -#define PRJ_FLAG_INSTANCE_NEGATIVE_PATH_CACHE 0x00000002 - -STDAPI_DEPRECATED("Use PrjStartVirtualizing instead of PrjStartVirtualizationInstanceEx.") -PrjStartVirtualizationInstanceEx ( - _In_ LPCWSTR VirtualizationRootPath, - _In_ PPRJ_COMMAND_CALLBACKS Callbacks, - _In_opt_ PVOID InstanceContext, - _In_opt_ PVIRTUALIZATION_INST_EXTENDED_PARAMETERS ExtendedParameters, - _Outptr_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE* VirtualizationInstanceHandle - ); - -STDAPI_DEPRECATED("Use PrjStopVirtualizing instead of PrjStopVirtualizationInstance.") -PrjStopVirtualizationInstance ( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle - ); - -STDAPI_DEPRECATED("Use PrjGetVirtualizationInstanceInfo instead of PrjGetVirtualizationInstanceIdFromHandle.") -PrjGetVirtualizationInstanceIdFromHandle ( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _Out_ LPGUID VirtualizationInstanceID - ); - -#pragma endregion - -#pragma region Placeholder and File APIs - -#define PRJ_FLAG_VIRTUALIZATION_ROOT 0x00000010 - -STDAPI_DEPRECATED("Use PrjMarkDirectoryAsPlaceholder instead of PrjConvertDirectoryToPlaceholder.") -PrjConvertDirectoryToPlaceholder ( - _In_ LPCWSTR RootPathName, - _In_ LPCWSTR TargetPathName, - _In_opt_ PRJ_PLACEHOLDER_VERSION_INFO* VersionInfo, - _In_opt_ DWORD Flags, - _In_ LPCGUID VirtualizationInstanceID - ); - -typedef struct _PRJ_EA_INFORMATION -{ - DWORD EaBufferSize; - DWORD OffsetToFirstEa; -} PRJ_EA_INFORMATION, *PPRJ_EA_INFORMATION; - -typedef struct _PRJ_SECURITY_INFORMATION -{ - DWORD SecurityBufferSize; - DWORD OffsetToSecurityDescriptor; -} PRJ_SECURITY_INFORMATION, *PPRJ_SECURITY_INFORMATION; - -typedef struct _PRJ_STREAMS_INFORMATION -{ - DWORD StreamsInfoBufferSize; - DWORD OffsetToFirstStreamInfo; -} PRJ_STREAMS_INFORMATION, *PPRJ_STREAMS_INFORMATION; - -typedef struct _PRJ_PLACEHOLDER_INFORMATION -{ - DWORD Size; - PRJ_FILE_BASIC_INFO FileBasicInfo; - PRJ_EA_INFORMATION EaInformation; - PRJ_SECURITY_INFORMATION SecurityInformation; - PRJ_STREAMS_INFORMATION StreamsInformation; - PRJ_PLACEHOLDER_VERSION_INFO VersionInfo; - UCHAR VariableData[ANYSIZE_ARRAY]; -} PRJ_PLACEHOLDER_INFORMATION, *PPRJ_PLACEHOLDER_INFORMATION; - -STDAPI_DEPRECATED("Use PrjWritePlaceholderInfo instead of PrjWritePlaceholderInformation.") -PrjWritePlaceholderInformation ( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _In_ LPCWSTR DestinationFileName, - _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, - _In_ DWORD Length - ); - -STDAPI_DEPRECATED("Use PrjUpdateFileIfNeeded instead of PrjUpdatePlaceholderIfNeeded.") -PrjUpdatePlaceholderIfNeeded ( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _In_ LPCWSTR DestinationFileName, - _In_ PPRJ_PLACEHOLDER_INFORMATION PlaceholderInformation, - _In_ DWORD Length, - _In_opt_ DWORD UpdateFlags, - _Out_opt_ PDWORD FailureReason - ); - -STDAPI_DEPRECATED("Use PrjWriteFileData instead of PrjWriteFile.") -PrjWriteFile ( - _In_ PRJ_VIRTUALIZATIONINSTANCE_HANDLE VirtualizationInstanceHandle, - _In_ const GUID* StreamId, - _In_reads_bytes_(Length) void* Buffer, - _In_ UINT64 ByteOffset, - _In_ UINT32 Length - ); - -#pragma endregion - -#pragma region Callback support - -STDAPI_DEPRECATED("PrjCommandCallbacksInit is deprecated and will not exist in future versions of Windows.") -PrjCommandCallbacksInit ( - _In_ DWORD CallbacksSize, - _Out_writes_bytes_(CallbacksSize) PPRJ_COMMAND_CALLBACKS Callbacks - ); - -typedef -HRESULT -(CALLBACK PRJ_GET_PLACEHOLDER_INFORMATION_CB) ( - _In_ PRJ_CALLBACK_DATA* CallbackData, - _In_ DWORD DesiredAccess, - _In_ DWORD ShareMode, - _In_ DWORD CreateDisposition, - _In_ DWORD CreateOptions, - _In_ LPCWSTR DestinationFileName - ); - -typedef -HRESULT -(CALLBACK PRJ_GET_FILE_STREAM_CB) ( - _In_ PRJ_CALLBACK_DATA* CallbackData, - _In_ LARGE_INTEGER ByteOffset, - _In_ DWORD Length - ); - -typedef union _PRJ_OPERATION_PARAMETERS { - - struct { - DWORD DesiredAccess; - DWORD ShareMode; - DWORD CreateDisposition; - DWORD CreateOptions; - DWORD IoStatusInformation; - DWORD NotificationMask; - } PostCreate; - - struct { - DWORD NotificationMask; - } FileRenamed; - - struct { - BOOLEAN IsFileModified; - } FileDeletedOnHandleClose; - -} PRJ_OPERATION_PARAMETERS, *PPRJ_OPERATION_PARAMETERS; - -typedef -HRESULT -(CALLBACK PRJ_NOTIFY_OPERATION_CB) ( - _In_ PRJ_CALLBACK_DATA* CallbackData, - _In_ BOOLEAN IsDirectory, - _In_ PRJ_NOTIFICATION_TYPE NotificationType, - _In_opt_ LPCWSTR DestinationFileName, - _Inout_ PPRJ_OPERATION_PARAMETERS OperationParameters - ); - -typedef struct _PRJ_COMMAND_CALLBACKS -{ - - // - // Size of this structure. Initialized by PrjCommandCallbacksInit(). - // - - DWORD Size; - - // - // The provider must implement the following callbacks. - // - - PRJ_START_DIRECTORY_ENUMERATION_CB* PrjStartDirectoryEnumeration; - - PRJ_END_DIRECTORY_ENUMERATION_CB* PrjEndDirectoryEnumeration; - - PRJ_GET_DIRECTORY_ENUMERATION_CB* PrjGetDirectoryEnumeration; - - PRJ_GET_PLACEHOLDER_INFORMATION_CB* PrjGetPlaceholderInformation; - - PRJ_GET_FILE_STREAM_CB* PrjGetFileStream; - - // - // Optional. If the provider does not implement this callback, ProjFS will invoke the directory - // enumeration callbacks to determine the existence of a file path in the provider's store. - // - - PRJ_QUERY_FILE_NAME_CB* PrjQueryFileName; - - // - // Optional. If the provider does not implement this callback, it will not get any notifications - // from ProjFS. - // - - PRJ_NOTIFY_OPERATION_CB* PrjNotifyOperation; - - // - // Optional. If the provider does not implement this callback, operations in ProjFS will not - // be able to be canceled. - // - - PRJ_CANCEL_COMMAND_CB* PrjCancelCommand; - -} PRJ_COMMAND_CALLBACKS, *PPRJ_COMMAND_CALLBACKS; - -#pragma endregion - -#endif // _WIN32_WINNT >= _WIN32_WINNT_WIN10_RS4 -#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -#endif // PRJLIB_DEPRECATED_H diff --git a/ProjectedFSLib.Managed.API/scripts/CreateCliAssemblyVersion.bat b/ProjectedFSLib.Managed.API/scripts/CreateCliAssemblyVersion.bat deleted file mode 100644 index d7603e4..0000000 --- a/ProjectedFSLib.Managed.API/scripts/CreateCliAssemblyVersion.bat +++ /dev/null @@ -1,14 +0,0 @@ -REM Usage: -REM CreateCliAssemblyVersion ProjectName VersionString SolutionDir -set ProjectName=%1 -set VersionString=%2 -set SolutionDir=%3 - -mkdir %SolutionDir%\..\BuildOutput -mkdir %SolutionDir%\..\BuildOutput\%ProjectName% -echo #include "stdafx.h" > %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h -echo using namespace System::Reflection; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h -echo [assembly:AssemblyVersion("%VersionString%")]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h -echo [assembly:AssemblyFileVersion("%VersionString%")]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h -echo [assembly:AssemblyKeyFileAttribute(LR"(%SolutionDir%ProjectedFSLib.Managed.API\signing\35MSSharedLib1024.snk)")]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h -echo [assembly:AssemblyDelaySignAttribute(true)]; >> %SolutionDir%\..\BuildOutput\%ProjectName%\AssemblyVersion.h \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/scripts/CreateVersionHeader.bat b/ProjectedFSLib.Managed.API/scripts/CreateVersionHeader.bat deleted file mode 100644 index 74d6f53..0000000 --- a/ProjectedFSLib.Managed.API/scripts/CreateVersionHeader.bat +++ /dev/null @@ -1,16 +0,0 @@ -REM Usage: -REM CreateVersionHeader.bat ProjectName VersionString SolutionDir -set ProjectName=%1 -set VersionString=%2 -set SolutionDir=%3 - -mkdir %SolutionDir%\..\BuildOutput -mkdir %SolutionDir%\..\BuildOutput\%ProjectName% - -set comma_version_string=%VersionString% -set comma_version_string=%comma_version_string:.=,% - -echo #define PRJLIB_FILE_VERSION %comma_version_string% > %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h -echo #define PRJLIB_FILE_VERSION_STRING "%VersionString%" >> %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h -echo #define PRJLIB_PRODUCT_VERSION %comma_version_string% >> %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h -echo #define PRJLIB_PRODUCT_VERSION_STRING "%VersionString%" >> %SolutionDir%\..\BuildOutput\%ProjectName%\VersionHeader.h \ No newline at end of file diff --git a/ProjectedFSLib.Managed.API/signing/35MSSharedLib1024.snk b/ProjectedFSLib.Managed.API/signing/35MSSharedLib1024.snk deleted file mode 100644 index 695f1b3..0000000 Binary files a/ProjectedFSLib.Managed.API/signing/35MSSharedLib1024.snk and /dev/null differ diff --git a/ProjectedFSLib.Managed.API/signing/CodeSignConfig.xml b/ProjectedFSLib.Managed.API/signing/CodeSignConfig.xml deleted file mode 100644 index 41e1228..0000000 --- a/ProjectedFSLib.Managed.API/signing/CodeSignConfig.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/ProjectedFSLib.Managed.API/signing/NuPkgSignConfig.xml b/ProjectedFSLib.Managed.API/signing/NuPkgSignConfig.xml deleted file mode 100644 index 841d331..0000000 --- a/ProjectedFSLib.Managed.API/signing/NuPkgSignConfig.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/ProjectedFSLib.Managed.API/stdafx.cpp b/ProjectedFSLib.Managed.API/stdafx.cpp deleted file mode 100644 index affb20d..0000000 Binary files a/ProjectedFSLib.Managed.API/stdafx.cpp and /dev/null differ diff --git a/ProjectedFSLib.Managed.API/stdafx.h b/ProjectedFSLib.Managed.API/stdafx.h deleted file mode 100644 index 8923efa..0000000 Binary files a/ProjectedFSLib.Managed.API/stdafx.h and /dev/null differ diff --git a/ProjectedFSLib.Managed.Test/BasicTests.cs b/ProjectedFSLib.Managed.Test/BasicTests.cs index 159ba3b..3f2aed3 100644 --- a/ProjectedFSLib.Managed.Test/BasicTests.cs +++ b/ProjectedFSLib.Managed.Test/BasicTests.cs @@ -35,7 +35,7 @@ public class BasicTests [OneTimeSetUp] public void ClassSetup() { - // Default timeout for wait handles is 10 seconds + // Default timeout for wait handles helpers = new Helpers(10 * 1000); } diff --git a/ProjectedFSLib.Managed.Test/Helpers.cs b/ProjectedFSLib.Managed.Test/Helpers.cs index 87294db..e86cdc5 100644 --- a/ProjectedFSLib.Managed.Test/Helpers.cs +++ b/ProjectedFSLib.Managed.Test/Helpers.cs @@ -1,7 +1,7 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using NUnit.Framework; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using NUnit.Framework; using System; using System.Collections.Generic; using System.Diagnostics; @@ -41,9 +41,9 @@ public Helpers( int waitTimeoutInMs ) { - m_waitTimeoutInMs = waitTimeoutInMs; - - // Create the events that the notifications tests use. + m_waitTimeoutInMs = waitTimeoutInMs; + + // Create the events that the notifications tests use. notificationEvents = new List(); foreach (string eventName in Enum.GetNames(typeof(NotifyWaitHandleNames))) { @@ -51,9 +51,9 @@ int waitTimeoutInMs } } - public void StartTestProvider() + public void StartTestProvider() { - StartTestProvider(out string sourceRoot, out string virtRoot); + StartTestProvider(out string sourceRoot, out string virtRoot); } public void StartTestProvider(out string sourceRoot, out string virtRoot) @@ -75,7 +75,8 @@ public void StartTestProvider(out string sourceRoot, out string virtRoot) // Add all the arguments, as well as the "test mode" argument. ProviderProcess.StartInfo.Arguments = sourceArg + virtRootArg + " -t"; - ProviderProcess.StartInfo.UseShellExecute = true; + ProviderProcess.StartInfo.UseShellExecute = false; + ProviderProcess.StartInfo.CreateNoWindow = true; ProviderProcess.Start(); @@ -88,7 +89,18 @@ public void StartTestProvider(out string sourceRoot, out string virtRoot) public void StopTestProvider() { - ProviderProcess.CloseMainWindow(); + try + { + if (ProviderProcess != null && !ProviderProcess.HasExited) + { + ProviderProcess.Kill(); + ProviderProcess.WaitForExit(5000); + } + } + catch (Exception ex) + { + Console.Error.WriteLine("StopTestProvider: {0}", ex.Message); + } } // Makes name strings for the source and virtualization roots for a test, using the NUnit @@ -103,8 +115,22 @@ public void StopTestProvider() // itself or in a setup/teardown fixture for a test case. public void GetRootNamesForTest(out string sourceName, out string virtRootName) { + // ProjFS symlinks require NTFS (ReFS doesn't support the atomic create ECP for symlinks). + // If the working directory is on a non-NTFS volume, use a temp directory on the system drive. + string workDir = TestContext.CurrentContext.WorkDirectory; + try + { + var driveInfo = new System.IO.DriveInfo(Path.GetPathRoot(workDir)); + if (!string.Equals(driveInfo.DriveFormat, "NTFS", StringComparison.OrdinalIgnoreCase)) + { + workDir = Path.Combine(Path.GetTempPath(), "ProjFSTests"); + Directory.CreateDirectory(workDir); + } + } + catch { /* If we can't determine the FS type, use the original workDir */ } + string baseName = Path.Combine( - TestContext.CurrentContext.WorkDirectory, + workDir, TestContext.CurrentContext.Test.MethodName); sourceName = baseName + "_source"; @@ -160,38 +186,38 @@ public string CreateVirtualFile(string fileName) // Creates a symlink in the source to another file in the source so that it is projected into the virtualization root. // Returns the full path to the virtual symlink. - public string CreateVirtualSymlink(string fileName, string targetName, bool useRootedPaths = false) - { - GetRootNamesForTest(out string sourceRoot, out string virtRoot); - string sourceSymlinkName = Path.Combine(sourceRoot, fileName); - string sourceTargetName = useRootedPaths ? Path.Combine(sourceRoot, targetName) : targetName; - - if (!File.Exists(sourceSymlinkName)) - { - CreateSymlinkAndAncestor(sourceSymlinkName, sourceTargetName, true); - } - - return Path.Combine(virtRoot, fileName); - } - - // Creates a symlink in the source to another directory in the source so that it is projected into the virtualization root. - // Returns the full path to the virtual symlink. - public string CreateVirtualSymlinkDirectory(string symlinkDirectoryName, string targetName, bool useRootedPaths = false) - { - GetRootNamesForTest(out string sourceRoot, out string virtRoot); - string sourceSymlinkName = Path.Combine(sourceRoot, symlinkDirectoryName); - string sourceTargetName = useRootedPaths ? Path.Combine(sourceRoot, targetName) : targetName; - - if (!Directory.Exists(sourceSymlinkName)) - { - CreateSymlinkAndAncestor(sourceSymlinkName, sourceTargetName, false); - } - - return Path.Combine(virtRoot, symlinkDirectoryName); - } - - // Create a file in the virtualization root (i.e. a non-projected or "full" file). - // Returns the full path to the full file. + public string CreateVirtualSymlink(string fileName, string targetName, bool useRootedPaths = false) + { + GetRootNamesForTest(out string sourceRoot, out string virtRoot); + string sourceSymlinkName = Path.Combine(sourceRoot, fileName); + string sourceTargetName = useRootedPaths ? Path.Combine(sourceRoot, targetName) : targetName; + + if (!File.Exists(sourceSymlinkName)) + { + CreateSymlinkAndAncestor(sourceSymlinkName, sourceTargetName, true); + } + + return Path.Combine(virtRoot, fileName); + } + + // Creates a symlink in the source to another directory in the source so that it is projected into the virtualization root. + // Returns the full path to the virtual symlink. + public string CreateVirtualSymlinkDirectory(string symlinkDirectoryName, string targetName, bool useRootedPaths = false) + { + GetRootNamesForTest(out string sourceRoot, out string virtRoot); + string sourceSymlinkName = Path.Combine(sourceRoot, symlinkDirectoryName); + string sourceTargetName = useRootedPaths ? Path.Combine(sourceRoot, targetName) : targetName; + + if (!Directory.Exists(sourceSymlinkName)) + { + CreateSymlinkAndAncestor(sourceSymlinkName, sourceTargetName, false); + } + + return Path.Combine(virtRoot, symlinkDirectoryName); + } + + // Create a file in the virtualization root (i.e. a non-projected or "full" file). + // Returns the full path to the full file. public string CreateFullFile(string fileName, string fileContent) { GetRootNamesForTest(out string sourceRoot, out string virtRoot); @@ -238,17 +264,17 @@ public string ReadFileInVirtRoot(string fileName) return fileContent; } - public string ReadReparsePointTargetInVirtualRoot(string symlinkFileName) - { + public string ReadReparsePointTargetInVirtualRoot(string symlinkFileName) + { GetRootNamesForTest(out string sourceRoot, out string virtRoot); string fullSymlinkName = Path.Combine(virtRoot, symlinkFileName); - - if (!SimpleProviderManaged.FileSystemApi.TryGetReparsePointTarget(fullSymlinkName, out string reparsePointTarget)) - { - throw new Exception($"Failed to get a reparse point of {fullSymlinkName}."); - } - - return reparsePointTarget; + + if (!SimpleProviderManaged.FileSystemApi.TryGetReparsePointTarget(fullSymlinkName, out string reparsePointTarget)) + { + throw new Exception($"Failed to get a reparse point of {fullSymlinkName}."); + } + + return reparsePointTarget; } public FileStream OpenFileInVirtRoot(string fileName, FileMode mode) @@ -267,20 +293,20 @@ public string RandomString(int length) const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return new string(Enumerable.Repeat(chars, length) .Select(s => s[random.Next(s.Length)]).ToArray()); - } - - private void CreateSymlinkAndAncestor(string sourceSymlinkName, string sourceTargetName, bool isFile) - { - DirectoryInfo ancestorPath = new DirectoryInfo(Path.GetDirectoryName(sourceSymlinkName)); - if (!ancestorPath.Exists) - { - ancestorPath.Create(); - } - - if (!SimpleProviderManaged.FileSystemApi.TryCreateSymbolicLink(sourceSymlinkName, sourceTargetName, isFile)) - { - throw new Exception($"Failed to create directory symlink {sourceSymlinkName}."); - } + } + + private void CreateSymlinkAndAncestor(string sourceSymlinkName, string sourceTargetName, bool isFile) + { + DirectoryInfo ancestorPath = new DirectoryInfo(Path.GetDirectoryName(sourceSymlinkName)); + if (!ancestorPath.Exists) + { + ancestorPath.Create(); + } + + if (!SimpleProviderManaged.FileSystemApi.TryCreateSymbolicLink(sourceSymlinkName, sourceTargetName, isFile)) + { + throw new Exception($"Failed to create directory symlink {sourceSymlinkName}."); + } } } } diff --git a/ProjectedFSLib.Managed.Test/ProjectedFSLib.Managed.Test.csproj b/ProjectedFSLib.Managed.Test/ProjectedFSLib.Managed.Test.csproj index 8807447..ec857f5 100644 --- a/ProjectedFSLib.Managed.Test/ProjectedFSLib.Managed.Test.csproj +++ b/ProjectedFSLib.Managed.Test/ProjectedFSLib.Managed.Test.csproj @@ -1,22 +1,21 @@  - - net48;netcoreapp3.1 + net48;net8.0-windows10.0.17763.0;net10.0-windows10.0.17763.0 false false x64 Exe + true - - + + - - + diff --git a/ProjectedFSLib.Managed.Test/README.md b/ProjectedFSLib.Managed.Test/README.md index ffa5946..24b4dca 100644 --- a/ProjectedFSLib.Managed.Test/README.md +++ b/ProjectedFSLib.Managed.Test/README.md @@ -1,6 +1,16 @@ -# ProjectedFSLib.Managed.Test.exe +# ProjectedFSLib.Managed.Test -## Command line parameters +## Running Tests + +The simplest way to run all tests is via `dotnet test`: + +```bash +dotnet test -c Release +``` + +## Command line parameters (NUnitLite runner) + +The test project also builds as an executable that can be run directly: `ProjectedFSLib.Managed.Test.exe [NUnit parameters] --params ProviderExe=` @@ -13,6 +23,19 @@ Where: Each test case creates a source directory and a virtualization root and then uses the SimpleProviderManaged provider to project the contents of the source directory into the virtualization root. +### NTFS Requirement for Symlink Tests + +The symlink tests (`TestCanReadSymlinksThroughVirtualizationRoot`, etc.) require the test +working directory to be on an **NTFS** volume. ProjFS creates symlink placeholders via the +NTFS atomic create ECP, which is not supported on ReFS. If the working directory is on a +non-NTFS volume (e.g. ReFS), the test helper automatically falls back to a temp directory on +the system drive (typically `C:\`, which is NTFS). + +If symlink tests fail with `ERROR_NOT_SUPPORTED`, check the volume's filesystem type: +```powershell +Get-Volume -DriveLetter D | Select-Object FileSystem +``` + By default the test creates the source and virtualization roots in the test's working directory. If you are using [VFSForGit](https://github.com/Microsoft/VFSForGit) to project GitHub enlistments to your local machine and you try to run the test under the Visual Studio debugger, then it is possible that the working directory will end up under diff --git a/ProjectedFSLib.Managed.cpp.props b/ProjectedFSLib.Managed.cpp.props deleted file mode 100644 index 2ec09b9..0000000 --- a/ProjectedFSLib.Managed.cpp.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - $(BuildOutputDir)\$(MSBuildProjectName)\bin\$(Platform)\$(Configuration)\ - $(BuildOutputDir)\$(MSBuildProjectName)\intermediate\$(Platform)\$(Configuration)\ - - - diff --git a/ProjectedFSLib.Managed.cs.props b/ProjectedFSLib.Managed.cs.props deleted file mode 100644 index ab7e095..0000000 --- a/ProjectedFSLib.Managed.cs.props +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - $(BuildOutputDir)\$(MSBuildProjectName)\bin\$(Platform)\$(Configuration)\ - $(BuildOutputDir)\$(MSBuildProjectName)\obj\$(Platform)\$(Configuration)\ - $(BuildOutputDir)\$(MSBuildProjectName)\intermediate\$(Platform)\$(Configuration)\ - - - diff --git a/ProjectedFSLib.Managed.props b/ProjectedFSLib.Managed.props deleted file mode 100644 index ef70ac0..0000000 --- a/ProjectedFSLib.Managed.props +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - 0.2.173.2 - - - - Debug - x64 - $(SolutionDir)..\BuildOutput - $(SolutionDir)..\packages - - - diff --git a/ProjectedFSLib.Managed.sln b/ProjectedFSLib.Managed.sln deleted file mode 100644 index 91213d5..0000000 --- a/ProjectedFSLib.Managed.sln +++ /dev/null @@ -1,56 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29512.175 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectedFSLib.Managed", "ProjectedFSLib.Managed.API\NetFramework\ProjectedFSLib.Managed.vcxproj", "{4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectedFSLib.Managed.NetCore", "ProjectedFSLib.Managed.API\NetCore\ProjectedFSLib.Managed.NetCore.vcxproj", "{D29E5723-25E6-41C7-AEB9-099CDE30538A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectedFSLib.Managed.Test", "ProjectedFSLib.Managed.Test\ProjectedFSLib.Managed.Test.csproj", "{B4018C7F-BF11-47BC-8770-72B05C9437EE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleProviderManaged", "simpleProviderManaged\SimpleProviderManaged.csproj", "{5697F978-E1ED-4C2E-8218-4110F5EA559D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{028D4D62-E4B9-44F0-A086-921292B7E89B}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{80F77524-CD14-4BD0-A197-8E7D5DD7B6E0}" - ProjectSection(SolutionItems) = preProject - scripts\BuildProjFS-Managed.bat = scripts\BuildProjFS-Managed.bat - scripts\InitializeEnvironment.bat = scripts\InitializeEnvironment.bat - scripts\NukeBuildOutputs.bat = scripts\NukeBuildOutputs.bat - scripts\RunTests.bat = scripts\RunTests.bat - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Debug|x64.ActiveCfg = Debug|x64 - {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Debug|x64.Build.0 = Debug|x64 - {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Release|x64.ActiveCfg = Release|x64 - {4E5F40B3-B56F-4B62-92CB-68E7E0E36AFA}.Release|x64.Build.0 = Release|x64 - {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Debug|x64.ActiveCfg = Debug|x64 - {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Debug|x64.Build.0 = Debug|x64 - {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Release|x64.ActiveCfg = Release|x64 - {D29E5723-25E6-41C7-AEB9-099CDE30538A}.Release|x64.Build.0 = Release|x64 - {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Debug|x64.ActiveCfg = Debug|Any CPU - {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Debug|x64.Build.0 = Debug|Any CPU - {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Release|x64.ActiveCfg = Release|Any CPU - {B4018C7F-BF11-47BC-8770-72B05C9437EE}.Release|x64.Build.0 = Release|Any CPU - {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Debug|x64.ActiveCfg = Debug|Any CPU - {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Debug|x64.Build.0 = Debug|Any CPU - {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Release|x64.ActiveCfg = Release|Any CPU - {5697F978-E1ED-4C2E-8218-4110F5EA559D}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0F3321A2-4572-44F0-B7F3-BD0E79433A1E} - EndGlobalSection -EndGlobal diff --git a/ProjectedFSLib.Managed.slnx b/ProjectedFSLib.Managed.slnx new file mode 100644 index 0000000..778b298 --- /dev/null +++ b/ProjectedFSLib.Managed.slnx @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/ProjectedFSLib.Managed/DirectoryEnumerationResults.cs b/ProjectedFSLib.Managed/DirectoryEnumerationResults.cs new file mode 100644 index 0000000..48041e6 --- /dev/null +++ b/ProjectedFSLib.Managed/DirectoryEnumerationResults.cs @@ -0,0 +1,139 @@ +#nullable disable +using System; +using System.IO; +using System.Runtime.InteropServices; +using static Microsoft.Windows.ProjFS.ProjFSNative; + +namespace Microsoft.Windows.ProjFS +{ + /// + /// Pure C# P/Invoke implementation of IDirectoryEnumerationResults, + /// wrapping PrjFillDirEntryBuffer for the given PRJ_DIR_ENTRY_BUFFER_HANDLE. + /// + public class DirectoryEnumerationResults : IDirectoryEnumerationResults + { + private readonly IntPtr _dirEntryBufferHandle; + + internal DirectoryEnumerationResults(IntPtr dirEntryBufferHandle) + { + _dirEntryBufferHandle = dirEntryBufferHandle; + } + + /// Gets the native directory entry buffer handle for use by CompleteCommand. + internal IntPtr DirEntryBufferHandle => _dirEntryBufferHandle; + + /// + public bool Add( + string fileName, + long fileSize, + bool isDirectory, + FileAttributes fileAttributes, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime) + { + ValidateFileName(fileName); + + var basicInfo = new PRJ_FILE_BASIC_INFO + { + IsDirectory = isDirectory ? (byte)1 : (byte)0, + FileSize = fileSize, + CreationTime = creationTime.ToFileTime(), + LastAccessTime = lastAccessTime.ToFileTime(), + LastWriteTime = lastWriteTime.ToFileTime(), + ChangeTime = changeTime.ToFileTime(), + FileAttributes = (uint)fileAttributes, + }; + + int hr = ProjFSNative.PrjFillDirEntryBuffer(fileName, ref basicInfo, _dirEntryBufferHandle); + return hr >= 0; // S_OK = success; negative HRESULT (e.g. INSUFFICIENT_BUFFER) = buffer full + } + + /// + public bool Add(string fileName, long fileSize, bool isDirectory) + { + ValidateFileName(fileName); + + var basicInfo = new PRJ_FILE_BASIC_INFO + { + IsDirectory = isDirectory ? (byte)1 : (byte)0, + FileSize = fileSize, + }; + + int hr = ProjFSNative.PrjFillDirEntryBuffer(fileName, ref basicInfo, _dirEntryBufferHandle); + return hr >= 0; + } + + /// Adds one entry to a directory enumeration result, with optional symlink target. + /// + /// + /// NTFS Required for Symlinks: When is non-null, + /// this calls PrjFillDirEntryBuffer2 which only works on NTFS volumes. ReFS does not support + /// the atomic create ECP used by ProjFS to create symlink placeholders. On ReFS, the subsequent + /// WritePlaceholderInfo2 call will fail with ERROR_NOT_SUPPORTED (0x80070032). + /// + /// + /// is null or empty. + public bool Add( + string fileName, + long fileSize, + bool isDirectory, + FileAttributes fileAttributes, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + string symlinkTargetOrNull) + { + ValidateFileName(fileName); + + var basicInfo = new PRJ_FILE_BASIC_INFO + { + IsDirectory = isDirectory ? (byte)1 : (byte)0, + FileSize = fileSize, + CreationTime = creationTime.ToFileTime(), + LastAccessTime = lastAccessTime.ToFileTime(), + LastWriteTime = lastWriteTime.ToFileTime(), + ChangeTime = changeTime.ToFileTime(), + FileAttributes = (uint)fileAttributes, + }; + + if (symlinkTargetOrNull != null) + { + var extendedInfo = new PRJ_EXTENDED_INFO + { + InfoType = PRJ_EXT_INFO_TYPE_SYMLINK, + NextInfoOffset = 0, + }; + + IntPtr targetPtr = Marshal.StringToHGlobalUni(symlinkTargetOrNull); + try + { + extendedInfo.SymlinkTargetName = targetPtr; + int hr = ProjFSNative.PrjFillDirEntryBuffer2( + _dirEntryBufferHandle, fileName, ref basicInfo, ref extendedInfo); + return hr >= 0; + } + finally + { + Marshal.FreeHGlobal(targetPtr); + } + } + else + { + int hr = ProjFSNative.PrjFillDirEntryBuffer2NoExtInfo( + _dirEntryBufferHandle, fileName, ref basicInfo, IntPtr.Zero); + return hr >= 0; + } + } + + private static void ValidateFileName(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentException("fileName cannot be empty."); + } + } + } +} diff --git a/ProjectedFSLib.Managed/ProjFSLib.cs b/ProjectedFSLib.Managed/ProjFSLib.cs new file mode 100644 index 0000000..8da85c8 --- /dev/null +++ b/ProjectedFSLib.Managed/ProjFSLib.cs @@ -0,0 +1,386 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.Windows.ProjFS +{ + /// + /// HRESULT values used by the ProjFS managed API. + /// Values match the original Microsoft.Windows.ProjFS library exactly. + /// + public enum HResult : int + { + Ok = 0, + Pending = -2147023899, + InternalError = -2147023537, + Handle = -2147024890, + FileNotFound = -2147024894, + PathNotFound = -2147024893, + DirNotEmpty = -2147024751, + VirtualizationInvalidOp = -2147024511, + VirtualizationUnavaliable = -2147024527, + AccessDenied = -2147024891, + AlreadyInitialized = -2147023649, + CannotDelete = -805306079, + Directory = -2147024629, + InsufficientBuffer = -2147024774, + InvalidArg = -2147024809, + OutOfMemory = -2147024882, + ReparsePointEncountered = -2147020501, + } + + [Flags] + public enum NotificationType : uint + { + None = 0x00000001, + FileOpened = 0x00000002, + NewFileCreated = 0x00000004, + FileOverwritten = 0x00000008, + PreDelete = 0x00000010, + PreRename = 0x00000020, + PreCreateHardlink = 0x00000040, + FileRenamed = 0x00000080, + HardlinkCreated = 0x00000100, + FileHandleClosedNoModification = 0x00000200, + FileHandleClosedFileModified = 0x00000400, + FileHandleClosedFileDeleted = 0x00000800, + FilePreConvertToFull = 0x00001000, + UseExistingMask = 0xFFFFFFFF, + } + + [Flags] + public enum UpdateType : uint + { + AllowDirtyMetadata = 1, + AllowDirtyData = 2, + AllowTombstone = 4, + AllowReadOnly = 32, + } + + [Flags] + public enum UpdateFailureCause : uint + { + NoFailure = 0, + DirtyMetadata = 1, + DirtyData = 2, + Tombstone = 4, + ReadOnly = 8, + } + + [Flags] + public enum OnDiskFileState : uint + { + Placeholder = 1, + HydratedPlaceholder = 2, + DirtyPlaceholder = 4, + Full = 8, + Tombstone = 16, + } + + public class NotificationMapping + { + /// + /// Initializes a new instance with set to + /// and set to null. + /// + public NotificationMapping() + { + NotificationMask = NotificationType.None; + } + + /// + /// Initializes a new instance with the specified notification mask and root path. + /// + /// The set of notifications for this root. + /// Path relative to the virtualization root. Use empty string for the root itself. + /// + /// is "." or begins with ".\". + /// + public NotificationMapping(NotificationType notificationMask, string notificationRoot) + { + NotificationMask = notificationMask; + ValidateNotificationRoot(notificationRoot); + NotificationRoot = notificationRoot; + } + + /// A bit vector of values. + public NotificationType NotificationMask { get; set; } + + /// + /// A path to a directory, relative to the virtualization root. + /// The virtualization root itself must be specified as an empty string. + /// + /// + /// The value is "." or begins with ".\". + /// + public string NotificationRoot + { + get => _notificationRoot; + set + { + ValidateNotificationRoot(value); + _notificationRoot = value; + } + } + + private string _notificationRoot; + + private static void ValidateNotificationRoot(string root) + { + if (root == "." || (root != null && root.StartsWith(".\\"))) + { + throw new ArgumentException( + "notificationRoot cannot be \".\" or begin with \".\\\""); + } + } + } + + // Callback delegates matching the original Microsoft.Windows.ProjFS signatures + public delegate void CancelCommandCallback(int commandId); + + public delegate bool NotifyFileOpenedCallback( + string relativePath, + bool isDirectory, + uint triggeringProcessId, + string triggeringProcessImageFileName, + out NotificationType notificationMask); + + public delegate void NotifyNewFileCreatedCallback( + string relativePath, + bool isDirectory, + uint triggeringProcessId, + string triggeringProcessImageFileName, + out NotificationType notificationMask); + + public delegate void NotifyFileOverwrittenCallback( + string relativePath, + bool isDirectory, + uint triggeringProcessId, + string triggeringProcessImageFileName, + out NotificationType notificationMask); + + public delegate void NotifyFileHandleClosedNoModificationCallback( + string relativePath, + bool isDirectory, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate void NotifyFileHandleClosedFileModifiedOrDeletedCallback( + string relativePath, + bool isDirectory, + bool isFileModified, + bool isFileDeleted, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate bool NotifyFilePreConvertToFullCallback( + string relativePath, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate void NotifyFileRenamedCallback( + string relativePath, + string destinationPath, + bool isDirectory, + uint triggeringProcessId, + string triggeringProcessImageFileName, + out NotificationType notificationMask); + + public delegate void NotifyHardlinkCreatedCallback( + string relativePath, + string destinationPath, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate bool NotifyPreDeleteCallback( + string relativePath, + bool isDirectory, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate bool NotifyPreRenameCallback( + string relativePath, + string destinationPath, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate bool NotifyPreCreateHardlinkCallback( + string relativePath, + string destinationPath, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + public delegate HResult QueryFileNameCallback(string relativePath); + + // Interfaces + public interface IWriteBuffer : IDisposable + { + IntPtr Pointer { get; } + UnmanagedMemoryStream Stream { get; } + long Length { get; } + } + + public interface IDirectoryEnumerationResults + { + bool Add( + string fileName, + long fileSize, + bool isDirectory, + FileAttributes fileAttributes, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime); + + bool Add(string fileName, long fileSize, bool isDirectory); + + /// Adds one entry to a directory enumeration result, with optional symlink target. + /// The symlink target path, or null if this is not a symlink. + /// + /// Symlink entries require an NTFS volume. ReFS does not support ProjFS symlink placeholders. + /// See WritePlaceholderInfo2 remarks for details. + /// + bool Add( + string fileName, + long fileSize, + bool isDirectory, + FileAttributes fileAttributes, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + string symlinkTargetOrNull); + } + + public interface IRequiredCallbacks + { + HResult StartDirectoryEnumerationCallback( + int commandId, + Guid enumerationId, + string relativePath, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + HResult EndDirectoryEnumerationCallback(Guid enumerationId); + + HResult GetDirectoryEnumerationCallback( + int commandId, + Guid enumerationId, + string filterFileName, + bool restartScan, + IDirectoryEnumerationResults result); + + HResult GetPlaceholderInfoCallback( + int commandId, + string relativePath, + uint triggeringProcessId, + string triggeringProcessImageFileName); + + HResult GetFileDataCallback( + int commandId, + string relativePath, + ulong byteOffset, + uint length, + Guid dataStreamId, + byte[] contentId, + byte[] providerId, + uint triggeringProcessId, + string triggeringProcessImageFileName); + } + + public interface IVirtualizationInstance + { + /// Returns the virtualization instance GUID. + Guid VirtualizationInstanceId { get; } + + /// Returns the maximum allowed length of a placeholder's contentID or provider ID. + int PlaceholderIdLength { get; } + + /// Retrieves the interface. + IRequiredCallbacks RequiredCallbacks { get; } + + CancelCommandCallback OnCancelCommand { get; set; } + NotifyFileOpenedCallback OnNotifyFileOpened { get; set; } + NotifyNewFileCreatedCallback OnNotifyNewFileCreated { get; set; } + NotifyFileOverwrittenCallback OnNotifyFileOverwritten { get; set; } + NotifyFileHandleClosedNoModificationCallback OnNotifyFileHandleClosedNoModification { get; set; } + NotifyFileHandleClosedFileModifiedOrDeletedCallback OnNotifyFileHandleClosedFileModifiedOrDeleted { get; set; } + NotifyFilePreConvertToFullCallback OnNotifyFilePreConvertToFull { get; set; } + NotifyFileRenamedCallback OnNotifyFileRenamed { get; set; } + NotifyHardlinkCreatedCallback OnNotifyHardlinkCreated { get; set; } + NotifyPreDeleteCallback OnNotifyPreDelete { get; set; } + NotifyPreRenameCallback OnNotifyPreRename { get; set; } + NotifyPreCreateHardlinkCallback OnNotifyPreCreateHardlink { get; set; } + QueryFileNameCallback OnQueryFileName { get; set; } + + HResult StartVirtualizing(IRequiredCallbacks requiredCallbacks); + void StopVirtualizing(); + HResult ClearNegativePathCache(out uint totalEntryNumber); + HResult DeleteFile(string relativePath, UpdateType updateFlags, out UpdateFailureCause failureReason); + + HResult UpdateFileIfNeeded( + string relativePath, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + FileAttributes fileAttributes, + long endOfFile, + byte[] contentId, + byte[] providerId, + UpdateType updateFlags, + out UpdateFailureCause failureReason); + + HResult WritePlaceholderInfo( + string relativePath, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + FileAttributes fileAttributes, + long endOfFile, + bool isDirectory, + byte[] contentId, + byte[] providerId); + + HResult CompleteCommand(int commandId, NotificationType newNotificationMask); + HResult CompleteCommand(int commandId, IDirectoryEnumerationResults results); + HResult CompleteCommand(int commandId, HResult completionResult); + HResult CompleteCommand(int commandId); + + IWriteBuffer CreateWriteBuffer(ulong byteOffset, uint length, out ulong alignedByteOffset, out uint alignedLength); + IWriteBuffer CreateWriteBuffer(uint desiredBufferSize); + + HResult WriteFileData(Guid dataStreamId, IWriteBuffer buffer, ulong byteOffset, uint length); + HResult MarkDirectoryAsPlaceholder(string targetDirectoryPath, byte[] contentId, byte[] providerId); + } + + /// + /// Static utility methods wrapping native ProjFS functions. + /// + public abstract class Utils + { + public static bool DoesNameContainWildCards(string fileName) + { + return ProjFSNative.PrjDoesNameContainWildCards(fileName); + } + + public static bool IsFileNameMatch(string fileNameToCheck, string pattern) + { + return ProjFSNative.PrjFileNameMatch(fileNameToCheck, pattern); + } + + public static int FileNameCompare(string fileName1, string fileName2) + { + return ProjFSNative.PrjFileNameCompare(fileName1, fileName2); + } + + public static bool TryGetOnDiskFileState(string fullPath, out OnDiskFileState fileState) + { + int hr = ProjFSNative.PrjGetOnDiskFileState(fullPath, out uint state); + fileState = (OnDiskFileState)state; + return hr >= 0; + } + } +} diff --git a/ProjectedFSLib.Managed/ProjFSNative.cs b/ProjectedFSLib.Managed/ProjFSNative.cs new file mode 100644 index 0000000..3d66d5e --- /dev/null +++ b/ProjectedFSLib.Managed/ProjFSNative.cs @@ -0,0 +1,490 @@ +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Windows.ProjFS +{ + /// + /// Native P/Invoke declarations for ProjectedFSLib.dll. + /// This replaces the C++/CLI mixed-mode ProjectedFSLib.Managed.dll with pure C# P/Invoke. + /// On .NET 7+, uses LibraryImport source generators for AOT compatibility. + /// On .NET Standard 2.0, falls back to traditional DllImport. + /// + internal static partial class ProjFSNative + { + private const string ProjFSLib = "ProjectedFSLib.dll"; + + internal const int PlaceholderIdLength = 128; + + // ============================ + // Core virtualization lifetime + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjStartVirtualizing( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjStartVirtualizing( +#endif + string virtualizationRootPath, + ref PRJ_CALLBACKS callbacks, + IntPtr instanceContext, + ref PRJ_STARTVIRTUALIZING_OPTIONS options, + out IntPtr namespaceVirtualizationContext); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial void PrjStopVirtualizing(IntPtr namespaceVirtualizationContext); +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern void PrjStopVirtualizing(IntPtr namespaceVirtualizationContext); +#endif + + // ============================ + // Placeholder management + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjWritePlaceholderInfo( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjWritePlaceholderInfo( +#endif + IntPtr namespaceVirtualizationContext, + string destinationFileName, + ref PRJ_PLACEHOLDER_INFO placeholderInfo, + uint length); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjWritePlaceholderInfo2( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjWritePlaceholderInfo2( +#endif + IntPtr namespaceVirtualizationContext, + string destinationFileName, + ref PRJ_PLACEHOLDER_INFO placeholderInfo, + uint placeholderInfoSize, + ref PRJ_EXTENDED_INFO extendedInfo); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, EntryPoint = "PrjWritePlaceholderInfo2")] + internal static partial int PrjWritePlaceholderInfo2Raw( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "PrjWritePlaceholderInfo2")] + internal static extern int PrjWritePlaceholderInfo2Raw( +#endif + IntPtr namespaceVirtualizationContext, + IntPtr destinationFileName, + IntPtr placeholderInfo, + uint placeholderInfoSize, + IntPtr extendedInfo); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjUpdateFileIfNeeded( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjUpdateFileIfNeeded( +#endif + IntPtr namespaceVirtualizationContext, + string destinationFileName, + ref PRJ_PLACEHOLDER_INFO placeholderInfo, + uint length, + uint updateFlags, + out uint failureReason); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjDeleteFile( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjDeleteFile( +#endif + IntPtr namespaceVirtualizationContext, + string destinationFileName, + uint updateFlags, + out uint failureReason); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjMarkDirectoryAsPlaceholder( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjMarkDirectoryAsPlaceholder( +#endif + string rootPathName, + string targetPathName, + ref PRJ_PLACEHOLDER_VERSION_INFO versionInfo, + ref Guid virtualizationInstanceID); + + // Overload for MarkDirectoryAsVirtualizationRoot (versionInfo = null) +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "PrjMarkDirectoryAsPlaceholder")] + internal static partial int PrjMarkDirectoryAsVirtualizationRoot( + string rootPathName, + string? targetPathName, +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "PrjMarkDirectoryAsPlaceholder")] + internal static extern int PrjMarkDirectoryAsVirtualizationRoot( + string rootPathName, + [MarshalAs(UnmanagedType.LPWStr)] string targetPathName, +#endif + IntPtr versionInfo, + ref Guid virtualizationInstanceID); + + // ============================ + // File data streaming + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial int PrjWriteFileData( +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern int PrjWriteFileData( +#endif + IntPtr namespaceVirtualizationContext, + ref Guid dataStreamId, + IntPtr buffer, + ulong byteOffset, + uint length); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial IntPtr PrjAllocateAlignedBuffer( +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern IntPtr PrjAllocateAlignedBuffer( +#endif + IntPtr namespaceVirtualizationContext, + UIntPtr size); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial void PrjFreeAlignedBuffer(IntPtr buffer); +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern void PrjFreeAlignedBuffer(IntPtr buffer); +#endif + + // ============================ + // Command completion + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial int PrjCompleteCommand( +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern int PrjCompleteCommand( +#endif + IntPtr namespaceVirtualizationContext, + int commandId, + int completionResult, + IntPtr extendedParameters); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, EntryPoint = "PrjCompleteCommand")] + internal static partial int PrjCompleteCommandWithNotification( +#else + [DllImport(ProjFSLib, ExactSpelling = true, EntryPoint = "PrjCompleteCommand")] + internal static extern int PrjCompleteCommandWithNotification( +#endif + IntPtr namespaceVirtualizationContext, + int commandId, + int completionResult, + ref PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS extendedParameters); + + // ============================ + // Cache management + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial int PrjClearNegativePathCache( +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern int PrjClearNegativePathCache( +#endif + IntPtr namespaceVirtualizationContext, + out uint totalEntryNumber); + + // ============================ + // Directory enumeration + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjFillDirEntryBuffer( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjFillDirEntryBuffer( +#endif + string fileName, + ref PRJ_FILE_BASIC_INFO fileBasicInfo, + IntPtr dirEntryBufferHandle); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjFillDirEntryBuffer2( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjFillDirEntryBuffer2( +#endif + IntPtr dirEntryBufferHandle, + string fileName, + ref PRJ_FILE_BASIC_INFO fileBasicInfo, + ref PRJ_EXTENDED_INFO extendedInfo); + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "PrjFillDirEntryBuffer2")] + internal static partial int PrjFillDirEntryBuffer2NoExtInfo( +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "PrjFillDirEntryBuffer2")] + internal static extern int PrjFillDirEntryBuffer2NoExtInfo( +#endif + IntPtr dirEntryBufferHandle, + string fileName, + ref PRJ_FILE_BASIC_INFO fileBasicInfo, + IntPtr extendedInfo); + + // ============================ + // Filename utilities + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool PrjDoesNameContainWildCards(string fileName); +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool PrjDoesNameContainWildCards(string fileName); +#endif + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool PrjFileNameMatch(string fileNameToCheck, string pattern); +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool PrjFileNameMatch(string fileNameToCheck, string pattern); +#endif + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjFileNameCompare(string fileName1, string fileName2); +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjFileNameCompare(string fileName1, string fileName2); +#endif + + // ============================ + // File state query + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib, StringMarshalling = StringMarshalling.Utf16)] + internal static partial int PrjGetOnDiskFileState(string destinationFileName, out uint fileState); +#else + [DllImport(ProjFSLib, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int PrjGetOnDiskFileState(string destinationFileName, out uint fileState); +#endif + + // ============================ + // Virtualization instance info + // ============================ + +#if NET7_0_OR_GREATER + [LibraryImport(ProjFSLib)] + internal static partial int PrjGetVirtualizationInstanceInfo( +#else + [DllImport(ProjFSLib, ExactSpelling = true)] + internal static extern int PrjGetVirtualizationInstanceInfo( +#endif + IntPtr namespaceVirtualizationContext, + ref PRJ_VIRTUALIZATION_INSTANCE_INFO virtualizationInstanceInfo); + + // ============================ + // Native structures + // ============================ + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_CALLBACK_DATA + { + public uint Size; + public uint Flags; + public IntPtr NamespaceVirtualizationContext; + public int CommandId; + public Guid FileId; + public Guid DataStreamId; + public IntPtr FilePathName; // PCWSTR + public IntPtr VersionInfo; // PRJ_PLACEHOLDER_VERSION_INFO* + public uint TriggeringProcessId; + public IntPtr TriggeringProcessImageFileName; // PCWSTR + public IntPtr InstanceContext; + } + + internal const uint PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN = 0x00000001; + internal const uint PRJ_CB_DATA_FLAG_ENUM_RETURN_SINGLE_ENTRY = 0x00000002; + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct PRJ_PLACEHOLDER_VERSION_INFO + { + public fixed byte ProviderID[PlaceholderIdLength]; + public fixed byte ContentID[PlaceholderIdLength]; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_FILE_BASIC_INFO + { + public byte IsDirectory; // BOOLEAN (1 byte) + private byte _pad1; + private byte _pad2; + private byte _pad3; + private int _pad4; // Pad to 8-byte alignment for FileSize + public long FileSize; // INT64 + public long CreationTime; // LARGE_INTEGER (FileTime) + public long LastAccessTime; // LARGE_INTEGER (FileTime) + public long LastWriteTime; // LARGE_INTEGER (FileTime) + public long ChangeTime; // LARGE_INTEGER (FileTime) + public uint FileAttributes; // UINT32 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_PLACEHOLDER_INFO + { + public PRJ_FILE_BASIC_INFO FileBasicInfo; + // EaInformation + public uint EaBufferSize; + public uint OffsetToFirstEa; + // SecurityInformation + public uint SecurityBufferSize; + public uint OffsetToSecurityDescriptor; + // StreamsInformation + public uint StreamsInfoBufferSize; + public uint OffsetToFirstStreamInfo; + // VersionInfo + public PRJ_PLACEHOLDER_VERSION_INFO VersionInfo; + // VariableData[1] — flexible array member in the native struct + // Must be included so that sizeof matches the native sizeof(PRJ_PLACEHOLDER_INFO) = 344 + public byte VariableData; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_STARTVIRTUALIZING_OPTIONS + { + public uint Flags; + public uint PoolThreadCount; + public uint ConcurrentThreadCount; + public IntPtr NotificationMappings; // PRJ_NOTIFICATION_MAPPING* + public uint NotificationMappingsCount; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_VIRTUALIZATION_INSTANCE_INFO + { + public Guid InstanceID; + public uint WriteAlignment; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_EXTENDED_INFO + { + public uint InfoType; + public uint NextInfoOffset; + public IntPtr SymlinkTargetName; // PCWSTR + } + + internal const uint PRJ_EXT_INFO_TYPE_SYMLINK = 1; + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_NOTIFICATION_MAPPING_NATIVE + { + public uint NotificationBitMask; // PRJ_NOTIFY_TYPES + public IntPtr NotificationRoot; // PCWSTR + } + + internal const uint PRJ_FLAG_NONE = 0; + internal const uint PRJ_FLAG_USE_NEGATIVE_PATH_CACHE = 1; + + // PRJ_NOTIFICATION values (for the notification callback) + internal const int PRJ_NOTIFICATION_FILE_OPENED = 0x00000002; + internal const int PRJ_NOTIFICATION_NEW_FILE_CREATED = 0x00000004; + internal const int PRJ_NOTIFICATION_FILE_OVERWRITTEN = 0x00000008; + internal const int PRJ_NOTIFICATION_PRE_DELETE = 0x00000010; + internal const int PRJ_NOTIFICATION_PRE_RENAME = 0x00000020; + internal const int PRJ_NOTIFICATION_PRE_SET_HARDLINK = 0x00000040; + internal const int PRJ_NOTIFICATION_FILE_RENAMED = 0x00000080; + internal const int PRJ_NOTIFICATION_HARDLINK_CREATED = 0x00000100; + internal const int PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION = 0x00000200; + internal const int PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_MODIFIED = 0x00000400; + internal const int PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED = 0x00000800; + internal const int PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL = 0x00001000; + + // PRJ_COMPLETE_COMMAND_TYPE + internal const int PRJ_COMPLETE_COMMAND_TYPE_NOTIFICATION = 1; + internal const int PRJ_COMPLETE_COMMAND_TYPE_ENUMERATION = 2; + + [StructLayout(LayoutKind.Explicit)] + internal struct PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS + { + [FieldOffset(0)] + public int CommandType; + + // For Notification type: notification mask at offset 8 + [FieldOffset(8)] + public uint NotificationMask; + + // For Enumeration type: dir entry buffer handle at offset 8 + [FieldOffset(8)] + public IntPtr DirEntryBufferHandle; + } + + // ============================ + // Callback function pointer struct + // ============================ + + [StructLayout(LayoutKind.Sequential)] + internal struct PRJ_CALLBACKS + { + public IntPtr StartDirectoryEnumerationCallback; + public IntPtr EndDirectoryEnumerationCallback; + public IntPtr GetDirectoryEnumerationCallback; + public IntPtr GetPlaceholderInfoCallback; + public IntPtr GetFileDataCallback; + public IntPtr QueryFileNameCallback; + public IntPtr NotificationCallback; + public IntPtr CancelCommandCallback; + } + + // Managed delegate types for callbacks (these get marshaled to native function pointers) + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int StartDirectoryEnumerationDelegate(PRJ_CALLBACK_DATA* callbackData, Guid* enumerationId); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int EndDirectoryEnumerationDelegate(PRJ_CALLBACK_DATA* callbackData, Guid* enumerationId); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int GetDirectoryEnumerationDelegate(PRJ_CALLBACK_DATA* callbackData, Guid* enumerationId, IntPtr searchExpression, IntPtr dirEntryBufferHandle); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int GetPlaceholderInfoDelegate(PRJ_CALLBACK_DATA* callbackData); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int GetFileDataDelegate(PRJ_CALLBACK_DATA* callbackData, ulong byteOffset, uint length); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int QueryFileNameDelegate(PRJ_CALLBACK_DATA* callbackData); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate int NotificationDelegate(PRJ_CALLBACK_DATA* callbackData, byte isDirectory, int notification, IntPtr destinationFileName, IntPtr operationParameters); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal unsafe delegate void CancelCommandDelegate(PRJ_CALLBACK_DATA* callbackData); + } +} diff --git a/ProjectedFSLib.Managed/ProjectedFSLib.Managed.csproj b/ProjectedFSLib.Managed/ProjectedFSLib.Managed.csproj new file mode 100644 index 0000000..ec446be --- /dev/null +++ b/ProjectedFSLib.Managed/ProjectedFSLib.Managed.csproj @@ -0,0 +1,48 @@ + + + + netstandard2.0;net8.0;net9.0;net10.0 + Microsoft.Windows.ProjFS + ProjectedFSLib.Managed + true + true + + + Microsoft.Windows.ProjFS + $(ProjFSManagedVersion) + Microsoft + Pure C# P/Invoke wrapper for the Windows Projected File System (ProjFS) API. +Drop-in replacement for the C++/CLI ProjectedFSLib.Managed.dll that supports NativeAOT, +trimming, and cross-compilation without requiring the C++ build toolchain. + +Uses LibraryImport source generators on .NET 8+ for improved AOT performance, +with DllImport fallback on .NET Standard 2.0 for broad compatibility. + +Requires Windows 10 version 1809+ with the ProjFS optional feature enabled. + MIT + ProjFS;ProjectedFileSystem;VirtualFileSystem;Windows;PInvoke;NativeAOT + https://github.com/microsoft/ProjFS-Managed-API + https://github.com/microsoft/ProjFS-Managed-API + git + README.md + + + true + true + true + snupkg + + + + + + + + + + + + + + + diff --git a/ProjectedFSLib.Managed/VirtualizationInstance.cs b/ProjectedFSLib.Managed/VirtualizationInstance.cs new file mode 100644 index 0000000..0a47b31 --- /dev/null +++ b/ProjectedFSLib.Managed/VirtualizationInstance.cs @@ -0,0 +1,787 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using static Microsoft.Windows.ProjFS.ProjFSNative; + +namespace Microsoft.Windows.ProjFS +{ + /// + /// Pure C# P/Invoke implementation of the ProjFS VirtualizationInstance, + /// replacing the C++/CLI mixed-mode ProjectedFSLib.Managed.dll for NativeAOT compatibility. + /// + public class VirtualizationInstance : IVirtualizationInstance + { + public const int PlaceholderIdLength = ProjFSNative.PlaceholderIdLength; + + /// Returns the maximum allowed length of a placeholder's contentID or provider ID. + int IVirtualizationInstance.PlaceholderIdLength => PlaceholderIdLength; + + private readonly string _rootPath; + private readonly uint _poolThreadCount; + private readonly uint _concurrentThreadCount; + private readonly bool _enableNegativePathCache; + private readonly List _notificationMappings; + + private IntPtr _context; // PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT + private GCHandle _selfHandle; + private Guid _instanceId; + private IRequiredCallbacks _requiredCallbacks; + private GCHandle _notificationMappingsHandle; + private IntPtr[] _notificationRootStrings; + + // Keep delegates alive to prevent GC while native code holds function pointers + private StartDirectoryEnumerationDelegate _startDirEnumDelegate; + private EndDirectoryEnumerationDelegate _endDirEnumDelegate; + private GetDirectoryEnumerationDelegate _getDirEnumDelegate; + private GetPlaceholderInfoDelegate _getPlaceholderInfoDelegate; + private GetFileDataDelegate _getFileDataDelegate; + private QueryFileNameDelegate _queryFileNameDelegate; + private NotificationDelegate _notificationDelegate; + private CancelCommandDelegate _cancelCommandDelegate; + + public VirtualizationInstance( + string virtualizationRootPath, + uint poolThreadCount, + uint concurrentThreadCount, + bool enableNegativePathCache, + IReadOnlyCollection notificationMappings) + { + _rootPath = virtualizationRootPath ?? throw new ArgumentNullException(nameof(virtualizationRootPath)); + _poolThreadCount = poolThreadCount; + _concurrentThreadCount = concurrentThreadCount; + _enableNegativePathCache = enableNegativePathCache; + _notificationMappings = new List(notificationMappings ?? Array.Empty()); + + // Match C++/CLI behavior: create the directory and mark as virtualization root + bool markAsRoot = false; + var dirInfo = new DirectoryInfo(_rootPath); + if (!dirInfo.Exists) + { + _instanceId = Guid.NewGuid(); + dirInfo.Create(); + markAsRoot = true; + } + else + { + // Check if the directory already has a ProjFS reparse point. + // If not, we need to mark it as a root. + // Use PrjGetOnDiskFileState to detect if it's already a ProjFS placeholder/root. + int hr = ProjFSNative.PrjGetOnDiskFileState(_rootPath, out uint fileState); + if (hr < 0 || fileState == 0) + { + // Not a ProjFS virtualization root yet — need to mark it + _instanceId = Guid.NewGuid(); + markAsRoot = true; + } + else + { + // Already marked. Get the instance ID via PrjGetVirtualizationInstanceInfo + // after StartVirtualizing. For now, generate a new one. + _instanceId = Guid.NewGuid(); + } + } + + if (markAsRoot) + { + HResult markResult = MarkDirectoryAsVirtualizationRoot(_rootPath, _instanceId); + if (markResult != HResult.Ok) + { + int errorCode = unchecked((int)markResult) & 0xFFFF; + throw new System.ComponentModel.Win32Exception(errorCode, + $"Failed to mark directory {_rootPath} as virtualization root. HRESULT: 0x{unchecked((uint)markResult):X8}"); + } + } + } + + public CancelCommandCallback OnCancelCommand { get; set; } + public NotifyFileOpenedCallback OnNotifyFileOpened { get; set; } + public NotifyNewFileCreatedCallback OnNotifyNewFileCreated { get; set; } + public NotifyFileOverwrittenCallback OnNotifyFileOverwritten { get; set; } + public NotifyFileHandleClosedNoModificationCallback OnNotifyFileHandleClosedNoModification { get; set; } + public NotifyFileHandleClosedFileModifiedOrDeletedCallback OnNotifyFileHandleClosedFileModifiedOrDeleted { get; set; } + public NotifyFilePreConvertToFullCallback OnNotifyFilePreConvertToFull { get; set; } + public NotifyFileRenamedCallback OnNotifyFileRenamed { get; set; } + public NotifyHardlinkCreatedCallback OnNotifyHardlinkCreated { get; set; } + public NotifyPreDeleteCallback OnNotifyPreDelete { get; set; } + public NotifyPreRenameCallback OnNotifyPreRename { get; set; } + public NotifyPreCreateHardlinkCallback OnNotifyPreCreateHardlink { get; set; } + public QueryFileNameCallback OnQueryFileName { get; set; } + + public Guid VirtualizationInstanceId => _instanceId; + public IRequiredCallbacks RequiredCallbacks => _requiredCallbacks; + + /// + /// Marks the specified directory as a virtualization root. + /// + public static HResult MarkDirectoryAsVirtualizationRoot(string rootPath, Guid virtualizationInstanceGuid) + { + int hr = ProjFSNative.PrjMarkDirectoryAsVirtualizationRoot( + rootPath, + null, + IntPtr.Zero, + ref virtualizationInstanceGuid); + return (HResult)hr; + } + + public unsafe HResult StartVirtualizing(IRequiredCallbacks requiredCallbacks) + { + _requiredCallbacks = requiredCallbacks ?? throw new ArgumentNullException(nameof(requiredCallbacks)); + + _selfHandle = GCHandle.Alloc(this); + + // Create managed delegates for native callbacks (prevents GC) + _startDirEnumDelegate = NativeStartDirectoryEnumeration; + _endDirEnumDelegate = NativeEndDirectoryEnumeration; + _getDirEnumDelegate = NativeGetDirectoryEnumeration; + _getPlaceholderInfoDelegate = NativeGetPlaceholderInfo; + _getFileDataDelegate = NativeGetFileData; + _queryFileNameDelegate = NativeQueryFileName; + _notificationDelegate = NativeNotification; + _cancelCommandDelegate = NativeCancelCommand; + + var callbacks = new PRJ_CALLBACKS + { + StartDirectoryEnumerationCallback = Marshal.GetFunctionPointerForDelegate(_startDirEnumDelegate), + EndDirectoryEnumerationCallback = Marshal.GetFunctionPointerForDelegate(_endDirEnumDelegate), + GetDirectoryEnumerationCallback = Marshal.GetFunctionPointerForDelegate(_getDirEnumDelegate), + GetPlaceholderInfoCallback = Marshal.GetFunctionPointerForDelegate(_getPlaceholderInfoDelegate), + GetFileDataCallback = Marshal.GetFunctionPointerForDelegate(_getFileDataDelegate), + QueryFileNameCallback = Marshal.GetFunctionPointerForDelegate(_queryFileNameDelegate), + NotificationCallback = Marshal.GetFunctionPointerForDelegate(_notificationDelegate), + CancelCommandCallback = Marshal.GetFunctionPointerForDelegate(_cancelCommandDelegate), + }; + + // Set up notification mappings + var nativeMappings = new PRJ_NOTIFICATION_MAPPING_NATIVE[_notificationMappings.Count]; + var allocatedStrings = new IntPtr[_notificationMappings.Count]; + + for (int i = 0; i < _notificationMappings.Count; i++) + { + string root = _notificationMappings[i].NotificationRoot ?? string.Empty; + allocatedStrings[i] = Marshal.StringToHGlobalUni(root); + nativeMappings[i] = new PRJ_NOTIFICATION_MAPPING_NATIVE + { + NotificationBitMask = (uint)_notificationMappings[i].NotificationMask, + NotificationRoot = allocatedStrings[i], + }; + } + + var options = new PRJ_STARTVIRTUALIZING_OPTIONS + { + Flags = _enableNegativePathCache ? PRJ_FLAG_USE_NEGATIVE_PATH_CACHE : PRJ_FLAG_NONE, + PoolThreadCount = _poolThreadCount, + ConcurrentThreadCount = _concurrentThreadCount, + NotificationMappingsCount = (uint)nativeMappings.Length, + }; + + GCHandle mappingsHandle = default; + try + { + if (nativeMappings.Length > 0) + { + mappingsHandle = GCHandle.Alloc(nativeMappings, GCHandleType.Pinned); + options.NotificationMappings = mappingsHandle.AddrOfPinnedObject(); + } + + int hr = ProjFSNative.PrjStartVirtualizing( + _rootPath, + ref callbacks, + GCHandle.ToIntPtr(_selfHandle), + ref options, + out _context); + + if (hr < 0) + { + _selfHandle.Free(); + } + + return (HResult)hr; + } + finally + { + // Do NOT free mappingsHandle or allocatedStrings here! + // ProjFS may cache the notification mapping pointers. + // Store them for cleanup in StopVirtualizing. + if (mappingsHandle.IsAllocated) + { + _notificationMappingsHandle = mappingsHandle; + } + _notificationRootStrings = allocatedStrings; + } + // NOTE: Do NOT free allocatedStrings here — ProjFS may cache notification + // mapping pointers. They are freed in StopVirtualizing. + } + public void StopVirtualizing() + { + if (_context != IntPtr.Zero) + { + ProjFSNative.PrjStopVirtualizing(_context); + _context = IntPtr.Zero; + } + + if (_selfHandle.IsAllocated) + { + _selfHandle.Free(); + } + } + + public HResult ClearNegativePathCache(out uint totalEntryNumber) + { + int hr = ProjFSNative.PrjClearNegativePathCache(_context, out totalEntryNumber); + return (HResult)hr; + } + + public HResult DeleteFile(string relativePath, UpdateType updateFlags, out UpdateFailureCause failureReason) + { + int hr = ProjFSNative.PrjDeleteFile(_context, relativePath, (uint)updateFlags, out uint cause); + failureReason = (UpdateFailureCause)cause; + return (HResult)hr; + } + + public unsafe HResult WritePlaceholderInfo( + string relativePath, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + FileAttributes fileAttributes, + long endOfFile, + bool isDirectory, + byte[] contentId, + byte[] providerId) + { + var info = new PRJ_PLACEHOLDER_INFO(); + info.FileBasicInfo.IsDirectory = isDirectory ? (byte)1 : (byte)0; + info.FileBasicInfo.FileSize = endOfFile; + info.FileBasicInfo.CreationTime = creationTime.ToFileTime(); + info.FileBasicInfo.LastAccessTime = lastAccessTime.ToFileTime(); + info.FileBasicInfo.LastWriteTime = lastWriteTime.ToFileTime(); + info.FileBasicInfo.ChangeTime = changeTime.ToFileTime(); + info.FileBasicInfo.FileAttributes = (uint)fileAttributes; + + CopyIdToVersionInfo(contentId, providerId, ref info.VersionInfo); + + int hr = ProjFSNative.PrjWritePlaceholderInfo( + _context, + relativePath, + ref info, + (uint)Marshal.SizeOf()); + + return (HResult)hr; + } + + /// + /// Sends file or directory metadata to ProjFS, with optional symlink extended info. + /// Requires Windows 10 version 2004 or later. + /// + /// + /// + /// NTFS Required: Symlink placeholders are created via the NTFS atomic create ECP. + /// This method will return or an + /// ERROR_NOT_SUPPORTED HRESULT (0x80070032) if the virtualization root is on a ReFS volume, + /// because ReFS does not support the atomic create ECP with IO_REPARSE_TAG_SYMLINK. + /// Non-symlink placeholder creation (when is null) + /// works on both NTFS and ReFS. + /// + /// + public unsafe HResult WritePlaceholderInfo2( + string relativePath, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + FileAttributes fileAttributes, + long endOfFile, + bool isDirectory, + string symlinkTargetOrNull, + byte[] contentId, + byte[] providerId) + { + var info = new PRJ_PLACEHOLDER_INFO(); + info.FileBasicInfo.IsDirectory = isDirectory ? (byte)1 : (byte)0; + info.FileBasicInfo.FileSize = isDirectory ? 0 : endOfFile; + info.FileBasicInfo.CreationTime = creationTime.ToFileTime(); + info.FileBasicInfo.LastAccessTime = lastAccessTime.ToFileTime(); + info.FileBasicInfo.LastWriteTime = lastWriteTime.ToFileTime(); + info.FileBasicInfo.ChangeTime = changeTime.ToFileTime(); + info.FileBasicInfo.FileAttributes = (uint)fileAttributes; + + CopyIdToVersionInfo(contentId, providerId, ref info.VersionInfo); + + if (!string.IsNullOrEmpty(symlinkTargetOrNull)) + { + int hr; + fixed (char* pTarget = symlinkTargetOrNull) + fixed (char* pPath = relativePath) + { + var extendedInfo = new PRJ_EXTENDED_INFO + { + InfoType = PRJ_EXT_INFO_TYPE_SYMLINK, + NextInfoOffset = 0, + SymlinkTargetName = (IntPtr)pTarget, + }; + + PRJ_EXTENDED_INFO* pExt = &extendedInfo; + + hr = ProjFSNative.PrjWritePlaceholderInfo2Raw( + _context, + (IntPtr)pPath, + (IntPtr)System.Runtime.CompilerServices.Unsafe.AsPointer(ref info), + (uint)sizeof(PRJ_PLACEHOLDER_INFO), + (IntPtr)pExt); + } + + return (HResult)hr; + } + else + { + int hr = ProjFSNative.PrjWritePlaceholderInfo( + _context, + relativePath, + ref info, + (uint)Marshal.SizeOf()); + return (HResult)hr; + } + } + + public unsafe HResult UpdateFileIfNeeded( + string relativePath, + DateTime creationTime, + DateTime lastAccessTime, + DateTime lastWriteTime, + DateTime changeTime, + FileAttributes fileAttributes, + long endOfFile, + byte[] contentId, + byte[] providerId, + UpdateType updateFlags, + out UpdateFailureCause failureReason) + { + var info = new PRJ_PLACEHOLDER_INFO(); + info.FileBasicInfo.IsDirectory = 0; + info.FileBasicInfo.FileSize = endOfFile; + info.FileBasicInfo.CreationTime = creationTime.ToFileTime(); + info.FileBasicInfo.LastAccessTime = lastAccessTime.ToFileTime(); + info.FileBasicInfo.LastWriteTime = lastWriteTime.ToFileTime(); + info.FileBasicInfo.ChangeTime = changeTime.ToFileTime(); + info.FileBasicInfo.FileAttributes = (uint)fileAttributes; + + CopyIdToVersionInfo(contentId, providerId, ref info.VersionInfo); + + int hr = ProjFSNative.PrjUpdateFileIfNeeded( + _context, + relativePath, + ref info, + (uint)Marshal.SizeOf(), + (uint)updateFlags, + out uint cause); + + failureReason = (UpdateFailureCause)cause; + return (HResult)hr; + } + + public HResult CompleteCommand(int commandId, HResult completionResult) + { + int hr = ProjFSNative.PrjCompleteCommand(_context, commandId, (int)completionResult, IntPtr.Zero); + return (HResult)hr; + } + + public HResult CompleteCommand(int commandId, NotificationType newNotificationMask) + { + var extParams = new PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS + { + CommandType = PRJ_COMPLETE_COMMAND_TYPE_NOTIFICATION, + NotificationMask = (uint)newNotificationMask, + }; + + int hr = ProjFSNative.PrjCompleteCommandWithNotification(_context, commandId, 0, ref extParams); + return (HResult)hr; + } + + public HResult CompleteCommand(int commandId, IDirectoryEnumerationResults results) + { + var dirResults = (DirectoryEnumerationResults)results; + var extParams = new PRJ_COMPLETE_COMMAND_EXTENDED_PARAMETERS + { + CommandType = PRJ_COMPLETE_COMMAND_TYPE_ENUMERATION, + DirEntryBufferHandle = dirResults.DirEntryBufferHandle, + }; + + int hr = ProjFSNative.PrjCompleteCommandWithNotification(_context, commandId, 0, ref extParams); + return (HResult)hr; + } + + public HResult CompleteCommand(int commandId) + { + int hr = ProjFSNative.PrjCompleteCommand(_context, commandId, 0, IntPtr.Zero); + return (HResult)hr; + } + + public IWriteBuffer CreateWriteBuffer(uint desiredBufferSize) + { + return new WriteBuffer(_context, desiredBufferSize); + } + + public IWriteBuffer CreateWriteBuffer(ulong byteOffset, uint length, out ulong alignedByteOffset, out uint alignedLength) + { + // Get the sector size from PrjGetVirtualizationInstanceInfo so we can + // compute aligned values for byteOffset and length. + var instanceInfo = new PRJ_VIRTUALIZATION_INSTANCE_INFO(); + int hr = ProjFSNative.PrjGetVirtualizationInstanceInfo(_context, ref instanceInfo); + if (hr < 0) + { + throw new System.ComponentModel.Win32Exception(hr, + $"Failed to retrieve virtualization instance info for directory {_rootPath}."); + } + + uint bytesPerSector = instanceInfo.WriteAlignment; + + // alignedByteOffset is byteOffset, rounded down to the nearest bytesPerSector boundary. + alignedByteOffset = byteOffset & ~((ulong)bytesPerSector - 1); + + // alignedLength is the end offset of the requested range, rounded up to the nearest + // bytesPerSector boundary, minus the aligned start offset. + ulong rangeEndOffset = byteOffset + (ulong)length; + ulong alignedRangeEndOffset = (rangeEndOffset + ((ulong)bytesPerSector - 1)) & ~((ulong)bytesPerSector - 1); + alignedLength = (uint)(alignedRangeEndOffset - alignedByteOffset); + + // Create a buffer of the aligned length. + return CreateWriteBuffer(alignedLength); + } + + public HResult WriteFileData(Guid dataStreamId, IWriteBuffer buffer, ulong byteOffset, uint length) + { + int hr = ProjFSNative.PrjWriteFileData(_context, ref dataStreamId, buffer.Pointer, byteOffset, length); + return (HResult)hr; + } + + public unsafe HResult MarkDirectoryAsPlaceholder(string targetDirectoryPath, byte[] contentId, byte[] providerId) + { + var versionInfo = new PRJ_PLACEHOLDER_VERSION_INFO(); + CopyIdToVersionInfo(contentId, providerId, ref versionInfo); + + int hr = ProjFSNative.PrjMarkDirectoryAsPlaceholder( + _rootPath, + targetDirectoryPath, + ref versionInfo, + ref _instanceId); + + return (HResult)hr; + } + + // =========================================================== + // Native callback implementations + // =========================================================== + + private static VirtualizationInstance GetInstance(IntPtr instanceContext) + { + return (VirtualizationInstance)GCHandle.FromIntPtr(instanceContext).Target; + } + + private static unsafe int NativeStartDirectoryEnumeration(PRJ_CALLBACK_DATA* pData, Guid* pEnumId) + { + try + { + var inst = GetInstance(pData->InstanceContext); + string virtualPath = Marshal.PtrToStringUni(pData->FilePathName); + string processName = Marshal.PtrToStringUni(pData->TriggeringProcessImageFileName); + return (int)inst._requiredCallbacks.StartDirectoryEnumerationCallback( + pData->CommandId, *pEnumId, virtualPath, pData->TriggeringProcessId, processName); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine($"[ProjFS] StartDirEnum EXCEPTION: {ex}"); + return (int)HResult.InternalError; + } + } + + private static unsafe int NativeEndDirectoryEnumeration(PRJ_CALLBACK_DATA* pData, Guid* pEnumId) + { + try + { + var inst = GetInstance(pData->InstanceContext); + return (int)inst._requiredCallbacks.EndDirectoryEnumerationCallback(*pEnumId); + } + catch + { + return (int)HResult.InternalError; + } + } + + private static unsafe int NativeGetDirectoryEnumeration(PRJ_CALLBACK_DATA* pData, Guid* pEnumId, IntPtr searchExpression, IntPtr dirEntryBufferHandle) + { + try + { + var inst = GetInstance(pData->InstanceContext); + string filterFileName = searchExpression != IntPtr.Zero ? Marshal.PtrToStringUni(searchExpression) : null; + bool restartScan = (pData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN) != 0; + var results = new DirectoryEnumerationResults(dirEntryBufferHandle); + return (int)inst._requiredCallbacks.GetDirectoryEnumerationCallback( + pData->CommandId, *pEnumId, filterFileName, restartScan, results); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine($"[ProjFS] GetDirEnum EXCEPTION: {ex}"); + return (int)HResult.InternalError; + } + } + + private static unsafe int NativeGetPlaceholderInfo(PRJ_CALLBACK_DATA* pData) + { + try + { + var inst = GetInstance(pData->InstanceContext); + string virtualPath = Marshal.PtrToStringUni(pData->FilePathName); + string processName = Marshal.PtrToStringUni(pData->TriggeringProcessImageFileName); + return (int)inst._requiredCallbacks.GetPlaceholderInfoCallback( + pData->CommandId, virtualPath, pData->TriggeringProcessId, processName); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine($"[ProjFS] GetPlaceholderInfo EXCEPTION: {ex}"); + return (int)HResult.InternalError; + } + } + + private static unsafe int NativeGetFileData(PRJ_CALLBACK_DATA* pData, ulong byteOffset, uint length) + { + try + { + var inst = GetInstance(pData->InstanceContext); + string virtualPath = Marshal.PtrToStringUni(pData->FilePathName); + string processName = Marshal.PtrToStringUni(pData->TriggeringProcessImageFileName); + + byte[] contentId = null; + byte[] providerId = null; + if (pData->VersionInfo != IntPtr.Zero) + { + var versionInfo = (PRJ_PLACEHOLDER_VERSION_INFO*)pData->VersionInfo; + contentId = new byte[PlaceholderIdLength]; + providerId = new byte[PlaceholderIdLength]; + fixed (byte* pContent = contentId, pProvider = providerId) + { + Buffer.MemoryCopy(versionInfo->ContentID, pContent, PlaceholderIdLength, PlaceholderIdLength); + Buffer.MemoryCopy(versionInfo->ProviderID, pProvider, PlaceholderIdLength, PlaceholderIdLength); + } + } + + return (int)inst._requiredCallbacks.GetFileDataCallback( + pData->CommandId, virtualPath, byteOffset, length, + pData->DataStreamId, contentId, providerId, + pData->TriggeringProcessId, processName); + } + catch + { + return (int)HResult.InternalError; + } + } + + private static unsafe int NativeQueryFileName(PRJ_CALLBACK_DATA* pData) + { + try + { + var inst = GetInstance(pData->InstanceContext); + if (inst.OnQueryFileName != null) + { + string virtualPath = Marshal.PtrToStringUni(pData->FilePathName); + return (int)inst.OnQueryFileName(virtualPath); + } + + return (int)HResult.Ok; + } + catch + { + return (int)HResult.InternalError; + } + } + + private static unsafe int NativeNotification(PRJ_CALLBACK_DATA* pData, byte isDirectory, int notification, IntPtr destinationFileName, IntPtr operationParameters) + { + try + { + var inst = GetInstance(pData->InstanceContext); + string virtualPath = Marshal.PtrToStringUni(pData->FilePathName); + string destPath = destinationFileName != IntPtr.Zero ? Marshal.PtrToStringUni(destinationFileName) : null; + bool isDir = isDirectory != 0; + uint processId = pData->TriggeringProcessId; + string processName = Marshal.PtrToStringUni(pData->TriggeringProcessImageFileName); + + switch (notification) + { + case PRJ_NOTIFICATION_FILE_OPENED: + if (inst.OnNotifyFileOpened != null) + { + bool allow = inst.OnNotifyFileOpened(virtualPath, isDir, processId, processName, out NotificationType mask); + if (!allow) + { + return (int)HResult.AccessDenied; + } + + WriteNotificationMask(operationParameters, (uint)mask); + } + + break; + + case PRJ_NOTIFICATION_NEW_FILE_CREATED: + if (inst.OnNotifyNewFileCreated != null) + { + inst.OnNotifyNewFileCreated(virtualPath, isDir, processId, processName, out NotificationType mask); + WriteNotificationMask(operationParameters, (uint)mask); + } + + break; + + case PRJ_NOTIFICATION_FILE_OVERWRITTEN: + if (inst.OnNotifyFileOverwritten != null) + { + inst.OnNotifyFileOverwritten(virtualPath, isDir, processId, processName, out NotificationType mask); + WriteNotificationMask(operationParameters, (uint)mask); + } + + break; + + case PRJ_NOTIFICATION_PRE_DELETE: + if (inst.OnNotifyPreDelete != null) + { + bool allow = inst.OnNotifyPreDelete(virtualPath, isDir, processId, processName); + if (!allow) + { + return (int)HResult.AccessDenied; + } + } + + break; + + case PRJ_NOTIFICATION_PRE_RENAME: + if (inst.OnNotifyPreRename != null) + { + bool allow = inst.OnNotifyPreRename(virtualPath, destPath, processId, processName); + if (!allow) + { + return (int)HResult.AccessDenied; + } + } + + break; + + case PRJ_NOTIFICATION_PRE_SET_HARDLINK: + if (inst.OnNotifyPreCreateHardlink != null) + { + bool allow = inst.OnNotifyPreCreateHardlink(virtualPath, destPath, processId, processName); + if (!allow) + { + return (int)HResult.AccessDenied; + } + } + + break; + + case PRJ_NOTIFICATION_FILE_RENAMED: + if (inst.OnNotifyFileRenamed != null) + { + inst.OnNotifyFileRenamed(virtualPath, destPath, isDir, processId, processName, out NotificationType mask); + WriteNotificationMask(operationParameters, (uint)mask); + } + + break; + + case PRJ_NOTIFICATION_HARDLINK_CREATED: + inst.OnNotifyHardlinkCreated?.Invoke(virtualPath, destPath, processId, processName); + break; + + case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION: + inst.OnNotifyFileHandleClosedNoModification?.Invoke(virtualPath, isDir, processId, processName); + break; + + case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_MODIFIED: + inst.OnNotifyFileHandleClosedFileModifiedOrDeleted?.Invoke( + virtualPath, isDir, isFileModified: true, isFileDeleted: false, processId, processName); + break; + + case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED: + { + bool isFileModified = false; + if (operationParameters != IntPtr.Zero) + { + // PRJ_NOTIFICATION_PARAMETERS.FileDeletedOnHandleClose.IsFileModified + isFileModified = Marshal.ReadByte(operationParameters) != 0; + } + + inst.OnNotifyFileHandleClosedFileModifiedOrDeleted?.Invoke( + virtualPath, isDir, isFileModified, isFileDeleted: true, processId, processName); + } + + break; + + case PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL: + if (inst.OnNotifyFilePreConvertToFull != null) + { + bool allow = inst.OnNotifyFilePreConvertToFull(virtualPath, processId, processName); + if (!allow) + { + return (int)HResult.AccessDenied; + } + } + + break; + } + + return (int)HResult.Ok; + } + catch + { + return (int)HResult.InternalError; + } + } + + private static unsafe void NativeCancelCommand(PRJ_CALLBACK_DATA* pData) + { + try + { + var inst = GetInstance(pData->InstanceContext); + inst.OnCancelCommand?.Invoke(pData->CommandId); + } + catch + { + // CancelCommand is void; nothing to return. + } + } + + // =========================================================== + // Helper methods + // =========================================================== + + private static unsafe void CopyIdToVersionInfo(byte[] contentId, byte[] providerId, ref PRJ_PLACEHOLDER_VERSION_INFO versionInfo) + { + if (contentId != null) + { + int len = Math.Min(contentId.Length, PlaceholderIdLength); + fixed (byte* pDst = versionInfo.ContentID, pSrc = contentId) + { + Buffer.MemoryCopy(pSrc, pDst, PlaceholderIdLength, len); + } + } + + if (providerId != null) + { + int len = Math.Min(providerId.Length, PlaceholderIdLength); + fixed (byte* pDst = versionInfo.ProviderID, pSrc = providerId) + { + Buffer.MemoryCopy(pSrc, pDst, PlaceholderIdLength, len); + } + } + } + + private static void WriteNotificationMask(IntPtr operationParameters, uint mask) + { + // PRJ_NOTIFICATION_PARAMETERS is a union; for post-create/overwrite/rename, + // the first field is NotificationMask (PRJ_NOTIFY_TYPES, 4 bytes) + if (operationParameters != IntPtr.Zero) + { + Marshal.WriteInt32(operationParameters, unchecked((int)mask)); + } + } + } +} diff --git a/ProjectedFSLib.Managed/WriteBuffer.cs b/ProjectedFSLib.Managed/WriteBuffer.cs new file mode 100644 index 0000000..e24d6c1 --- /dev/null +++ b/ProjectedFSLib.Managed/WriteBuffer.cs @@ -0,0 +1,69 @@ +#nullable disable +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Windows.ProjFS +{ + /// + /// Pure C# P/Invoke implementation of IWriteBuffer, using PrjAllocateAlignedBuffer/PrjFreeAlignedBuffer. + /// Replaces the C++/CLI WriteBuffer from ProjectedFSLib.Managed.dll for NativeAOT compatibility. + /// + public class WriteBuffer : IWriteBuffer + { + private IntPtr _buffer; + private bool _disposed; + + internal unsafe WriteBuffer(IntPtr virtualizationContext, uint desiredBufferSize) + { + _buffer = ProjFSNative.PrjAllocateAlignedBuffer(virtualizationContext, new UIntPtr(desiredBufferSize)); + if (_buffer == IntPtr.Zero) + { + throw new OutOfMemoryException("PrjAllocateAlignedBuffer returned null"); + } + + Length = desiredBufferSize; + byte* pBuf = (byte*)_buffer.ToPointer(); + Stream = new UnmanagedMemoryStream(pBuf, desiredBufferSize, desiredBufferSize, FileAccess.Write); + } + + ~WriteBuffer() + { + Dispose(false); + } + + public IntPtr Pointer => _buffer; + + public UnmanagedMemoryStream Stream { get; private set; } + + public long Length { get; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + Stream?.Dispose(); + Stream = null; + } + + if (_buffer != IntPtr.Zero) + { + ProjFSNative.PrjFreeAlignedBuffer(_buffer); + _buffer = IntPtr.Zero; + } + + _disposed = true; + } + } +} diff --git a/README.md b/README.md index 56e91dd..2f24f5c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ |:--:|:--:| |**main**|[![Build status](https://dev.azure.com/projfs/ci/_apis/build/status/PR%20-%20Build%20and%20Functional%20Test%20-%202022?branchName=main)](https://dev.azure.com/projfs/ci/_build/latest?definitionId=7)| +| | | +|---|---| +| **Package** | `Microsoft.Windows.ProjFS` | +| **Version** | 2.0.0 | +| **Targets** | netstandard2.0, net8.0, net9.0, net10.0 | +| **License** | MIT | ## About ProjFS @@ -19,65 +25,130 @@ Conceptual documentation for ProjFS along with documentation of its Win32 API is ## Enabling ProjFS -ProjFS enablement is **required** for this library to work correctly. ProjFS ships as an [optional component](https://docs.microsoft.com/en-us/windows/desktop/projfs/enabling-windows-projected-file-system) starting in Windows 10 version 1809. +ProjFS enablement is **required** for this library to work correctly. ProjFS ships as an +[optional component](https://docs.microsoft.com/en-us/windows/desktop/projfs/enabling-windows-projected-file-system) +starting in Windows 10 version 1809. + +To enable ProjFS, run the following in an elevated PowerShell prompt: + +```powershell +Enable-WindowsOptionalFeature -Online -FeatureName Client-ProjFS -NoRestart +``` ## About the ProjFS Managed API -The Windows SDK contains a native C API for ProjFS. The ProjFS Managed API provides a wrapper around -the native API so that developers can write ProjFS providers using managed code. +The Windows SDK contains a native C API for ProjFS. The ProjFS Managed API provides a pure C# +P/Invoke wrapper around the native API so that developers can write ProjFS providers using managed code. + +This is a complete rewrite of the original C++/CLI wrapper. Key improvements: + +- **No C++ toolchain required** — builds with `dotnet build`, no Visual Studio C++ workload needed +- **NativeAOT compatible** — fully supports ahead-of-time compilation and trimming +- **Cross-compilation friendly** — can be built on any machine with the .NET SDK +- **LibraryImport on .NET 7+** — uses source-generated P/Invoke marshalling for better AOT perf +- **DllImport fallback** — netstandard2.0 target retains traditional P/Invoke for broad compatibility +- **Same API surface** — drop-in replacement using the same `Microsoft.Windows.ProjFS` namespace +- **NuGet ready** — publishes to nuget.org as `Microsoft.Windows.ProjFS` -Note that to use this library on a computer that does not have Visual Studio installed, you must install the [Visual C++ redistributable](https://visualstudio.microsoft.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2019). +### Prerequisites + +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) (required for net10.0 TFM; also builds net8.0/net9.0/netstandard2.0) +- Windows 10 version 1809 or later with ProjFS enabled ## Solution Layout ### ProjectedFSLib.Managed project -This project contains the code that builds the API wrapper, ProjectedFSLib.Managed.dll. It is in the -ProjectedFSLib.Managed.API directory. +This project contains the pure C# P/Invoke implementation of the ProjFS managed wrapper, +producing `ProjectedFSLib.Managed.dll`. It targets netstandard2.0, net8.0, and net10.0. + +The netstandard2.0 target allows use from .NET Framework 4.8 and .NET Core 3.1+ projects, +providing a migration path from the original C++/CLI package without requiring a TFM upgrade. + +### P/Invoke Strategy + +The library uses two P/Invoke strategies depending on the target framework: + +| Target | Strategy | Benefit | +|--------|----------|---------| +| net8.0, net9.0, net10.0 | `LibraryImport` (source generator) | AOT-compatible, no runtime marshalling overhead | +| netstandard2.0 | `DllImport` (traditional) | Broad compatibility (.NET Framework 4.8, .NET Core 3.1+) | + +The declarations live in two partial class files: +- `ProjFSNative.cs` — shared structs/constants + DllImport fallback +- `ProjFSNative.LibraryImport.cs` — LibraryImport declarations (compiled only on .NET 7+) ### SimpleProviderManaged project -This project builds a simple ProjFS provider, SimpleProviderManaged.exe, that uses the managed API. +This project builds a simple ProjFS provider, `SimpleProviderManaged.exe`, that uses the managed API. It projects the contents of one directory (the "source") into another one (the "virtualization root"). ### ProjectedFSLib.Managed.Test project -This project builds an NUnit test, ProjectedFSLib.Managed.Test.exe, that uses the SimpleProviderManaged -provider to exercise the API wrapper. The managed API is a fairly thin wrapper around the native API, -and the native API has its own much more comprehensive tests that are routinely executed at Microsoft -in the normal course of OS development. So this managed test is just a basic functional test to get -coverage of the managed wrapper API surface. - -## Building the ProjFS Managed API - -* Install [Visual Studio 2022 Community Edition](https://www.visualstudio.com/downloads/). - * Include the following workloads: - * **.NET desktop development** - * **Desktop development with C++** - * Ensure the following individual components are installed: - * **C++/CLI support** - * **Windows 10 SDK (10.0.19041.0)** -* Create a folder to clone into, e.g. `C:\Repos\ProjFS-Managed` -* Clone this repo into a subfolder named `src`, e.g. `C:\Repos\ProjFS-Managed\src` -* Run `src\scripts\BuildProjFS-Managed.bat` - * You can also build in Visual Studio by opening `src\ProjectedFSLib.Managed.sln` and building. - -The build outputs will be placed under a `BuildOutput` subfolder, e.g. `C:\Repos\ProjFS-Managed\BuildOutput`. - -**Note:** The Windows Projected File System optional component must be enabled in Windows before +This project builds an NUnit test, `ProjectedFSLib.Managed.Test.exe`, that uses the SimpleProviderManaged +provider to exercise the API wrapper. + +## Building + +```powershell +dotnet build ProjectedFSLib.Managed.slnx -c Release +``` + +Or use the build script: + +```powershell +.\scripts\BuildProjFS-Managed.bat Release +``` + +## Running Tests + +```powershell +dotnet test ProjectedFSLib.Managed.slnx -c Release +``` + +Or use the test script: + +```powershell +.\scripts\RunTests.bat Release +``` + +## NuGet Package + +### Installing + +```powershell +dotnet add package Microsoft.Windows.ProjFS +``` + +### Creating a Package Locally + +```powershell +.\scripts\Pack-NuGet.ps1 +``` + +This produces `.nupkg` and `.snupkg` files in `artifacts/packages/`. + +Official NuGet publishing is handled by the internal CI/CD pipeline. + +**Note:** The Windows Projected File System optional component must be enabled before you can run SimpleProviderManaged.exe or a provider of your own devising. Refer to -[this page](https://docs.microsoft.com/en-us/windows/desktop/projfs/enabling-windows-projected-file-system) -for instructions. +[Enabling ProjFS](#enabling-projfs) above for instructions. -### Dealing with BadImageFormatExceptions -The simplest cause for BadImageFormatExceptions is that you still need to [enable ProjFS](#enabling-projfs). +### Known Filesystem Limitations -For .Net Core specific consumers, this can also occur when the .NET Core loader attempts to find Ijwhost.dll from the .NET Core runtime. To force this to be deployed with your application under MSBuild, add the following property to each csproj file that is importing the Microsoft.Windows.ProjFS package: +**Symlink placeholders require NTFS.** ProjFS symlink support (`WritePlaceholderInfo2`, +`PrjFillDirEntryBuffer2` with `PRJ_EXT_INFO_TYPE_SYMLINK`) uses the NTFS atomic create +ECP internally. **ReFS does not support this**, and `PrjWritePlaceholderInfo2` will return +`HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)` (`0x80070032`) when the virtualization root is on +a ReFS volume. Non-symlink operations (regular placeholders, file hydration, directory +enumeration, notifications) work correctly on both NTFS and ReFS. - - True - +If you encounter `ERROR_NOT_SUPPORTED` from `WritePlaceholderInfo2`, verify the virtualization +root is on an NTFS volume: +```powershell +Get-Volume -DriveLetter D | Select-Object FileSystem # Should be "NTFS" +``` ## Contributing diff --git a/doc/ProjectedFSLib.Managed.xml b/doc/ProjectedFSLib.Managed.xml deleted file mode 100644 index a9a6fb0..0000000 --- a/doc/ProjectedFSLib.Managed.xml +++ /dev/null @@ -1,3068 +0,0 @@ - - - - "ProjectedFSLib.Managed" - - - - -Marks an existing directory as the provider's virtualization root. - - -A provider may wish to designate its virtualization root before it is ready or able to -instantiate the class. In that case it may use this -method to designate the root. The provider must generate a GUID to identify the virtualization -instance and pass it in . The - constructor will use that -value to identify the provider to ProjFS. - - -The full path to the directory to mark as the virtualization root. - - -A GUID generated by the provider. ProjFS uses this value to internally identify the provider. - - - - if the conversion succeeded. - - if is an empty string. - - if does not specify a directory. - - if is already a placeholder or some other kind of reparse point. - - if is an ancestor or descendant of an existing virtualization root. - - - - -Converts an existing directory to a hydrated directory placeholder. - - -Children of the directory are not affected. - - -The full path (i.e. not relative to the virtualization root) to the directory to convert -to a placeholder. - - - -A content identifier, generated by the provider. This value is used to distinguish between -different versions of the same file, for example different file contents and/or metadata -(e.g. timestamps) for the same file path. - - -This value must be at most bytes in size. Any data -beyond that length will be discarded. - - - - -Optional provider-specific data. The provider may use this value as its own unique identifier, -for example as a version number for the format of the value. - - -This value must be at most bytes in size. Any data -beyond that length will be discarded. - - - - - if the conversion succeeded. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string -or if it is not a directory path. - - if -is already a placeholder or some other kind of reparse point. - - - - -Creates a for use with . - - - - The object ensures that any alignment requirements of the - underlying storage device are met when writing data with the - method. - - - This overload allows a provider to get sector-aligned values for the start offset and - length of the write. The provider uses and - to copy the correct data out of its backing store - into the and transfer it when calling . - - - Note that unlike most methods on , this method - throws rather than return . This makes it convenient to use - in constructions like a using clause. - - - -Byte offset from the beginning of the file at which the provider wants to write data. - - -The number of bytes to write to the file. - - - , aligned to the sector size of the storage device. The -provider uses this value as the byteOffset parameter to . - - - , aligned to the sector size of the storage device. The -provider uses this value as the length parameter to . - - -A that provides the needed capacity. - - -An error occurred retrieving the sector size from ProjFS. - - -A buffer could not be allocated. - - - - -Creates a for use with . - - - - The object ensures that any alignment requirements of the - underlying storage device are met when writing data with the - method. - - - Note that unlike most methods on , this method - throws rather than return . This makes it convenient to use - in constructions like a using clause. - - - -The size in bytes of the buffer required to write the data. - - -A that provides at least -bytes of capacity. - - -A buffer could not be allocated. - - - - -Signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - - -Used when completing Microsoft.Windows.ProjFS.Notify* callbacks that have a -notificationMask parameter. Specifies a bitwise-OR of -values indicating the set of notifications the provider wishes to receive for this file. - - -If the provider sets this value to 0, it is equivalent to specifying -. - - - - - if completion succeeded. - - if does not specify a pended callback - or if is not a valid combination of - values. - - - - -Signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - -Used when completing . Receives -the results of the enumeration. - - - - if completion succeeded. - - if does not specify a pended callback or - if is not a valid . - - - - -Signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - -The final status of the operation. See the descriptions for the callback delegates for -appropriate return codes. - - - - if completion succeeded. - - if does not specify a pended callback. - - - - -Signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - - - if completion succeeded. - - if does not specify a pended callback. - - - - -Updates an item that has been cached on the local file system. - - - -This routine cannot be called on a virtual file or directory. - - -If the file or directory to be updated is in any state other than "placeholder", the provider -must specify an appropriate combination of values in the - parameter. This helps guard against accidental loss of -data, since upon successful return from this routine the item becomes a placeholder with -the updated metadata. Any metadata that had been changed since the placeholder was created, -or any file data it contained is discarded. - - -Note that the timestamps the provider specifies in the , -, , and -parameters may be any values the provider wishes. This allows the provider to preserve -the illusion of files and directories that already exist on the user's system even before they -are physically created on the user's disk. - - - -The path, relative to the virtualization root, to the file or directory to updated. - - -The time the file was created. - - -The time the file was last accessed. - - -The time the file was last written to. - - -The time the file was last changed. - - -The file attributes. - - -The size of the file in bytes. - - - -A content identifier, generated by the provider. ProjFS will pass this value back to the -provider when requesting file contents in the callback. -This allows the provider to distinguish between different versions of the same file, i.e. -different file contents and/or metadata for the same file path. - - -If this parameter specifies a content identifier that is the same as the content identifier -already on the file or directory, the call succeeds and no update takes place. Otherwise, -if the call succeeds then the value of this parameter replaces the existing content identifier -on the file or directory. - - -This value must be at most bytes in size. Any data -beyond that length will be discarded. - - - - -Optional provider-specific data. ProjFS will pass this value back to the provider -when requesting file contents in the callback. The -provider may use this value as its own unique identifier, for example as a version number -for the format of the value. - - -This value must be at most bytes in size. Any data -beyond that length will be discarded. - - - - -A combination of 0 or more values to control whether ProjFS -should allow the update given the state of the file or directory on disk. See the documentation -of for a description of each flag and what it will allow. - - -If the item is a dirty placeholder, full file, or tombstone, and the provider does not -specify the appropriate flag(s), this routine will fail to update the item. - - - -If this method fails with , this receives a - value that describes the reason the update failed. - - - - if the update succeeded. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string. - - if cannot - be found. It may be for a virtual file or directory. - - if contains - an intermediate component that cannot be found. The path may terminate beneath a - directory that has been replaced with a tombstone. - - if is a - directory and is not empty. - - if the input value of - does not allow the update given the state of the file or directory on disk. The value - of indicates the cause of the failure. - - - - -Sends file or directory metadata to ProjFS. - - - - The provider uses this method to create a placeholder on disk. It does this when ProjFS - calls the provider's implementation of , - or the provider may use this method to proactively lay down a placeholder. - - - Note that the timestamps the provider specifies in the , - , , and - parameters may be any values the provider wishes. This allows the provider to preserve - the illusion of files and directories that already exist on the user's system even before they - are physically created on the user's disk. - - - - - The path, relative to the virtualization root, of the file or directory. - - - If the provider is processing a call to , - then this must be a match to the relativePath value passed in that call. The - provider should use the method to determine whether - the two names match. - - - For example, if specifies - dir1\dir1\FILE.TXT in relativePath, and the provider’s backing store contains - a file called File.txt in the dir1\dir2 directory, and - returns 0 when comparing the names FILE.TXT and File.txt, then the provider - specifies dir1\dir2\File.txt as the value of this parameter. - - - -The time the file was created. - - -The time the file was last accessed. - - -The time the file was last written to. - - -The time the file was last changed. - - -The file attributes. - - -The size of the file in bytes. - - - true if is for a directory, false otherwise. - - - - A content identifier, generated by the provider. ProjFS will pass this value back to the - provider when requesting file contents in the callback. - This allows the provider to distinguish between different versions of the same file, i.e. - different file contents and/or metadata for the same file path. - - - This value must be at most bytes in size. Any data - beyond that length will be discarded. - - - - - Optional provider-specific data. ProjFS will pass this value back to the provider - when requesting file contents in the callback. The - provider may use this value as its own unique identifier, for example as a version number - for the format of the value. - - - This value must be at most bytes in size. Any data - beyond that length will be discarded. - - - - - if the placeholder information was successfully written. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string. - - - - -Enables a provider to delete a file or directory that has been cached on the local file system. - - - - If the item is still in the provider's store, deleting it from the local file system changes - it to a virtual item. - - - This routine will fail if called on a file or directory that is already virtual. - - - If the file or directory to be deleted is in any state other than "placeholder", the - provider must specify an appropriate combination of values - in the parameter. This helps guard against accidental - loss of data. If the provider did not specify a combination of - values in the parameter that would allow the delete - to happen, the method fails with . - - - If a directory contains only tombstones, it may be deleted using this method and - specifying in . - If the directory contains non-tombstone files, then this method will return . - - - -The path, relative to the virtualization root, to the file or directory to delete. - - - - A combination of 0 or more values to control whether ProjFS - should allow the delete given the state of the file or directory on disk. See the documentation - of for a description of each flag and what it will allow. - - - If the item is a dirty placeholder, full file, or tombstone, and the provider does not - specify the appropriate flag(s), this routine will fail to delete the placeholder. - - - -If this method fails with , this receives a - value that describes the reason the delete failed. - - - - if the delete succeeded. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string. - - if cannot - be found. It may be for a virtual file or directory. - - if contains - an intermediate component that cannot be found. The path may terminate beneath a - directory that has been replaced with a tombstone. - - if is a - directory and is not empty. - - if the input value of - does not allow the delete given the state of the file or directory on disk. The value - of indicates the cause of the failure. - - - - -Sends file contents to ProjFS. - - - - The provider uses this method to provide the data requested when ProjFS calls the provider's - implementation of . - - - The provider calls to create an instance of - to contain the data to be written. The ensures that any alignment - requirements of the underlying storage device are met. - - - -Identifier for the data stream to write to. The provider must use the value of - passed in its -callback. - - -A created using that contains -the data to write. - - -Byte offset from the beginning of the file at which to write the data. - - -The number of bytes to write to the file. - - - - if the data was successfully written. - - if a buffer could not be allocated to communicate with ProjFS. - - if is not specified, - is 0, or is greater than the - length of the file. - - if does not - correspond to a placeholder that is expecting data to be provided. - - - - - -Purges the virtualization instance's negative path cache, if it is active. - - - - If the negative path cache is active, then if the provider indicates that a file path does - not exist by returning from its implementation of - - then ProjFS will fail subsequent opens of that path without calling - again. - - - To resume receiving for - paths the provider has indicated do not exist, the provider must call this method. - - - -Returns the number of paths that were in the cache before it was purged. - - - - if the the cache was successfully purged. - - if a buffer could not be allocated to communicate with ProjFS. - - - - -Stops the virtualization instance, making it unavailable to service I/O or invoke callbacks -on the provider. - - -The virtualization instance is in an invalid state (it may already be stopped). - - - - -Starts the virtualization instance, making it available to service I/O and invoke callbacks -on the provider. - - - - If the provider has implemented any optional callback delegates, it must set their - implementations into the On... properties prior to calling this method. - - - On Windows 10 version 1803 this method attempts to determine the sector alignment - requirements of the underlying storage device and stores that information internally - in the instance. This information is required - by the method to ensure that it can return data in - the method when the original reader is using unbuffered - I/O. If this method cannot determine the sector alignment requirements of the - underlying storage device, it will throw a - exception. - - - On Windows 10 version 1809 and later versions the alignment requirements are determined - by the system. - - - - - The provider's implementation of the interface. - - - - - if the virtualization instance started successfully. - - if a buffer could not be allocated to communicate with ProjFS. - - if the virtualization root is an ancestor or descendant of an existing virtualization root. - - if the virtualization instance is already running. - - -The sector alignment requirements of the volume could not be determined. See the Remarks section. - - - - Returns the maximum allowed length of a placeholder's contentID or provider ID. - -See or for more information. - - - - Allows the provider to retrieve the virtualization instance GUID. - -A virtualization instance is identified with a GUID. If the provider did not generate -and store a GUID itself using the method, -then the VirtualizationInstance class generates one for it. Either way, the provider -can retrieve the GUID via this property. - - - - Retrieves the interface. - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file is about to be converted from a placeholder to a full file. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file handle has been closed on a modified file, or the file was deleted as a result -of closing the handle. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file handle has been closed and the file has not been modified. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a hard link has been created for a file. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file has been renamed. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a hard link is about to be created for a file. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file is about to be renamed. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file is about to be deleted. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file has been superseded or overwritten. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a new file has been created. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - The provider is not required to provide an implementation of this callback. -If it does not provide this callback, the provider will not receive notifications when -a file has been opened. - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - Stores the provider's implementation of . - - - -If the provider wishes to support asynchronous processing of callbacks (that is, if it -intends to return from any of its callbacks), then the provider -must set this property prior to calling . - - -If the provider does not wish to support asynchronous processing of callbacks, then it -is not required to provide an implementation of this callback. - - If the provider does implement this callback, then it must set this property prior to -calling . - - - - -The provider has already called . - - - - -Stores the provider's implementation of . - - - The provider must set this property prior to calling . - - - -Initializes an object that manages communication between a provider and ProjFS. - - - - If doesn't already exist, this constructor - will create it and mark it as the virtualization root. The constructor will generate - a GUID to serve as the virtualization instance ID. - - - If does exist, this constructor will check - for a ProjFS reparse point. If the reparse point does not exist, virtualizationRootPath - will be marked as the virtualization root. If it has a different reparse point then - the constructor will throw a for - ERROR_REPARSE_TAG_MISMATCH. - - - For providers that create their virtualization root separately from instantiating the - VirtualizationInstance class, the static method - is provided. - - - - The full path to the virtualization root directory. If this directory does not already - exist, it will be created. See the Remarks section for further details. - - - - The number of threads the provider will have available to process callbacks from ProjFS. - - - If the provider specifies 0, ProjFS will use a default value of 2 * . - - - - - The maximum number of threads the provider wants to run concurrently to process callbacks - from ProjFS. - - - If the provider specifies 0, ProjFS will use a default value equal to the number of - CPU cores in the system. - - - - - If true, specifies that the virtualization instance should maintain a "negative - path cache". If the negative path cache is active, then if the provider indicates - that a file path does not exist by returning from its - implementation of , then ProjFS will - fail subsequent opens of that path without calling - again. - - - To resume receiving for paths the provider has - indicated do not exist, the provider must call . - - - - - A collection of zero or more objects that describe the - notifications the provider wishes to receive for the virtualization root. - - - If the collection is empty, ProjFS will send the notifications , - , and - for all files and directories under the virtualization root. - - - -The native ProjFS library (ProjectedFSLib.dll) is not available. - - -An expected entry point cannot be found in ProjectedFSLib.dll. - - -An error occurred in setting up the virtualization root. - - - - -Provides methods and callbacks that allow a provider to interact with a virtualization instance. - - - -The provider creates one instance of this class for each virtualization root that it manages. -The provider uses this class's properties and methods to receive and respond to callbacks from -ProjFS for its virtualization instance, and to send commands that control the virtualization -instance's state. - - - - - -When overridden in a derived class, converts an existing directory to a hydrated directory -placeholder. - - -Children of the directory are not affected. - - -The full path (i.e. not relative to the virtualization root) to the directory to convert -to a placeholder. - - - -A content identifier, generated by the provider. This value is used to distinguish between -different versions of the same file, for example different file contents and/or metadata -(e.g. timestamps) for the same file path. - - - - -Optional provider-specific data. The provider may use this value as its own unique identifier, -for example as a version number for the format of the value. - - - - - if the conversion succeeded. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string -or if it is not a directory path. - - if -is already a placeholder or some other kind of reparse point. - - - - -When overridden in a derived class, creates a for use with . - - - - The object ensures that any alignment requirements of the - underlying storage device are met when writing data with the - method. - - - This overload allows a provider to get sector-aligned values for the start offset and - length of the write. The provider uses and - to copy the correct data out of its backing store - into the and transfer it when calling . - - - -Byte offset from the beginning of the file at which the provider wants to write data. - - -The number of bytes to write to the file. - - - , aligned to the sector size of the storage device. The -provider uses this value as the byteOffset parameter to . - - - , aligned to the sector size of the storage device. The -provider uses this value as the length parameter to . - - -A that provides the needed capacity. - - -An error occurred retrieving the sector size from ProjFS. - - -A buffer could not be allocated. - - - - -When overridden in a derived class, creates a for use with -. - - - - The object ensures that any alignment requirements of the - underlying storage device are met when writing data with the - method. - - - -The size in bytes of the buffer required to write the data. - - -A that provides at least -bytes of capacity. - - -A buffer could not be allocated. - - - - -When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - - -Used when completing Microsoft.Windows.ProjFS.Notify* callbacks that have a -notificationMask parameter. Specifies a bitwise-OR of -values indicating the set of notifications the provider wishes to receive for this file. - - -If the provider sets this value to 0, it is equivalent to specifying -. - - - - - if completion succeeded. - - if does not specify a pended callback - or if is not a valid combination of - values. - - - - -When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - -Used when completing . Receives -the results of the enumeration. - - - - if completion succeeded. - - if does not specify a pended callback or - if is not a valid . - - - - -When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - -The final status of the operation. See the descriptions for the callback delegates for -appropriate return codes. - - - - if completion succeeded. - - if does not specify a pended callback. - - - - -When overridden in a derived class, signals to ProjFS that the provider has completed processing a callback from which it -previously returned . - - -If the provider calls this method for the passed by the - callback it is not an error, however it is a no-op -because the I/O that caused the callback invocation identified by -has already ended. - - -A value that uniquely identifies the callback invocation to complete. This value must be -equal to the value of the parameter of the callback from -which the provider previously returned . - - - - if completion succeeded. - - if does not specify a pended callback. - - - - -When overridden in a derived class, updates an item that has been cached on the local -file system. - - - -This routine cannot be called on a virtual file or directory. - - -If the file or directory to be updated is in any state other than "placeholder", the provider -must specify an appropriate combination of values in the - parameter. This helps guard against accidental loss of -data, since upon successful return from this routine the item becomes a placeholder with -the updated metadata. Any metadata that had been changed since the placeholder was created, -or any file data it contained is discarded. - - -Note that the timestamps the provider specifies in the , -, , and -parameters may be any values the provider wishes. This allows the provider to preserve -the illusion of files and directories that already exist on the user's system even before they -are physically created on the user's disk. - - - -The path, relative to the virtualization root, to the file or directory to updated. - - -The time the file was created. - - -The time the file was last accessed. - - -The time the file was last written to. - - -The time the file was last changed. - - -The file attributes. - - -The size of the file in bytes. - - - -A content identifier, generated by the provider. ProjFS will pass this value back to the -provider when requesting file contents in the callback. -This allows the provider to distinguish between different versions of the same file, i.e. -different file contents and/or metadata for the same file path. - - -If this parameter specifies a content identifier that is the same as the content identifier -already on the file or directory, the call succeeds and no update takes place. Otherwise, -if the call succeeds then the value of this parameter replaces the existing content identifier -on the file or directory. - - - - -Optional provider-specific data. ProjFS will pass this value back to the provider -when requesting file contents in the callback. The -provider may use this value as its own unique identifier, for example as a version number -for the format of the value. - - - - -A combination of 0 or more values to control whether ProjFS -should allow the update given the state of the file or directory on disk. See the documentation -of for a description of each flag and what it will allow. - - -If the item is a dirty placeholder, full file, or tombstone, and the provider does not -specify the appropriate flag(s), this routine will fail to update the item. - - - -If this method fails with , this receives a - value that describes the reason the update failed. - - - - if the update succeeded. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string. - - if cannot - be found. It may be for a virtual file or directory. - - if contains - an intermediate component that cannot be found. The path may terminate beneath a - directory that has been replaced with a tombstone. - - if is a - directory and is not empty. - - if the input value of - does not allow the update given the state of the file or directory on disk. The value - of indicates the cause of the failure. - - - - -When overridden in a derived class, sends file or directory metadata to ProjFS. - - - - The provider uses this method to create a placeholder on disk. It does this when ProjFS - calls the provider's implementation of , - or the provider may use this method to proactively lay down a placeholder. - - - Note that the timestamps the provider specifies in the , - , , and - parameters may be any values the provider wishes. This allows the provider to preserve - the illusion of files and directories that already exist on the user's system even before they - are physically created on the user's disk. - - - - - The path, relative to the virtualization root, of the file or directory. - - - If the provider is processing a call to , - then this must be a match to the relativePath value passed in that call. The - provider should use the method to determine whether - the two names match. - - - For example, if specifies - dir1\dir1\FILE.TXT in relativePath, and the provider’s backing store contains - a file called File.txt in the dir1\dir2 directory, and - returns 0 when comparing the names FILE.TXT and File.txt, then the provider - specifies dir1\dir2\File.txt as the value of this parameter. - - - -The time the file was created. - - -The time the file was last accessed. - - -The time the file was last written to. - - -The time the file was last changed. - - -The file attributes. - - -The size of the file in bytes. - - - true if is for a directory, false otherwise. - - - - A content identifier, generated by the provider. ProjFS will pass this value back to the - provider when requesting file contents in the callback. - This allows the provider to distinguish between different versions of the same file, i.e. - different file contents and/or metadata for the same file path. - - - - - Optional provider-specific data. ProjFS will pass this value back to the provider - when requesting file contents in the callback. The - provider may use this value as its own unique identifier, for example as a version number - for the format of the value. - - - - - if the placeholder information was successfully written. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string. - - - - -When overridden in a derived class, enables a provider to delete a file or directory that -has been cached on the local file system. - - - - If the item is still in the provider's store, deleting it from the local file system changes - it to a virtual item. - - - This routine will fail if called on a file or directory that is already virtual. - - - If the file or directory to be deleted is in any state other than "placeholder", the - provider must specify an appropriate combination of values - in the parameter. This helps guard against accidental - loss of data. If the provider did not specify a combination of - values in the parameter that would allow the delete - to happen, the method fails with . - - - If a directory contains only tombstones, it may be deleted using this method and - specifying in . - If the directory contains non-tombstone files, then this method will return . - - - -The path, relative to the virtualization root, to the file or directory to delete. - - - - A combination of 0 or more values to control whether ProjFS - should allow the delete given the state of the file or directory on disk. See the documentation - of for a description of each flag and what it will allow. - - - If the item is a dirty placeholder, full file, or tombstone, and the provider does not - specify the appropriate flag(s), this routine will fail to delete the placeholder. - - - -If this method fails with , this receives a - value that describes the reason the delete failed. - - - - if the delete succeeded. - - if a buffer could not be allocated to communicate with ProjFS. - - if is an empty string. - - if cannot - be found. It may be for a virtual file or directory. - - if contains - an intermediate component that cannot be found. The path may terminate beneath a - directory that has been replaced with a tombstone. - - if is a - directory and is not empty. - - if the input value of - does not allow the delete given the state of the file or directory on disk. The value - of indicates the cause of the failure. - - - - -When overridden in a derived class, sends file contents to ProjFS. - - - - The provider uses this method to provide the data requested when ProjFS calls the provider's - implementation of . - - - The provider calls to create an instance of - to contain the data to be written. The ensures that any alignment - requirements of the underlying storage device are met. - - - -Identifier for the data stream to write to. The provider must use the value of - passed in its -callback. - - -A created using that contains -the data to write. - - -Byte offset from the beginning of the file at which to write the data. - - -The number of bytes to write to the file. - - - - if the data was successfully written. - - if a buffer could not be allocated to communicate with ProjFS. - - if is not specified, - is 0, or is greater than the - length of the file. - - if does not - correspond to a placeholder that is expecting data to be provided. - - - - - -When overridden in a derived class, purges the virtualization instance's negative path -cache, if it is active. - - - - If the negative path cache is active, then if the provider indicates that a file path does - not exist by returning from its implementation of - - then ProjFS will fail subsequent opens of that path without calling - again. - - - To resume receiving for - paths the provider has indicated do not exist, the provider must call this method. - - - -Returns the number of paths that were in the cache before it was purged. - - - - if the the cache was successfully purged. - - if a buffer could not be allocated to communicate with ProjFS. - - - - -When overridden in a derived class, stops the virtualization instance, making it unavailable -to service I/O or invoke callbacks on the provider. - - -The virtualization instance is in an invalid state (it may already be stopped). - - - - -When overridden in a derived class, starts a ProjFS virtualization instance, making it -available to service I/O and invoke callbacks on the provider. - - - - The provider's implementation of the interface. - - - - - if the virtualization instance started successfully. - - if a buffer could not be allocated to communicate with ProjFS. - - if the virtualization root is an ancestor or descendant of an existing virtualization root. - - if the virtualization instance is already running. - - -The sector alignment requirements of the volume could not be determined. See the Remarks section. - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - When overridden in a derived class, stores the provider's implementation of . - - - - -When overridden in a derived class, stores the provider's implementation of . - - - - - -Interface to allow for easier unit testing of a virtualization provider. - - -This class defines the interface implemented by the ProjFS.VirtualizationInstance class. -This interface class is provided for use by unit tests to mock up the interface to ProjFS. - - - - -A path to a directory, relative to the virtualization root. The virtualization root itself -must be specified as an empty string. - - -ProjFS will send to the provider the notifications specified in -for this directory and its descendants. - - -The notification root value is . or begins with .\. The notification root -must be specified relative to the virtualization root, with the virtualization root itself -specified as an empty string. - - - - -A bit vector of NotificationType values. - - -ProjFS will send to the provider the specified notifications for operations performed on -the directory specified by the property and its descendants. - - - - -Initializes a new instance of the class with the -specified property values. - - The set of notifications that ProjFS should return for the -virtualization root specified in . - The path to the notification root, relative to the virtualization -root. The virtualization root itself must be specified as an empty string. - - is . or begins with .\. -must be specified relative to the virtualization root, with the virtualization root itself -specified as an empty string. - - - - -Initializes a new instance of the class with the - property set to the virtualization root (i.e. null) -and the property set to . - - - - -Represents a path relative to a virtualization root and the notification bit mask that should apply to it. - - - -A object describes a "notification mapping", which is a pairing between a directory -(referred to as a "notification root") and a set of notifications, expressed as a bit mask, which -ProjFS should send for that directory and its descendants. - - -The provider passes zero or more objects to the -parameter of the VirtualizationInstance::StartVirtualizing method to configure -notifications for the virtualization root. - - -If the provider does not specify any notification mappings, ProjFS will default to sending the -notifications , , -and for all files and directories -in the virtualization instance. - - -The property holds the notification root. It is specified -relative to the virtualization root, with an empty string representing the virtualization root -itself. - - -If the provider specifies multiple notification mappings, and some are descendants of others, -the mappings must be specified in descending depth. Notification mappings at deeper levels -override higher-level mappings for their descendants. - - -For example, consider the following virtualization instance layout, with C:\VirtRoot as the -virtualization root: - -C:\VirtRoot -+--- baz -\--- foo - +--- subdir1 - \--- subdir2 - -The provider wants: -Notification of new file/directory creates for most of the virtualization instanceNotification of new file/directory creates, file opens, and file deletes for C:\VirtRoot\fooNo notifications for C:\VirtRoot\foo\subdir1 -The provider could describe this with the following pseudocode: - -List<NotificationMapping> notificationMappings = new List<NotificationMapping>() -{ - // Configure default notifications - new NotificationMapping(NotificationType.NewFileCreated, - string.Empty), - // Configure notifications for C:\VirtRoot\foo - new NotificationMapping(NotificationType.NewFileCreated | NotificationType.FileOpened | NotificationType.FileHandleClosedFileDeleted, - "foo"), - // Configure notifications for C:\VirtRoot\foo\subdir1 - new NotificationMapping(NotificationType.None, - "foo\\subdir1"), -}; - -// Call VirtualizationRoot.StartVirtualizing() passing in the notificationMappings List. - - - - - -Defines values that describe why an attempt to update or delete a file in a virtualization -root has failed. - - -These values are used in the output parameter of -ProjFS.VirtualizationInstance.UpdateFileIfNeeded and ProjFS.VirtualizationInstance.DeleteFile. -These are set if the API returns HResult.VirtualizationInvalidOp because the file state -does not allow the operation with the value(s) passed to the API. - - - - -The item had the DOS read-only bit set and the provider did not specify UpdateType.AllowReadOnly. - - - -The item was a tombstone and the provider did not specify UpdateType.AllowTombstone. - - - - -The item was a full file and the provider did not specify UpdateType.AllowDirtyData. - - - - -The item was a dirty placeholder (hydrated or not), and the provider did not specify -UpdateType.AllowDirtyMetadata. - - - - -The update did not fail. - - - - -Indicates that the file is about to be converted from a placeholder to a full file, i.e. its -contents are likely to be modified. - - - - The provider sets its implementation of this delegate into the OnNotifyFilePreConvertToFull - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - If the provider returns false, then the file system will return - STATUS_ACCESS_DENIED from the operation that triggered the conversion, and the placeholder - will not be converted to a full file. - - The path, relative to the virtualization root, of the file or directory. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - true if the provider wants to allow the file to be converted to full, false - if it wants to prevent the file from being converted to full. - - - - -Indicates that a handle has been closed on the file or directory, and whether the file was modified -while that handle was open, or that the file or directory was deleted as part of closing the handle. - - - - The provider sets its implementation of this delegate into the OnNotifyFileHandleClosedFileModifiedOrDeleted - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - or when it started the virtualization instance. - - The path, relative to the virtualization root, of the file or directory. - - true if is for a directory, - false if is for a file. - - true if the file or directory was modified while the handle - was open, false otherwise. - - true if the file or directory was deleted as part of closing - the handle, false otherwise. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - -Indicates that a handle has been closed on the file or directory, and that the file was not modified -while that handle was open, and that the file or directory was not deleted as part of closing the handle. - - - - The provider sets its implementation of this delegate into the OnNotifyFileHandleClosedNoModification - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - - The path, relative to the virtualization root, of the file or directory. - - true if is for a directory, - false if is for a file. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - Indicates that a hard link has been created for the file. - - - The provider sets its implementation of this delegate into the OnNotifyHardlinkCreated - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - If both the and - parameters of this callback are non-empty strings, this indicates that the new hard link - was created under the virtualization root for a file that exists under the virtualization - root. If the provider specified different notification masks in the - parameter of ProjFS.VirtualizationInstance.StartVirtualizing for the source and - destination paths, then ProjFS will send this notification if the provider specified - when registering either the source or destination - paths. - - The path, relative to the virtualization root, of the file for - which the hard link was created. - This parameter will be "" to indicate that the hard link name was created under the - virtualization root, i.e. a new hard link was created under the virtualization to a file - that exists in a location not under the virtualization root. - The path, relative to the virtualization root, of the new hard - link name. - This parameter will be "" to indicate that the hard link name was created in a - location not under the virtualization root, i.e. a new hard link was created in a location - not under the virtualization root for a file that is under the virtualization root. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - -Indicates that a file or directory has been renamed. The file or directory may have been moved -into the virtualization instance. - - - - The provider sets its implementation of this delegate into the OnNotifyFileRenamed - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - If both the and - parameters of this callback are non-empty strings, this indicates that the source and - destination of the rename were both under the virtualization root. If the provider specified - different notification masks in the parameter of - ProjFS.VirtualizationInstance.StartVirtualizing for the source and destination - paths, then ProjFS will send this notification if the provider specified - when registering either the source or destination - paths. - - The original path, relative to the virtualization root, of the file - or directory that was renamed. - This parameter will be "" to indicate that the rename moved the file or directory - from outside the virtualization instance. In that case ProjFS will always send this notification - if the provider has implemented this callback, even if the provider did not specify - when registering the subtree containing the destination path. - - The path, relative to the virtualization root, to which the file - or directory was renamed. - This parameter will be "" to indicate that the rename moved the file or directory - out of the virtualization instance. - - - true if is for a directory, - false if is for a file. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - Upon return from this callback specifies a bitwise-OR of - values indicating the set of notifications the provider - wishes to receive for this file. - If the provider sets this value to 0, it is equivalent to specifying . - - - Indicates that a hard link is about to be created for the file. - - - The provider sets its implementation of this delegate into the OnNotifyPreCreateHardlink - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - If both the and - parameters of this callback are non-empty strings, this indicates that the new hard link - will be created under the virtualization root for a file that is under the virtualization - root. If the provider specified different notification masks in the - parameter of ProjFS.VirtualizationInstance.StartVirtualizing - for the source and destination paths, then ProjFS will send this notification if the provider - specified when registering either the - source or destination paths. - If the provider returns false, then the file system will return STATUS_ACCESS_DENIED - from the hard link operation, and the hard link will not be created. - - The path, relative to the virtualization root, of the file or directory - for which the hard link is to be created. - This parameter will be "" to indicate that the hard link name will be created under - the virtualization root, i.e. a new hard link is being created under the virtualization - root to a file whose path is not under the virtualization root. - The path, relative to the virtualization root, of the new hard - link name. - This parameter will be "" to indicate that the hard link name will be created in - a location not under the virtualization, i.e. a new hard link is being created in a location - not under the virtualization for a file is under the virtualization root. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - true if the provider wants to allow the hard link operation to happen, false - if it wants to prevent the hard link operation. - - - - Indicates that a file or directory is about to be renamed. - - - The provider sets its implementation of this delegate into the OnNotifyPreRename - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - If both the and - parameters of this callback are non-empty strings, this indicates that the source and - destination of the rename are under the virtualization root. If the provider specified - different notification masks in the parameter of - ProjFS.VirtualizationInstance.StartVirtualizing for the source and destination - paths, then ProjFS will send this notification if the provider specified - when registering either the source or destination - paths. - If the provider returns false, then the file system will return STATUS_ACCESS_DENIED - from the rename operation, and the rename will not take effect. - - The path, relative to the virtualization root, of the file or directory - to be renamed. - This parameter will be "" to indicate that the rename will move the file or directory - from a location not under the virtualization root. In that case ProjFS will always send - this notification if the provider has implemented this callback, even if the provider did - not specify when registering the subtree containing - the destination path. - - The path, relative to the virtualization root, to which the file - or directory will be renamed. - This parameter will be "" to indicate that the rename will move the file or directory - out of the virtualization instance. - - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - true if the provider wants to allow the rename to happen, false if it wants - to prevent the rename. - - - - Indicates that a file or directory is about to be deleted. - - - The provider sets its implementation of this delegate into the OnNotifyPreDelete - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - If the provider returns false then the file system will return STATUS_CANNOT_DELETE - from the operation that triggered the delete, and the delete will not take place. - - The path, relative to the virtualization root, of the file or directory. - - true if is for a directory, - false if is for a file. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - true if the provider wants to allow the delete to happen, false if it wants - to prevent the delete. - - - - Indicates that an existing file has been superseded or overwritten. - - - The provider sets its implementation of this delegate into the OnNotifyFileOverwritten - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - - The path, relative to the virtualization root, of the file or directory. - - true if is for a directory, - false if is for a file. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - Upon return from this callback specifies a bitwise-OR of - values indicating the set of notifications the provider - wishes to receive for this file. - If the provider sets this value to 0, it is equivalent to specifying . - - - Indicates that a new file or directory has been created. - - - The provider sets its implementation of this delegate into the OnNotifyNewFileCreated - property of ProjFS.VirtualizationInstance. - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - - The path, relative to the virtualization root, of the file or directory. - - true if is for a directory, - false if is for a file. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - Upon return from this callback specifies a bitwise-OR of - values indicating the set of notifications the provider - wishes to receive for this file. - If the provider sets this value to 0, it is equivalent to specifying . - - - Indicates that a handle has been created to an existing file or directory. - - - The provider sets its implementation of this delegate into the OnNotifyFileOpened - property of ProjFS.VirtualizationInstance. - - - ProjFS will invoke this callback if the provider registered for - when it started the virtualization instance. - - - If the provider returns false, then the file system will cancel the open of the file and - return STATUS_ACCESS_DENIED to the caller trying to open the file. - - - The path, relative to the virtualization root, of the file or directory. - - true if is for a directory, - false if is for a file. - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - Upon return from this callback specifies a bitwise-OR of - values indicating the set of notifications the provider - wishes to receive for this file. - If the provider sets this value to 0, it is equivalent to specifying . - - - true if the provider wants to allow the opened file to be returned to the - caller, false otherwise. - - - - Informs the provider that an operation begun by an earlier invocation of a callback -is to be canceled. - - - The provider sets its implementation of this delegate into the OnCancelCommand - property of ProjFS.VirtualizationInstance. - - - ProjFS invokes this callback to indicate that the I/O that caused the earlier callback - to be invoked was canceled, either explicitly or because the thread it was issued on terminated. - - - Calling ProjFS.VirtualizationInstance.CompleteCommand for the - passed by this callback is not an error, however it is a no-op because the I/O that caused - the callback invocation identified by has already ended. - - - ProjFS will invoke this callback for a given only after - the callback to be canceled is invoked. However if the provider is configured to allow - more than one concurrently running worker thread, the cancellation and original invocation - may run concurrently. The provider must be able to handle this situation. - - - A provider that does not return to any of its callbacks does - not need to handle this callback. - - - A value that identifies the callback invocation to be canceled. -Corresponds to the parameter of callbacks whose processing -can be canceled. - - - Determines whether a given file path exists in the provider's store. - - - The provider sets its implementation of this delegate into the OnQueryFileName - property of ProjFS.VirtualizationInstance. - - - If the provider does not implement this callback, ProjFS will call the enumeration callbacks - when it needs to find out whether a file path exists in the provider’s store. - - - The path, relative to the virtualization root, of the file being queried. - - - if exists in the provider's store. - - if does not exist in the provider's store. - An appropriate error code if the provider fails the operation. - - - - Requests the contents of a file's primary data stream. - - ProjFS uses the data the provider provides in this callback to convert the file into - a hydrated placeholder. - To handle this callback, the provider issues one or more calls to - ProjFS.VirtualizationInstance.WriteFile to give ProjFS the contents of the file's - primary data stream. Then the provider completes the callback. - - A value that uniquely identifies an invocation of the callback. - If the provider returns from this method, then it must pass - this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - finished processing this invocation of the callback. - The path, relative to the virtualization root, of the file for -which to provide data. - Offset in bytes from the beginning of the file at which the provider - must start returning data. The provider must return file data starting at or before this - offset. - Number of bytes of file data requested. The provider must return at least - this many bytes of file data beginning at . - The unique value to associate with this file stream. The provider - must pass this value to ProjFS.VirtualizationInstance.WriteFile when providing - file data as part of handling this callback. - The value specified by the provider when - it created the placeholder for this file. See ProjFS.VirtualizationInstance.WritePlaceholderInfo. - The value specified by the provider when - it created the placeholder for this file. See ProjFS.VirtualizationInstance.WritePlaceholderInfo. - The PID for the process that triggered this callback. If -this information is not available, this will be 0. - The image file name corresponding to -. If is 0 this -will be null. - - - if the provider successfully wrote all the requested data. - - if the provider wishes to complete the operation at a later time. - An appropriate error code if the provider fails the operation. - - - - Requests metadata information for a file or directory from the provider. - - ProjFS uses the information the provider provides in this callback to create a - placeholder for the requested item. - To handle this callback, the provider typically calls - ProjFS.VirtualizationInstance.WritePlaceholderInfo to give ProjFS the information - for the requested file name. Then the provider completes the callback. - - - A value that uniquely identifies an invocation of the callback. - If the provider returns from this method, then it must pass - this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - finished processing this invocation of the callback. - - The path, relative to the virtualization root, of the file for -which to return information. - The PID for the process that triggered this callback. If -this information is not available, this will be 0. - The image file name corresponding to -. If is 0 this -will be null. - - - if the file exists in the provider's store and it successfully - gave the file's information to ProjFS. - - if does not exist in the provider's store. - - if the provider wishes to complete the operation at a later time. - An appropriate error code if the provider fails the operation. - - - - -Informs the provider that a directory enumeration is over. - - - - - - ProjFS requests a directory enumeration from the provider by first invoking - , then the - callback one or more times, then this - callback. Because multiple enumerations may occur in parallel in the same location, - ProjFS uses the argument to associate the callback - invocations into a single enumeration, meaning that a given set of calls to the enumeration - callbacks will use the same value for for the same session. - - - Identifies this enumeration session. - - - if the provider successfully completes the operation. - An appropriate error code if the provider fails the operation. - - - - -Requests directory enumeration information from the provider. - - - - - - ProjFS requests a directory enumeration from the provider by first invoking - , then this callback one or more times, - then the callback. Because multiple - enumerations may occur in parallel in the same location, ProjFS uses the - argument to associate the callback invocations into a - single enumeration, meaning that a given set of calls to the enumeration callbacks will - use the same value for for the same session. - - - The provider must store the value of across calls - to this callback. The provider replaces the value of - if in a subsequent invocation of the callback is true - If no entries match the search expression specified in , - or if all the entries in the directory were added in a previous invocation of this callback, - the provider must return . - - - A value that uniquely identifies an invocation of the callback. - If the provider returns from this method, then it must pass - this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - finished processing this invocation of the callback. - - Identifies this enumeration session. - - An optional string specifying a search expression. This parameter may be null. - The search expression may include wildcard characters. The provider should use the - ProjFS.Utils.DoesNameContainWildCards method routine to determine whether wildcards - are present in the search expression. The provider should use the ProjFS.Utils.IsFileNameMatch - method to determine whether a directory entry in its store matches the search expression. - If this parameter is not null, only files whose names match the search expression - should be included in the directory scan. - If this parameter is null, all entries in the directory must be included. - - - - true if the scan is to start at the first entry in the directory. - - false if resuming the scan from a previous call. - On the first invocation of this callback for an enumeration session the provider must - treat this as true, regardless of its value (i.e. all enumerations must start at the - first entry). On subsequent invocations of this callback the provider must honor this value. - - - Receives the results of the enumeration from the provider. - The provider uses one of the ::Add - methods of this object to provide the enumeration results. - If the provider returns from this method, then it must pass - this value to ProjFS.VirtualizationInstance.CompleteCommand to provide the - enumeration results. - - - - if the provider successfully completes the operation. - - if the provider wishes to complete the operation at a later time. - - if .Add returned - for the first matching file or directory in the enumeration. - An appropriate error code if the provider fails the operation. - - - - -Informs the provider that a directory enumeration is starting. - - - - - - ProjFS requests a directory enumeration from the provider by first invoking this callback, - then the callback one or more times, then - the callback. Because multiple enumerations - may occur in parallel in the same location, ProjFS uses the - argument to associate the callback invocations into a single enumeration, meaning that - a given set of calls to the enumeration callbacks will use the same value for - for the same session. - - - A value that uniquely identifies an invocation of the callback. - If the provider returns from this method, then it must pass - this value to ProjFS.VirtualizationInstance.CompleteCommand to signal that it has - finished processing this invocation of the callback. - Identifies this enumeration session. - Identifies the directory to be enumerated. The path is specified - relative to the virtualization root. - - The PID of the process that triggered this callback. If this - information is not available, this will be 0. - The image file name corresponding to . - If is 0 this will be null. - - - if the provider successfully completes the operation. - - if the provider wishes to complete the operation at a later time. - An appropriate error code if the provider fails the operation. - - - - -Defines callbacks that a provider is required to implement. - - - -A provider must implement the methods in this class to supply basic file system functionality. -The provider passes a reference to its implementation in the Microsoft.Windows.ProjFS.StartVirtualizing -method. - - - - - Adds one entry to a directory enumeration result. - - - In its implementation of a GetDirectoryEnumerationCallback delegate the provider - calls this method for each matching file or directory in the enumeration. - - - If this method returns false, the provider returns and waits for - the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - the entry it was trying to add when it got false. - - - If the method returns false for the first file or directory in the enumeration, the - provider returns from the GetDirectoryEnumerationCallback - method. - - - The name of the file or directory. - The size of the file. - - true if this item is a directory, false if it is a file. - The file attributes. - The time the file was created. - The time the file was last accessed. - The time the file was last written to. - The time the file was last changed. - - - true if the entry was successfully added to the enumeration buffer, false otherwise. - - - - is null or empty. - - - - Adds one entry to a directory enumeration result. - - - In its implementation of a GetDirectoryEnumerationCallback delegate the provider - calls this method for each matching file or directory in the enumeration. - - - If the provider calls this Add overload, then the timestamps reported to the caller - of the enumeration are the current system time. If the provider wants the caller to see other - timestamps, it must use the other Add overload. - - - If this method returns false, the provider returns and waits for - the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with - the entry it was trying to add when it got false. - - - If the method returns false for the first file or directory in the enumeration, the - provider returns from the GetDirectoryEnumerationCallback - method. - - - The name of the file or directory. - The size of the file. - - true if this item is a directory, false if it is a file. - - - true if the entry was successfully added to the enumeration buffer, false otherwise. - - - - is null or empty. - - - - Helper class for providing the results of a directory enumeration. - -ProjFS passes an instance of this class to the provider in the -parameter of its implementation of a GetDirectoryEnumerationCallback delegate. The provider -calls one of its Add methods for each item in the enumeration -to add it to the result set. - - - - -When overridden in a derived class, adds one entry to a directory enumeration result. - - - -In its implementation of a GetDirectoryEnumerationCallback delegate the provider -calls this method for each matching file or directory in the enumeration. - - -If this method returns false, the provider returns HResult.Ok and waits for -the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with -the entry it was trying to add when it got false. - - -If the function returns false for the first file or directory in the enumeration, the -provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback -method. - - - The name of the file or directory. - The size of the file. - - true if this item is a directory, false if it is a file. - The file attributes. - Specifies the time that the file was created. - Specifies the time that the file was last accessed. - Specifies the time that the file was last written to. - Specifies the last time the file was changed. - - - true if the entry was successfully added to the enumeration buffer, false otherwise. - - - - - -When overridden in a derived class, adds one entry to a directory enumeration result. - - - -In its implementation of a GetDirectoryEnumerationCallback delegate the provider -calls this method for each matching file or directory in the enumeration. - - -If this method returns false, the provider returns HResult.Ok and waits for -the next GetDirectoryEnumerationCallback. Then it resumes filling the enumeration with -the entry it was trying to add when it got false. - - -If the function returns false for the first file or directory in the enumeration, the -provider returns HResult.InsufficientBuffer from the GetDirectoryEnumerationCallback -method. - - - The name of the file or directory. - The size of the file. - - true if this item is a directory, false if it is a file. - - - true if the entry was successfully added to the enumeration buffer, false otherwise. - - - - - -Interface to allow for easier unit testing of a virtualization provider. - - -This class defines the interface implemented by the Microsoft.Windows.ProjFS.DirectoryEnumerationResults -class. This interface class is provided for use by unit tests to mock up the interface to ProjFS. - - - - -Frees the internal buffer. - - - - -Gets a pointer to the internal buffer. - - - - -Gets a representing the internal buffer. - - - - -Gets the allocated length of the buffer. - - - - -Helper class to ensure correct alignment when providing file contents for a placeholder. - - - -The provider does not instantiate this class directly. It uses the -ProjFS.VirtualizationInstance.CreateWriteBuffer method to obtain a properly initialized -instance of this class. - - -The ProjFS.VirtualizationInstance.WriteFileData method requires a data buffer containing -file data for a placeholder so that ProjFS can convert the placeholder to a hydrated placeholder -(see ProjFS.OnDiskFileState for a discussion of file states). Internally ProjFS uses -the user's FILE_OBJECT to write this data to the file. Because the user may have opened the -file for unbuffered I/O, and unbuffered I/O imposes certain alignment requirements, this -class is provided to abstract out those details. - - -When the provider starts its virtualization instance, the VirtualizationInstance class -queries the alignment requirements of the underlying physical storage device and uses this -information to return a properly-initialized instance of this class from its CreateWriteBuffer -method. - - - - - Helper class for using the correct native APIs in the managed layer. - - -The final ProjFS native APIs released in Windows 10 version 1809 differ from the now-deprecated -beta APIs released in Windows 10 version 1803. In 1809 the beta APIs are still exported from -ProjectedFSLib.dll, in case an experimental provider written against the native 1803 APIs is run -on 1809. - - -This managed API wrapper is meant to be usable on 1803 and later, so it is able to use the -beta 1803 native APIs and the final 1809 native APIs. Since the 1809 APIs are not present on -1803, and because we intend to remove the beta 1803 APIs from a later version of Windows, we -dynamically load the native APIs here. If we didn't do that then trying to use this managed -wrapper on a version of Windows missing one or the other native API would result in the program -dying on startup with an unhandled System::IO::FileLoadException: "A procedure -imported by 'ProjectedFSLib.Managed.dll' could not be loaded." - - -It is likely that at some point after removing the beta 1803 native APIs from Windows we will -also remove support for them from this managed wrapper. - - - - - -When overridden in a derived class, gets a pointer to the internal buffer. - - - - -When overridden in a derived class, gets a -representing the internal buffer. - - - - -When overridden in a derived class, gets the allocated length of the buffer. - - - - -Interface to allow for easier unit testing of a virtualization provider. - - -This class defines the interface implemented by the Microsoft.Windows.ProjFS.WriteBuffer -class. This interface class is provided for use by unit tests to mock up the interface to ProjFS. - - - - Determines whether a string contains any wildcard characters. - - -This routine checks for the wildcard characters recognized by the file system. These -wildcards are sent by programs such as the cmd.exe command interpreter. - - - - - Character - Meaning - - - * - Matches 0 or more characters. - - - ? - Matches exactly one character. - - - DOS_DOT (") - Matches either a ".", or zero characters beyond the name string. - - - DOS_STAR (<) - Matches 0 or more characters until encountering and matching the final "." in the name. - - - DOS_QM (>) - Matches any single character, or upon encountering a period or end of name string, advances the expression - to the end of the set of contiguous DOS_QMs. - - - - - A string to check for wildcard characters. - - true if contains any wildcard characters, -false otherwise. - - - -Compares two file names and returns a value that indicates their relative collation order. - - -The provider may use this routine to determine how to sort file names in the same order -that the file system does. - - The first name to compare. - The second name to compare. - - A negative number if is before in collation order. - 0 if is equal to . - A positive number if is after in collation order. - - - - -Determines whether a file name string matches a pattern, potentially containing -wildcard characters, according to the rules used by the file system. - - -A provider should use this routine in its implementation of the GetDirectoryEnumerationCallback -delegate to determine whether a name it its backing store matches the search expression -from the filterFileName parameter of the GetDirectoryEnumerationCallback -delegate. - - The file name to check against . - The pattern for which to search. - - true if matches , -false otherwise. - - - -Returns the on-disk state of the specified file or directory. - - - - This routine tells the caller what the ProjFS caching state is of the specified file or - directory. For example, the caller can use this routine to determine whether the given item - is a placeholder or full file. - - - A running provider should be cautious if using this routine on files or directories within - one of its virtualization instances, as it may cause callbacks to be invoked in the provider. - Depending on the design of the provider this may lead to deadlocks. - - - Full path of the file or the directory. - On successful return contains a bitwise-OR of - values describing the file state. - - false if does not exist. - - - - -Provides utility methods for ProjFS providers. - - - - Defines values describing the on-disk state of a virtualized file. - - -The state is used to managed deleted files. When -a directory is enumerated ProjFS merges the set of local items (placeholders, full files, -etc.) with the set of virtual items projected by the provider's IRequiredCallbacks::GetDirectoryEnumerationCallback -method. If an item appears in both the local and projected sets, the local item takes precedence. -If a file does not exist there is no local state, so it would appear in the enumeration. -However if that item had been deleted, having it appear in the enumeration would be unexpected. -ProjFS deals with this by replacing a deleted item with a special hidden placeholder called -a "tombstone". This has the following effects: -Enumerations do not reveal the item.File opens that expect the item to exist fail with e.g. "file not found".File creates that expect to succeed only if the item does not exist succeed; - ProjFS removes the tombstone as part of the operation. - -To illustrate the on-disk states consider the following sequence, given a ProjFS provider -that has a single file "foo.txt" located in the virtualization root C:\root. -An app enumerates C:\root. It sees the virtual file "foo.txt". Since the - file has not yet been accessed, the file does not exist on disk.The app opens a handle to C:\root\foo.txt. ProjFS tells the provider to - create a placeholder for it. The file's state is now The app reads the content of the file. The provider provides the file - content to ProjFS and it is cached to C:\root\foo.txt. The file's state is - now | .The app updates the Last Modified timestamp. The file's state is now - | | .The app writes some new data to the file. C:\root\foo.txt's state - is now .The app deletes C:\root\foo.txt. ProjFS replaces the file with a tombstone, - so its state is now . - Now when the app enumerates C:\root it does not see foo.txt. If it tries to open the - file, the open fails with HResult.FileNotFound. - - - - -A special hidden placeholder that represents an item that has been deleted from the local -file system. - - -The item was a tombstone and the provider did not specify UpdateType.AllowTombstone. - - - - -The item's content (primary data stream) has been modified. The file is no longer a cache -of its state in the provider's store. Files that have been created on the local file -system (i.e.that do not exist in the provider's store at all) are also considered to be -full files. - - - - -The item's metadata has been locally modified and is no longer a cache of its state in -the provider's store. Note that creating or deleting a file or directory under a placeholder -directory causes that placeholder directory to become dirty. - - - - -The item's content and metadata have been cached to the disk. Also referred to as a -"partial file/directory". - - - - -The item's content (primary data stream) is not present on the disk. The item's metadata -(name, size, timestamps, attributes, etc.) is cached on the disk. - - - - -HRESULT values that ProjFS may report to a provider, or that a provider may return to ProjFS. - - - -.NET methods normally do not return error codes, preferring to throw exceptions. For the most -part this API does not throw exceptions, preferring instead to return error codes. We do this -for few reasons: - - This API is a relatively thin wrapper around a native API that itself returns HRESULT codes. - This managed library would have to translate those error codes into exceptions to throw. - - Errors that a provider returns are sent through the file system, back to the user who is - performing the I/O. If the provider callbacks threw exceptions, the managed library would - just have to catch them and turn them into HRESULT codes. - - If the API methods described here threw exceptions, either the provider would have to catch - them and turn them into error codes to return from its callbacks, or it would allow those - exceptions to propagate and this managed library would still have to deal with them as - described in the preceding bullet. - -So rather than deal with the overhead of exceptions just to try to conform to .NET conventions, -this API largely dispenses with them and uses HRESULT codes. - - -Note that for the convenience of C# developers the VirtualizationInstance::CreateWriteBuffer -method does throw System::OutOfMemoryException if it cannot allocate the buffer. This -makes the method convenient to use with the using keyword. - - -Note that when HRESULT codes returned from the provider are sent to the file system, the ProjFS -library translates them into NTSTATUS codes. Because there is not a 1-to-1 mapping of HRESULT -codes to NTSTATUS codes, the set of HRESULT codes that a provider is allowed to return is -necessarily constrained. - - -A provider's IRequiredCallbacks method and On... delegate implementations may -return any HResult value returned from a VirtualizationInstance, as well as the -following HResult values: - - -The remaining values in the HResult enum may be returned to a provider from ProjFS APIs -and are primarily intended to communicate information to the provider. As noted above, if -such a value is returned to a provider in its implementation of a callback or delegate, it may -return the value to ProjFS. - - - - - The virtualization operation is not allowed on the file in its current state. - - - The object manager encountered a reparse point while retrieving an object. - - - The system cannot find the path specified. - - - One or more arguments are invalid. - - - Invalid handle (it may already be closed). - - - The directory is not empty. - - - The directory name is invalid (it may not be a directory). - - - An attempt has been made to remove a file or directory that cannot be deleted. - - - Access is denied. - - - An attempt was made to perform an initialization operation when initialization -has already been completed. - - - The provider is in an invalid state that prevents it from servicing the callback -(only use this if none of the other error codes is a better match). - - - The provider that supports file system virtualization is temporarily unavailable. - - - The system cannot find the file specified. - - - The data area passed to a system call is too small. - - - Ran out of memory. - - - The data necessary to complete this operation is not yet available. - - - Success. - - - Defines values describing when to allow a virtualized file to be deleted or updated. - - -These values are used in the input parameter of -ProjFS.VirtualizationInstance.UpdateFileIfNeeded and ProjFS.VirtualizationInstance.DeleteFile. -The flags control whether ProjFS should allow the update given the state of the file or directory on disk. - - -See the documentation for ProjFS.OnDiskFileState for a description of possible file -and directory states in ProjFS. - - - - - -ProjFS will allow the update regardless of whether the DOS read-only bit is set on the item. - - - - -ProjFS will allow the update if the item is a placeholder or is a tombstone. - - - - -ProjFS will allow the update if the item is a placeholder or is a full file. - - - - -ProjFS will allow the update if the item is a placeholder or a dirty placeholder (whether hydrated or not). - - - - -Defines values for file system operation notifications ProjFS can send to a provider. - - - - ProjFS can send notifications of file system activity to a provider. When the provider - starts a virtualization instance it specifies which notifications it wishes to receive. - It may also specify a new set of notifications for a file when it is created or renamed. - The provider must set implementations of Notify...Callback delegates in the OnNotify... - properties of ProjFS.VirtualizationInstance in order to receive the notifications - for which it registers. - - - ProjFS sends notifications for files and directories managed by an active virtualization - instance. That is, ProjFS will send notifications for the virtualization root and its - descendants. Symbolic links and junctions within the virtualization root are not traversed - when determining what constitutes a descendant of the virtualization root. - - - ProjFS sends notifications only for the primary data stream of a file. ProjFS does not - send notifications for operations on alternate data streams. - - - ProjFS does not send notifications for an inactive virtualization instance. A virtualization - instance is inactive if any one of the following is true: - - The provider has not yet started it by calling ProjFS.VirtualizationInstance.StartVirtualizing. - - The provider has stopped the instance by calling ProjFS.VirtualizationInstance.StopVirtualizing. - - The provider process has exited. - - - The provider may specify which notifications it wishes to receive when starting a virtualization - instance, or in response to a notification that allows a new notification mask to be set. - The provider specifies a default set of notifications that it wants ProjFS to send for the - virtualization instance when it starts the instance. The provider specifies the default - notifications via the parameter of the - ProjFS.VirtualizationInstance constructor, which may specify different notification - masks for different subtrees of the virtualization instance. - - - The provider may choose to supply a different notification mask in response to a notification - of file open, create, overwrite, or rename. ProjFS will continue to send these notifications - for the given file until all handles to the file are closed. After that it will revert - to the default set of notifications. Naturally if the default set of notifications does - not specify that ProjFS should notify for open, create, etc., the provider will not get - the opportunity to specify a different mask for those operations. - - - - - -This value is not used when calling the VirtualizationInstance constructor. It -is only returned from OnNotify... callbacks that have a -parameter, and indicates that the provider wants to continue to receive the notifications -it registered for when starting the virtualization instance. - - - - -Indicates that ProjFS should call the provider's OnNotifyFilePreConvertToFull callback when it is about to convert a placeholder to a full file. - - - - -Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedFileModifiedOrDeleted callback when a handle is closed on a file or -directory and it is deleted as part of closing the handle. - - - - -Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedFileModifiedOrDeleted callback when a handle is closed on a file or -directory and the closing handle was used to modify it. - - - - -Indicates that ProjFS should call the provider's OnNotifyFileHandleClosedNoModification callback when a handle is closed on a file or directory -and the closing handle neither modified nor deleted it. - - - - -Indicates that ProjFS should call the provider's OnNotifyHardlinkCreated callback when a hard link has been created for a file. - - - - -Indicates that ProjFS should call the provider's OnNotifyFileRenamed callback when a file or directory has been renamed. - - - - -Indicates that ProjFS should call the provider's OnNotifyPreCreateHardlink callback when a hard link is about to be created for a file. - - - - -Indicates that ProjFS should call the provider's OnNotifyPreRename callback when a file or directory is about to be renamed. - - - - -Indicates that ProjFS should call the provider's OnNotifyPreDelete callback when a file or directory is about to be deleted. - - - - -Indicates that ProjFS should call the provider's OnNotifyFileOverwritten callback when an existing file is superseded or overwritten. - - - - -Indicates that ProjFS should call the provider's OnNotifyNewFileCreated callback when a new file or directory is created. - - - - -Indicates that ProjFS should call the provider's OnNotifyFileOpened callback when a handle is created to an existing file or directory. - - - - -Indicates that the provider does not want any notifications. This value overrides all others. - - - - \ No newline at end of file diff --git a/doc/design-pure-csharp.md b/doc/design-pure-csharp.md new file mode 100644 index 0000000..08c60b4 --- /dev/null +++ b/doc/design-pure-csharp.md @@ -0,0 +1,129 @@ +# Design: Pure C# ProjFS Managed API + +## Summary + +This document describes the design and motivation for replacing the C++/CLI mixed-mode +`ProjectedFSLib.Managed.dll` with a pure C# P/Invoke implementation that maintains +100% API compatibility while enabling NativeAOT compilation, trimming, and simplified builds. + +## Background + +The original ProjFS Managed API is a C++/CLI mixed-mode assembly that wraps the native +`ProjectedFSLib.dll` APIs. While functional, this approach has several limitations: + +| Concern | C++/CLI | Pure C# | +|---------|---------|---------| +| Build toolchain | Requires VS C++ workload + C++/CLI support | `dotnet build` only | +| Runtime dependency | Visual C++ redistributable (Ijwhost.dll) | None | +| NativeAOT | Not supported (mixed-mode incompatible) | Fully supported | +| Trimming | Not supported | `IsAotCompatible=true` | +| Cross-compilation | Requires matching native toolchain | Any machine with .NET SDK | +| TFMs | net48, netcoreapp3.1 | netstandard2.0, net8.0, net9.0, net10.0+ | +| Maintenance | Dual C++/C# expertise needed | C# only | + +## Design Goals + +1. **API compatibility** — Same `Microsoft.Windows.ProjFS` namespace, same types, same signatures +2. **Drop-in replacement** — Consumers change only the project reference, no code changes +3. **AOT-safe** — No reflection, no dynamic code generation, `IsAotCompatible=true` +4. **Correctness** — Byte-identical struct layouts verified against Windows SDK headers +5. **Complete feature coverage** — All v1809 APIs + v2004 symlink APIs + +## Architecture + +### P/Invoke Layer (`ProjFSNative.cs`) + +Direct P/Invoke declarations for all `ProjectedFSLib.dll` exports, using `#if NET7_0_OR_GREATER` +to select between `[LibraryImport]` (source-generated, AOT-optimal) and `[DllImport]` (traditional) +on a per-method basis: + +- Core: `PrjStartVirtualizing`, `PrjStopVirtualizing` +- Placeholders: `PrjWritePlaceholderInfo`, `PrjWritePlaceholderInfo2`, `PrjUpdateFileIfNeeded` +- Enumeration: `PrjFillDirEntryBuffer`, `PrjFillDirEntryBuffer2` +- Data: `PrjWriteFileData`, `PrjAllocateAlignedBuffer`, `PrjFreeAlignedBuffer` +- Utilities: `PrjFileNameMatch`, `PrjFileNameCompare`, `PrjDoesNameContainWildCards` + +All native structs use `[StructLayout(LayoutKind.Sequential)]` with exact field-level +padding verified against `sizeof()` and `offsetof()` from the Windows SDK: + +| Struct | C sizeof | C# Marshal.SizeOf | +|--------|----------|--------------------| +| `PRJ_FILE_BASIC_INFO` | 56 | 56 | +| `PRJ_PLACEHOLDER_INFO` | 344 | 344 | +| `PRJ_EXTENDED_INFO` | 16 | 16 | +| `PRJ_CALLBACK_DATA` | 96 | 96 | +| `PRJ_CALLBACKS` | 64 | 64 | +| `PRJ_STARTVIRTUALIZING_OPTIONS` | 32 | 32 | +| `PRJ_NOTIFICATION_MAPPING` | 16 | 16 | + +**Key lesson learned:** `PRJ_PLACEHOLDER_INFO` includes a `UINT8 VariableData[1]` flexible +array member at offset 336. Omitting this field causes `Marshal.SizeOf` to return 336 instead +of the native 344, and `PrjWritePlaceholderInfo` returns `ERROR_INSUFFICIENT_BUFFER`. + +### Callback Routing (`VirtualizationInstance.cs`) + +Native ProjFS callbacks (registered via `PRJ_CALLBACKS` function pointers) are routed to +managed code through `[UnmanagedFunctionPointer(CallingConvention.StdCall)]` delegates. +The instance context passed to `PrjStartVirtualizing` is a `GCHandle` that allows the +static callback methods to recover the `VirtualizationInstance` object. + +### Constructor Behavior + +The constructor matches C++/CLI behavior exactly: +1. Creates the virtualization root directory if it doesn't exist +2. Checks for existing ProjFS reparse point via `PrjGetOnDiskFileState` +3. Marks the directory as a virtualization root via `PrjMarkDirectoryAsPlaceholder` +4. Throws `Win32Exception` on failure + +### String Marshaling + +All `PCWSTR` parameters in ProjFS structs are passed as `IntPtr` with +`Marshal.StringToHGlobalUni()`. Using `GCHandle.Alloc(string, GCHandleType.Pinned)` +is **incorrect** — `AddrOfPinnedObject()` on a pinned string returns the object header +address, not the character data pointer. + +### Notification Mapping Lifetime + +Notification mapping data (the `PRJ_NOTIFICATION_MAPPING` array and the `NotificationRoot` +string pointers) must remain valid for the lifetime of the virtualization instance. +ProjFS may cache these pointers internally. They are freed only in `StopVirtualizing`. + +## Known Limitations + +### ReFS Symlink Restriction + +ProjFS symlink placeholders (`WritePlaceholderInfo2`, `PrjFillDirEntryBuffer2` with +`PRJ_EXT_INFO_TYPE_SYMLINK`) require an **NTFS** volume. The ProjFS kernel minifilter +(`PrjFlt.sys`) creates symlinks via the NTFS atomic create ECP (`GUID_ECP_ATOMIC_CREATE`), +which ReFS does not support. On ReFS volumes, `PrjWritePlaceholderInfo2` returns +`ERROR_NOT_SUPPORTED` (`0x80070032`). + +This was diagnosed via WinDbg: the user-mode `ProjectedFSLib.dll` correctly sends a +`FilterSendMessage` to the kernel, but `PrjFlt.sys` returns `STATUS_NOT_SUPPORTED` +(`0xC00000BB`) because `FltCreateFileEx2` with the symlink atomic create ECP fails on ReFS. + +Non-symlink operations work correctly on both NTFS and ReFS. + +### No Windows 10 1803 Beta API Support + +The pure C# implementation targets the v1809 (final) ProjFS API only. The v1803 beta API +(`PrjStartVirtualizationInstance`, `PrjWritePlaceholderInformation`, etc.) is not supported. +This matches the minimum supported Windows version for ProjFS as a shipped component. + +## Test Results + +All tests pass on net48, net8.0, and net10.0: + +- 10 core tests: placeholder creation, file hydration, directory enumeration, notifications +- 6 symlink tests: file symlinks, directory symlinks, relative paths (require NTFS + Developer Mode) +- net48 validates the DllImport/netstandard2.0 path; net8.0/net10.0 validate LibraryImport + +## Migration Guide + +To switch from the C++/CLI package to the pure C# implementation: + +1. Remove the `Microsoft.Windows.ProjFS` NuGet package reference +2. Add a project reference to `ProjectedFSLib.Managed.csproj` +3. Remove `True` from your project file (no longer needed) +4. Remove the Visual C++ redistributable from your installer +5. No code changes required — same namespace, same API surface diff --git a/doc/projfs-pure-csharp-overview.md b/doc/projfs-pure-csharp-overview.md new file mode 100644 index 0000000..bb70624 --- /dev/null +++ b/doc/projfs-pure-csharp-overview.md @@ -0,0 +1,151 @@ +--- +marp: true +theme: default +class: invert +paginate: true +header: "ProjFS Managed API — Pure C# Migration" +footer: "github.com/miniksa/ProjFS-Managed-API" +style: | + section { font-family: 'Segoe UI', sans-serif; } + h1 { color: #60a5fa; } + h2 { color: #93c5fd; } + code { background: #1e293b; color: #e2e8f0; } + table { font-size: 0.8em; } +--- + +# ProjFS Managed API +## From C++/CLI to Pure C# + +Drop the C++ toolchain. Keep the API. +Enable NativeAOT for all ProjFS consumers. + +--- + +# Why Change? + +The current `ProjectedFSLib.Managed.dll` is a **C++/CLI mixed-mode assembly**. + +| Problem | Impact | +|---------|--------| +| Requires VS C++ workload + C++/CLI support | Build infrastructure complexity | +| Requires Visual C++ redistributable | Deployment/installer overhead | +| Incompatible with NativeAOT | Blocks modern .NET optimization | +| Incompatible with trimming | Larger deployment sizes | +| Targets netcoreapp3.1 / net48 | Stuck on legacy TFMs | +| Two languages in one project | Higher maintenance bar | + +--- + +# What We Did + +**Pure C# P/Invoke replacement** — same namespace, same API, zero C++. + +```xml + + + + + + +``` + +- `Microsoft.Windows.ProjFS` namespace preserved +- All types, interfaces, delegates, enums — identical signatures +- 16/16 existing tests pass (including symlink tests) +- **netstandard2.0** target for .NET Framework 4.8 / .NET Core 3.1 compatibility + +--- + +# Architecture + +``` +┌──────────────────────────────────────────┐ +│ Your ProjFS Provider (C#) │ +│ (VFSForGit, SimpleProvider, etc.) │ +├──────────────────────────────────────────┤ +│ ProjectedFSLib.Managed.dll (C#) │ ← NEW: Pure C# P/Invoke +│ VirtualizationInstance · Callbacks · │ +│ DirectoryEnumerationResults · Utils │ +├──────────────────────────────────────────┤ +│ ProjectedFSLib.dll (native) │ Windows SDK (unchanged) +├──────────────────────────────────────────┤ +│ PrjFlt.sys (kernel) │ Minifilter (unchanged) +└──────────────────────────────────────────┘ +``` + +We replaced **one layer** — the managed wrapper. Everything above and below is unchanged. + +--- + +# Key Technical Decisions + +### Struct Layout Verification +Every P/Invoke struct verified against Windows SDK with native `sizeof()`/`offsetof()`: +- `PRJ_PLACEHOLDER_INFO` = **344 bytes** (includes `VariableData[1]` flexible array) +- Missing 8 bytes caused `ERROR_INSUFFICIENT_BUFFER` for all placeholder writes + +### String Marshaling +`PCWSTR` fields use `Marshal.StringToHGlobalUni()`, **not** `GCHandle.Alloc + AddrOfPinnedObject`. +Pinning a .NET string gives the object header address, not the character data. + +### Notification Mapping Lifetime +ProjFS caches notification mapping pointers — must keep alive for instance lifetime. + +--- + +# What We Learned + +## ReFS Does Not Support ProjFS Symlinks + +`WritePlaceholderInfo2` (symlink placeholders) fails with `ERROR_NOT_SUPPORTED` +on ReFS volumes. + +**Root cause** (via WinDbg + kernel source): +- `PrjFlt.sys` → `PrjfCreateSymbolicLink` → `FltCreateFileEx2` with atomic create ECP +- NTFS: ✅ Supports `GUID_ECP_ATOMIC_CREATE` with `IO_REPARSE_TAG_SYMLINK` +- ReFS: ❌ Returns `STATUS_NOT_SUPPORTED` (`0xC00000BB`) + +Non-symlink operations work on both NTFS and ReFS. + +--- + +# Results + +| Metric | C++/CLI | Pure C# | +|--------|---------|---------| +| Build toolchain | VS 2026 + C++ + C++/CLI | `dotnet build` | +| Runtime deps | VC++ Redist + Ijwhost.dll | None | +| NativeAOT | ❌ | ✅ | +| Trimming | ❌ | ✅ (`IsAotCompatible=true`) | +| TFMs | net48, netcoreapp3.1 | netstandard2.0, net8.0, net9.0, net10.0 | +| Tests passing | 16/16 | 16/16 (net48: 12/12) | +| P/Invoke strategy | C++/CLI mixed-mode | LibraryImport (.NET 7+) / DllImport (netstandard2.0) | +| Lines of code | ~4,000 (C++/CLI) | ~1,700 (C#) | +| Source files | 30+ (.h, .cpp, .vcxproj) | 5 (.cs, .csproj) | + +--- + +# Migration Path for Consumers + +1. Replace NuGet package reference with project reference +2. Remove `True` +3. Remove VC++ redistributable from installer +4. **No code changes** — same namespace, same API + +### For VFSForGit specifically: +This unblocks the complete .NET 10 + NativeAOT migration: +- 3.5x faster startup (53ms → 15ms) +- 2x faster pipe roundtrips (341ms → 179ms) +- 78% faster file reads +- Zero regressions across all benchmarks + +--- + +# Ask + +1. **Accept the PR** to replace C++/CLI with pure C# in ProjFS-Managed-API +2. **Publish updated NuGet** (`Microsoft.Windows.ProjFS` v2.0.0) targeting modern .NET TFMs +3. **VFSForGit** can then depend on the upstream package instead of vendoring + +This is **incremental** — the old C++/CLI package continues to work for existing consumers. +New consumers get NativeAOT support, simpler builds, and fewer dependencies. diff --git a/doc/projfs-pure-csharp-overview.pdf b/doc/projfs-pure-csharp-overview.pdf new file mode 100644 index 0000000..b9afdb3 Binary files /dev/null and b/doc/projfs-pure-csharp-overview.pdf differ diff --git a/global.json b/global.json index b3a5089..512142d 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { - "msbuild-sdks": { - "Microsoft.Build.NoTargets": "1.0.85" + "sdk": { + "version": "10.0.100", + "rollForward": "latestFeature" } } diff --git a/scripts/BuildProjFS-Managed.bat b/scripts/BuildProjFS-Managed.bat index 206f602..c0e3957 100644 --- a/scripts/BuildProjFS-Managed.bat +++ b/scripts/BuildProjFS-Managed.bat @@ -1,52 +1,15 @@ @ECHO OFF -SETLOCAL ENABLEDELAYEDEXPANSION - -CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 +SETLOCAL IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") -IF "%2"=="" (SET "ProjFSManagedVersion=0.2.173.2") ELSE (SET "ProjFSManagedVersion=%2") - -SET SolutionConfiguration=%Configuration% - -:: Make the build version available in the DevOps environment. -@echo ##vso[task.setvariable variable=PROJFS_MANAGED_VERSION]%ProjFSManagedVersion% - -SET nuget="%PROJFS_TOOLSDIR%\nuget.exe" -IF NOT EXIST %nuget% ( - mkdir %nuget%\.. - powershell -ExecutionPolicy Bypass -Command "Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile %nuget%" -) - -:: Use vswhere to find the latest VS installation (including prerelease installations) with the msbuild component. -:: See https://github.com/Microsoft/vswhere/wiki/Find-MSBuild -SET vswherever=2.8.4 -%nuget% install vswhere -Version %vswherever% -OutputDirectory %PROJFS_PACKAGESDIR% || exit /b 1 -SET vswhere=%PROJFS_PACKAGESDIR%\vswhere.%vswherever%\tools\vswhere.exe -set WINSDK_BUILD=19041 -echo Checking for VS installation: -echo %vswhere% -all -prerelease -latest -version "[16.4,18.0)" -products * -requires Microsoft.Component.MSBuild Microsoft.VisualStudio.Workload.ManagedDesktop Microsoft.VisualStudio.Workload.NativeDesktop Microsoft.VisualStudio.Component.Windows10SDK.%WINSDK_BUILD% Microsoft.VisualStudio.Component.VC.CLI.Support -property installationPath -for /f "usebackq tokens=*" %%i in (`%vswhere% -all -prerelease -latest -version "[16.4,18.0)" -products * -requires Microsoft.Component.MSBuild Microsoft.VisualStudio.Workload.ManagedDesktop Microsoft.VisualStudio.Workload.NativeDesktop Microsoft.VisualStudio.Component.Windows10SDK.%WINSDK_BUILD% Microsoft.VisualStudio.Component.VC.CLI.Support -property installationPath`) do ( - set VsDir=%%i -) -IF NOT DEFINED VsDir ( - echo All installed Visual Studio instances: - %vswhere% -all -prerelease -products * -format json - echo ERROR: Could not locate a Visual Studio installation with required components. - echo Refer to Readme.md for a list of the required Visual Studio components. - exit /b 10 +ECHO Building ProjFS Managed API (%Configuration%)... +dotnet build "%~dp0\..\ProjectedFSLib.Managed.slnx" -c %Configuration% +IF %ERRORLEVEL% NEQ 0 ( + ECHO Build failed with error %ERRORLEVEL% + EXIT /b %ERRORLEVEL% ) -echo Setting up the VS Developer Command Prompt environment variables from %VsDir% -call "%VsDir%\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 -@rem pushd "%VsDir%" -@rem call "%VsDir%\Common7\Tools\VsDevCmd.bat" -@rem popd - -:: Restore all dependencies and run the build. -pushd "%PROJFS_SRCDIR%" -msbuild /t:Restore ProjectedFSLib.Managed.sln -msbuild ProjectedFSLib.Managed.sln /p:ProjFSManagedVersion=%ProjFSManagedVersion% /p:Configuration=%SolutionConfiguration% /p:Platform=x64 || exit /b 1 -popd +ECHO Build succeeded. ENDLOCAL diff --git a/scripts/InitializeEnvironment.bat b/scripts/InitializeEnvironment.bat index eea4794..0b7dc1e 100644 --- a/scripts/InitializeEnvironment.bat +++ b/scripts/InitializeEnvironment.bat @@ -1,33 +1,26 @@ -@ECHO OFF - -:: Set environment variables for interesting paths that scripts might need access to. -PUSHD %~dp0 -SET PROJFS_SCRIPTSDIR=%CD% -POPD - -CALL :RESOLVEPATH "%PROJFS_SCRIPTSDIR%\.." -SET PROJFS_SRCDIR=%_PARSED_PATH_% - -CALL :RESOLVEPATH "%PROJFS_SRCDIR%\.." -SET PROJFS_ENLISTMENTDIR=%_PARSED_PATH_% - -SET PROJFS_OUTPUTDIR=%PROJFS_ENLISTMENTDIR%\BuildOutput -SET PROJFS_PACKAGESDIR=%PROJFS_ENLISTMENTDIR%\packages -SET PROJFS_PUBLISHDIR=%PROJFS_ENLISTMENTDIR%\Publish -SET PROJFS_TOOLSDIR=%PROJFS_ENLISTMENTDIR%\.tools - -:: Make the path variables available in the DevOps environment. -@echo ##vso[task.setvariable variable=PROJFS_SRCDIR]%PROJFS_SRCDIR% -@echo ##vso[task.setvariable variable=PROJFS_OUTPUTDIR]%PROJFS_OUTPUTDIR% -@echo ##vso[task.setvariable variable=PROJFS_PACKAGESDIR]%PROJFS_PACKAGESDIR% -@echo ##vso[task.setvariable variable=PROJFS_PUBLISHDIR]%PROJFS_PUBLISHDIR% -@echo ##vso[task.setvariable variable=PROJFS_TOOLSDIR]%PROJFS_TOOLSDIR% - -:: Clean up -SET _PARSED_PATH_= - -GOTO :EOF - -:RESOLVEPATH -SET "_PARSED_PATH_=%~f1" -GOTO :EOF +@ECHO OFF + +:: Set environment variables for interesting paths that scripts might need access to. +PUSHD %~dp0 +SET PROJFS_SCRIPTSDIR=%CD% +POPD + +CALL :RESOLVEPATH "%PROJFS_SCRIPTSDIR%\.." +SET PROJFS_ENLISTMENTDIR=%_PARSED_PATH_% + +SET PROJFS_OUTPUTDIR=%PROJFS_ENLISTMENTDIR%\artifacts +SET PROJFS_PACKAGESDIR=%PROJFS_ENLISTMENTDIR%\artifacts\packages + +:: Make the path variables available in the DevOps environment. +@echo ##vso[task.setvariable variable=PROJFS_ENLISTMENTDIR]%PROJFS_ENLISTMENTDIR% +@echo ##vso[task.setvariable variable=PROJFS_OUTPUTDIR]%PROJFS_OUTPUTDIR% +@echo ##vso[task.setvariable variable=PROJFS_PACKAGESDIR]%PROJFS_PACKAGESDIR% + +:: Clean up +SET _PARSED_PATH_= + +GOTO :EOF + +:RESOLVEPATH +SET "_PARSED_PATH_=%~f1" +GOTO :EOF diff --git a/scripts/NukeBuildOutputs.bat b/scripts/NukeBuildOutputs.bat index 17237a4..e916a3a 100644 --- a/scripts/NukeBuildOutputs.bat +++ b/scripts/NukeBuildOutputs.bat @@ -1,23 +1,24 @@ -@ECHO OFF -CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 - -IF EXIST %PROJFS_OUTPUTDIR% ( - ECHO deleting build outputs - rmdir /s /q %PROJFS_OUTPUTDIR% -) ELSE ( - ECHO no build outputs found -) - -IF EXIST %PROJFS_PUBLISHDIR% ( - ECHO deleting published output - rmdir /s /q %PROJFS_PUBLISHDIR% -) ELSE ( - ECHO no published output found -) - -IF EXIST %PROJFS_PACKAGESDIR% ( - ECHO deleting packages - rmdir /s /q %PROJFS_PACKAGESDIR% -) ELSE ( - ECHO no packages found -) +@ECHO OFF +CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 + +IF EXIST %PROJFS_OUTPUTDIR% ( + ECHO deleting artifacts + rmdir /s /q %PROJFS_OUTPUTDIR% +) ELSE ( + ECHO no artifacts found +) + +:: Clean per-project bin/obj directories +FOR %%D IN ( + "%PROJFS_ENLISTMENTDIR%\ProjectedFSLib.Managed\bin" + "%PROJFS_ENLISTMENTDIR%\ProjectedFSLib.Managed\obj" + "%PROJFS_ENLISTMENTDIR%\ProjectedFSLib.Managed.Test\bin" + "%PROJFS_ENLISTMENTDIR%\ProjectedFSLib.Managed.Test\obj" + "%PROJFS_ENLISTMENTDIR%\simpleProviderManaged\bin" + "%PROJFS_ENLISTMENTDIR%\simpleProviderManaged\obj" +) DO ( + IF EXIST %%D ( + ECHO deleting %%D + rmdir /s /q %%D + ) +) diff --git a/scripts/Pack-NuGet.ps1 b/scripts/Pack-NuGet.ps1 new file mode 100644 index 0000000..2ac0d67 --- /dev/null +++ b/scripts/Pack-NuGet.ps1 @@ -0,0 +1,60 @@ +<# +.SYNOPSIS + Builds the ProjFS Managed API and creates NuGet packages. + +.DESCRIPTION + Builds the ProjectedFSLib.Managed project in Release configuration + and produces .nupkg and .snupkg packages in the artifacts/ directory. + +.PARAMETER Configuration + Build configuration (default: Release). + +.PARAMETER OutputDirectory + Directory for NuGet packages (default: artifacts/packages). + +.EXAMPLE + .\Pack-NuGet.ps1 + .\Pack-NuGet.ps1 -Configuration Debug +#> +[CmdletBinding()] +param( + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [string]$OutputDirectory = (Join-Path $PSScriptRoot '..\artifacts\packages') +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path $PSScriptRoot -Parent +$project = Join-Path $repoRoot 'ProjectedFSLib.Managed' 'ProjectedFSLib.Managed.csproj' + +if (-not (Test-Path $project)) { + Write-Error "Project not found: $project" + exit 1 +} + +# Ensure output directory exists +if (-not (Test-Path $OutputDirectory)) { + New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null +} + +Write-Host "Building and packing $Configuration..." -ForegroundColor Cyan + +dotnet pack $project ` + -c $Configuration ` + -o $OutputDirectory ` + --include-symbols ` + -p:SymbolPackageFormat=snupkg + +if ($LASTEXITCODE -ne 0) { + Write-Error "dotnet pack failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE +} + +$packages = Get-ChildItem $OutputDirectory -Filter '*.nupkg' | Sort-Object LastWriteTime -Descending +Write-Host "`nPackages created:" -ForegroundColor Green +foreach ($pkg in $packages) { + Write-Host " $($pkg.Name)" -ForegroundColor Green +} diff --git a/scripts/RunTests.bat b/scripts/RunTests.bat index 72a25d5..202dcd0 100644 --- a/scripts/RunTests.bat +++ b/scripts/RunTests.bat @@ -1,21 +1,13 @@ @ECHO OFF -CALL %~dp0\InitializeEnvironment.bat || EXIT /b 10 +SETLOCAL IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1") -set RESULT_FRAMEWORK=0 -set TESTDIR=%PROJFS_OUTPUTDIR%\ProjectedFSLib.Managed.Test\bin\AnyCPU\%Configuration%\net48 -pushd %TESTDIR% -%TESTDIR%\ProjectedFSLib.Managed.Test.exe --params ProviderExe=%PROJFS_OUTPUTDIR%\SimpleProviderManaged\bin\AnyCPU\%Configuration%\net48\SimpleProviderManaged.exe || set RESULT_FRAMEWORK=1 -popd +ECHO Running ProjFS Managed API tests (%Configuration%)... +dotnet test "%~dp0\..\ProjectedFSLib.Managed.slnx" -c %Configuration% --no-build +IF %ERRORLEVEL% NEQ 0 ( + ECHO Tests failed with error %ERRORLEVEL% + EXIT /b %ERRORLEVEL% +) -set RESULT_CORE=0 -set TESTDIR=%PROJFS_OUTPUTDIR%\ProjectedFSLib.Managed.Test\bin\AnyCPU\%Configuration%\netcoreapp3.1 -pushd %TESTDIR% -%TESTDIR%\ProjectedFSLib.Managed.Test.exe --params ProviderExe=%PROJFS_OUTPUTDIR%\SimpleProviderManaged\bin\AnyCPU\%Configuration%\netcoreapp3.1\SimpleProviderManaged.exe || set RESULT_CORE=1 -popd - -set RESULT=0 -if "%RESULT_FRAMEWORK% %RESULT_CORE%" neq "0 0" set RESULT=1 - -exit /b %RESULT% \ No newline at end of file +ECHO All tests passed. \ No newline at end of file diff --git a/simpleProviderManaged/SimpleProviderManaged.csproj b/simpleProviderManaged/SimpleProviderManaged.csproj index 7a89872..3f6a843 100644 --- a/simpleProviderManaged/SimpleProviderManaged.csproj +++ b/simpleProviderManaged/SimpleProviderManaged.csproj @@ -1,30 +1,24 @@  - - - net48;netcoreapp3.1 + net48;net8.0-windows10.0.17763.0;net10.0-windows10.0.17763.0 Exe x64 - MinimumRecommendedRules.ruleset true - - - - - + + + + - - + - diff --git a/simpleProviderManaged/app.config b/simpleProviderManaged/app.config deleted file mode 100644 index 3dbff35..0000000 --- a/simpleProviderManaged/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - -