2025-11-28 00:35:46 +09:00

347 lines
13 KiB
C++

#pragma once
#include "stdafx.h"
using namespace regfs;
///////////////////////////////////////////////////////////////////////////////////////////////
// Virtualization instance control API wrappers.
///////////////////////////////////////////////////////////////////////////////////////////////
HRESULT VirtualizationInstance::Start(LPCWSTR rootPath,
PRJ_STARTVIRTUALIZING_OPTIONS* options)
{
_rootPath = rootPath;
if (options == nullptr)
{
_options = PRJ_STARTVIRTUALIZING_OPTIONS();
}
else
{
_options = *options;
}
// Ensure we have a virtualization root directory that is stamped with an instance ID using the
// PrjMarkDirectoryAsPlaceholder API.
HRESULT hr = EnsureVirtualizationRoot();
if (FAILED(hr))
{
return hr;
}
// Register the required C callbacks.
_callbacks.StartDirectoryEnumerationCallback = StartDirEnumCallback_C;
_callbacks.EndDirectoryEnumerationCallback = EndDirEnumCallback_C;
_callbacks.GetDirectoryEnumerationCallback = GetDirEnumCallback_C;
_callbacks.GetPlaceholderInfoCallback = GetPlaceholderInfoCallback_C;
_callbacks.GetFileDataCallback = GetFileDataCallback_C;
// Register the optional C callbacks.
// Register Notify if the provider says it implemented it, unless the provider didn't create any
// notification mappings.
if (((GetOptionalMethods() & OptionalMethods::Notify) != OptionalMethods::None) &&
(_options.NotificationMappingsCount != 0))
{
_callbacks.NotificationCallback = NotificationCallback_C;
}
// Register QueryFileName if the provider says it implemented it.
if ((GetOptionalMethods() & OptionalMethods::QueryFileName) != OptionalMethods::None)
{
_callbacks.QueryFileNameCallback = QueryFileName_C;
}
// Register CancelCommand if the provider says it implemented it.
if ((GetOptionalMethods() & OptionalMethods::CancelCommand) != OptionalMethods::None)
{
_callbacks.CancelCommandCallback = CancelCommand_C;
}
// Start the virtualization instance. Note that we pass our 'this' pointer in the instanceContext
// parameter. ProjFS will send this context back to us when calling our callbacks, which will
// allow them to fish out this instance of the VirtualizationInstance class and call our methods.
hr = PrjStartVirtualizing(_rootPath.c_str(),
&_callbacks,
this,
&_options,
&_instanceHandle);
return hr;
}
void VirtualizationInstance::Stop()
{
PrjStopVirtualizing(_instanceHandle);
}
HRESULT VirtualizationInstance::WritePlaceholderInfo(LPCWSTR relativePath,
PRJ_PLACEHOLDER_INFO* placeholderInfo,
DWORD length)
{
return PrjWritePlaceholderInfo(_instanceHandle,
relativePath,
placeholderInfo,
length);
}
HRESULT VirtualizationInstance::WriteFileData(LPCGUID streamId,
PVOID buffer,
ULONGLONG byteOffset,
DWORD length)
{
return PrjWriteFileData(_instanceHandle,
streamId,
buffer,
byteOffset,
length);
}
/////////////////////////////////////////////////////////////////
// Default implementations for non-pure virtual callback methods.
/////////////////////////////////////////////////////////////////
HRESULT VirtualizationInstance::Notify(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ BOOLEAN IsDirectory,
_In_ PRJ_NOTIFICATION NotificationType,
_In_opt_ PCWSTR DestinationFileName,
_Inout_ PRJ_NOTIFICATION_PARAMETERS* NotificationParameters
)
{
// If the derived provider implements this callback they must call SetOptionalMethods(OptionalMethods::Notify)
// to cause the callback to be registered when starting the virtualization instance.
throw NotImplemented();
}
HRESULT VirtualizationInstance::QueryFileName(
_In_ const PRJ_CALLBACK_DATA* CallbackData
)
{
// If the derived provider implements this callback they must call SetOptionalMethods(OptionalMethods::QueryFileName)
// to cause the callback to be registered when starting the virtualization instance.
throw NotImplemented();
}
void VirtualizationInstance::CancelCommand(
_In_ const PRJ_CALLBACK_DATA* CallbackData
)
{
// If the derived provider implements this callback they must call SetOptionalMethods(OptionalMethods::CancelCommand)
// to cause the callback to be registered when starting the virtualization instance.
throw NotImplemented();
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Getter/setter.
///////////////////////////////////////////////////////////////////////////////////////////////
// Gets the set of optional methods the derived class has indicated that it has implemented.
OptionalMethods VirtualizationInstance::GetOptionalMethods()
{
return _implementedOptionalMethods;
}
// Sets the set of optional methods the derived class wants to indicate that it has implemented.
void VirtualizationInstance::SetOptionalMethods(OptionalMethods optionalMethodsToSet)
{
_implementedOptionalMethods |= optionalMethodsToSet;
}
// Ensures that the directory _rootPath, which we want to use as the virtualization root, exists.
//
// If the _rootPath directory does not yet exist, this routine:
// 1. Creates the _rootPath directory.
// 2. Generates a virtualization instance ID.
// 3. Stores the ID in a file in the directory to mark it as the virtualization root.
// 4. Marks the directory as the virtualization root, using the PrjMarkDirectoryAsPlaceholder API
// and the generated ID.
//
// If the _rootPath directory already exists, this routine checks for the file that should contain
// the stored ID. If it exists, we assume this is our virtualization root.
HRESULT VirtualizationInstance::EnsureVirtualizationRoot()
{
DWORD win32error;
GUID instanceId;
// Try creating our virtualization root.
if (!::CreateDirectory(_rootPath.c_str(), nullptr))
{
win32error = GetLastError();
if (win32error == ERROR_ALREADY_EXISTS)
{
// The virtualization root already exists. Check for the stored virtualization instance
// ID.
auto idFileHandle = ::CreateFile2((_rootPath + _instanceIdFile).c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
OPEN_EXISTING,
nullptr);
if (idFileHandle == INVALID_HANDLE_VALUE)
{
win32error = GetLastError();
return HRESULT_FROM_WIN32(win32error);
}
DWORD bytesRead;
if (!::ReadFile(idFileHandle, &instanceId, sizeof(GUID), &bytesRead, nullptr))
{
win32error = GetLastError();
::CloseHandle(idFileHandle);
return HRESULT_FROM_WIN32(win32error);
}
// If we didn't read sizeof(GUID) bytes then this might not be our directory.
if (bytesRead != sizeof(GUID))
{
::CloseHandle(idFileHandle);
return HRESULT_FROM_WIN32(ERROR_BAD_CONFIGURATION);
}
::CloseHandle(idFileHandle);
}
else
{
return HRESULT_FROM_WIN32(win32error);
}
}
else
{
// We created a new directory. Create a virtualization instance ID.
::CoCreateGuid(&instanceId);
// Store the ID in the directory as a way for us to detect that this is our directory in
// the future.
auto idFileHandle = ::CreateFile2((_rootPath + _instanceIdFile).c_str(),
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
CREATE_NEW,
nullptr);
if (idFileHandle == INVALID_HANDLE_VALUE)
{
win32error = GetLastError();
return HRESULT_FROM_WIN32(win32error);
}
DWORD bytesWritten;
if (!::WriteFile(idFileHandle, &instanceId, sizeof(GUID), &bytesWritten, nullptr))
{
win32error = GetLastError();
::CloseHandle(idFileHandle);
return HRESULT_FROM_WIN32(win32error);
}
::CloseHandle(idFileHandle);
// Mark the directory as the virtualization root.
auto hr = PrjMarkDirectoryAsPlaceholder(_rootPath.c_str(),
nullptr,
nullptr,
&instanceId);
if (FAILED(hr))
{
// Let's do a best-effort attempt to clean up the directory.
DeleteFile((_rootPath + _instanceIdFile).c_str());
RemoveDirectory(_rootPath.c_str());
return hr;
}
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// The remaining methods are the callbacks we'll register with ProjFS. Each one simply gets our
// 'this' pointer out of CallbackData->InstanceContext and invokes the corresponding method.
///////////////////////////////////////////////////////////////////////////////////////////////
HRESULT VirtualizationInstance::StartDirEnumCallback_C (
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ const GUID* EnumerationId
)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->StartDirEnum(CallbackData, EnumerationId);
}
HRESULT VirtualizationInstance::EndDirEnumCallback_C (
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ const GUID* EnumerationId
)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->EndDirEnum(CallbackData, EnumerationId);
}
HRESULT VirtualizationInstance::GetDirEnumCallback_C (
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ const GUID* EnumerationId,
_In_opt_ PCWSTR SearchExpression,
_In_ PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle
)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->GetDirEnum(CallbackData,
EnumerationId,
SearchExpression,
DirEntryBufferHandle);
}
HRESULT VirtualizationInstance::GetPlaceholderInfoCallback_C(
_In_ const PRJ_CALLBACK_DATA* CallbackData
)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->GetPlaceholderInfo(CallbackData);
}
HRESULT VirtualizationInstance::GetFileDataCallback_C(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ UINT64 ByteOffset,
_In_ UINT32 Length
)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->GetFileData(CallbackData,
ByteOffset,
Length);
}
HRESULT VirtualizationInstance::NotificationCallback_C(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ BOOLEAN IsDirectory,
_In_ PRJ_NOTIFICATION NotificationType,
_In_opt_ PCWSTR DestinationFileName,
_Inout_ PRJ_NOTIFICATION_PARAMETERS* NotificationParameters
)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->Notify(CallbackData,
IsDirectory,
NotificationType,
DestinationFileName,
NotificationParameters);
}
HRESULT VirtualizationInstance::QueryFileName_C(
_In_ const PRJ_CALLBACK_DATA* CallbackData)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
return instance->QueryFileName(CallbackData);
}
void VirtualizationInstance::CancelCommand_C(
_In_ const PRJ_CALLBACK_DATA* CallbackData)
{
auto instance = reinterpret_cast<VirtualizationInstance*>(CallbackData->InstanceContext);
instance->CancelCommand(CallbackData);
}