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

539 lines
19 KiB
C++

#include "stdafx.h"
using namespace regfs;
RegfsProvider::RegfsProvider()
{
// Record that this class implements the optional Notify callback.
this->SetOptionalMethods(OptionalMethods::Notify);
}
/*++
Description:
ProjFS invokes this callback to request metadata information for a file or a directory.
If the file or directory exists in the provider's namespace, the provider calls
WritePlaceholderInfo() to give ProjFS the info for the requested name.
The metadata information ProjFS supports includes:
Mandatory:
FileBasicInfo.IsDirectory - the requested name is a file or directory.
Mandatory for files:
FileBasicInfo.FileSize - file size in bytes.
Optional:
VersionInfo - A 256 bytes ID which can be used to distinguish different versions of file content
for one file name.
FileBasicInfo.CreationTime/LastAccessTime/LastWriteTime/ChangeTime - timestamps of the file.
FileBasicInfo.FileAttributes - File Attributes.
Optional and less commonly used:
EaInformation - Extended attribute (EA) information.
SecurityInformation - Security descriptor information.
StreamsInformation - Alternate data stream information.
See also PRJ_PLACEHOLDER_INFORMATION structure in projectedfslib.h for more details.
If the file or directory doesn't exist in the provider's namespace, this callback returns
HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).
If the provider is unable to process the request (e.g. due to network error) or it wants to block
the request, this callback returns an appropriate HRESULT error code.
Below is a list of example commands that demonstrate how GetPlaceholderInfo is called.
Assuming z:\reg doesn't exist, run 'regfs.exe z:\reg' to create the root.
Now start another command line window, 'cd z:\reg' then run below commands in sequence.
1) cd HKEY_LOCAL_MACHINE
The first time you cd into a folder that exists in provider's namespace, GetPlaceholderInfo is
called with CallbackData->FilePathName = "HKEY_LOCAL_MACHINE". This callback will cause an
on-disk placeholder file called "HKEY_LOCAL_MACHINE" to be created under z:\reg.
2) cd .. & cd HKEY_LOCAL_MACHINE
The second and subsequent time you cd into a folder that exists in provider's namespace, this
callback will not be called because the on-disk placeholder for HKEY_LOCAL_MACHINE already exists.
3) mkdir newfolder
If _readonlyNamespace is true, GetPlaceholderInfo returns ERROR_ACCESS_DENIED, so the mkdir command
reports "Access is denied" and the placeholder is not created. If _readonlyNamespace is false,
GetPlaceholderInfo returns ERROR_FILE_NOT_FOUND so the command succeeds and newfolder is created.
4) cd SOFTWARE\Microsoft\.NETFramework
The first time you cd into a deep path, GetPlaceholderInfo is called repeatedly with the
following CallbackData->FilePathName values:
1) "HKEY_LOCAL_MACHINE\SOFTWARE"
2) "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft"
3) "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework"
--*/
HRESULT RegfsProvider::GetPlaceholderInfo(
_In_ const PRJ_CALLBACK_DATA* CallbackData
)
{
wprintf(L"\n----> %hs: Path [%s] triggered by [%s] \n",
__FUNCTION__, CallbackData->FilePathName, CallbackData->TriggeringProcessImageFileName);
bool isKey;
INT64 valSize = 0;
// Find out whether the specified path exists in the registry, and whether it is a key or a value.
if (_regOps.DoesKeyExist(CallbackData->FilePathName))
{
isKey = true;
}
else if (_regOps.DoesValueExist(CallbackData->FilePathName, valSize))
{
isKey = false;
}
else
{
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, ERROR_FILE_NOT_FOUND);
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
// Format the PRJ_PLACEHOLDER_INFO structure. For registry keys we create directories on disk,
// for values we create files.
PRJ_PLACEHOLDER_INFO placeholderInfo = {};
placeholderInfo.FileBasicInfo.IsDirectory = isKey;
placeholderInfo.FileBasicInfo.FileSize = valSize;
// Create the on-disk placeholder.
HRESULT hr = this->WritePlaceholderInfo(CallbackData->FilePathName,
&placeholderInfo,
sizeof(placeholderInfo));
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, hr);
return hr;
}
/*++
Description:
ProjFS invokes this callback to tell the provider that a directory enumeration is starting.
A user-mode tool usually uses FindFirstFile/FindNextFile APIs to enumerate a directory. Those
APIs send QueryDirectory requests to the file system. If the enumeration is for a placeholder
folder, ProjFS intercepts and blocks those requests. Then ProjFS invokes the registered directory
enumeration callbacks (StartDirEnum, GetDirEnum, EndDirEnum) to get a list of names in provider's
namespace, merges those names with the names physically on disk under that folder, then unblocks
the enumeration requests and returns the merged list to the caller.
--*/
HRESULT RegfsProvider::StartDirEnum(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ const GUID* EnumerationId
)
{
wprintf(L"\n----> %hs: Path [%s] triggerred by [%s]\n",
__FUNCTION__, CallbackData->FilePathName, CallbackData->TriggeringProcessImageFileName);
// For each dir enum session, ProjFS sends:
// one StartEnumCallback
// one or more GetEnumCallbacks
// one EndEnumCallback
// These callbacks will use the same value for EnumerationId for the same session.
// Here we map the EnumerationId to a new DirInfo object.
_activeEnumSessions[*EnumerationId] = std::make_unique<DirInfo>(CallbackData->FilePathName);
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, S_OK);
return S_OK;
};
/*++
Description:
ProjFS invokes this callback to tell the provider that a directory enumeration is over. This
gives the provider the opportunity to release any resources it set up for the enumeration.
--*/
HRESULT RegfsProvider::EndDirEnum(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ const GUID* EnumerationId
)
{
wprintf(L"\n----> %hs\n",
__FUNCTION__);
// Get rid of the DirInfo object we created in StartDirEnum.
_activeEnumSessions.erase(*EnumerationId);
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, S_OK);
return S_OK;
};
/*++
Description:
ProjFS invokes this callback to request a list of files and directories under the given directory.
To handle this callback, RegFS calls DirInfo->FillFileEntry/FillDirEntry for each matching file
or directory.
If the SearchExpression argument specifies something that doesn't exist in provider's namespace,
or if the directory being enumerated is empty, the provider just returns S_OK without storing
anything in DirEntryBufferHandle. ProjFS will return the correct error code to the caller.
Below is a list of example commands that will invoke GetDirectoryEntries callbacks.
These assume you've cd'd into the virtualization root folder.
Command CallbackData->FilePathName SearchExpression
------------------------------------------------------------------------
dir ""(root folder) *
dir foo* ""(root folder) foo*
dir H + TAB ""(root folder) H*
dir HKEY_LOCAL_MACHINE ""(root folder) HKEY_LOCAL_MACHINE
dir HKEY_LOCAL_MACHIN? ""(root folder) HKEY_LOCAL_MACHIN>
In the last example, the ">" character is the special wildcard value DOS_QM. ProjFS handles this
and other special file system wildcard values in its PrjFileNameMatch and PrjDoesNameContainWildCards
APIs.
--*/
HRESULT RegfsProvider::GetDirEnum(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ const GUID* EnumerationId,
_In_opt_ PCWSTR SearchExpression,
_In_ PRJ_DIR_ENTRY_BUFFER_HANDLE DirEntryBufferHandle
)
{
wprintf(L"\n----> %hs: Path [%s] SearchExpression [%s]\n",
__FUNCTION__, CallbackData->FilePathName, SearchExpression);
HRESULT hr = S_OK;
// Get the correct enumeration session from our map.
auto it = _activeEnumSessions.find(*EnumerationId);
if (it == _activeEnumSessions.end())
{
// We were asked for an enumeration we don't know about.
hr = E_INVALIDARG;
wprintf(L"<---- %hs: Unknown enumeration ID\n",
__FUNCTION__);
return hr;
}
// Get out our DirInfo helper object, which manages the context for this enumeration.
auto& dirInfo = it->second;
// If the enumeration is restarting, reset our bookkeeping information.
if (CallbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN)
{
dirInfo->Reset();
}
if (!dirInfo->EntriesFilled())
{
// The DirInfo associated with the current session hasn't been initialized yet. This method
// will enumerate the subkeys and values in the registry key corresponding to CallbackData->FilePathName.
// For each one that matches SearchExpression it will create an entry to return to ProjFS
// and store it in the DirInfo object.
HRESULT hr = PopulateDirInfoForPath(CallbackData->FilePathName,
dirInfo.get(),
SearchExpression);
if (FAILED(hr))
{
wprintf(L"<---- %hs: Failed to populate dirInfo: 0x%08x\n",
__FUNCTION__, hr);
return hr;
}
// This will ensure the entries in the DirInfo are sorted the way the file system expects.
dirInfo->SortEntriesAndMarkFilled();
}
// Return our directory entries to ProjFS.
while (dirInfo->CurrentIsValid())
{
// ProjFS allocates a fixed size buffer then invokes this callback. The callback needs to
// call PrjFillDirEntryBuffer to fill as many entries as possible until the buffer is full.
if (S_OK != PrjFillDirEntryBuffer(dirInfo->CurrentFileName(),
&dirInfo->CurrentBasicInfo(),
DirEntryBufferHandle))
{
break;
}
// Only move the current entry cursor after the entry was successfully filled, so that we
// can start from the correct index in the next GetDirEnum callback for this enumeration
// session.
dirInfo->MoveNext();
}
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, hr);
return hr;
};
// Populates a DirInfo object with directory and file entires that represent the registry keys and
// values that are under a given key.
HRESULT RegfsProvider::PopulateDirInfoForPath (
_In_ std::wstring relativePath,
_In_ DirInfo* dirInfo,
_In_ std::wstring searchExpression
)
{
RegEntries entries;
// Get a list of the registry keys and values under the given key.
HRESULT hr = _regOps.EnumerateKey(relativePath.c_str(), entries);
if (FAILED(hr))
{
wprintf(L"%hs: Could not enumerate key: 0x%08x",
__FUNCTION__, hr);
return hr;
}
// Store each registry key that matches searchExpression as a directory entry.
for (auto subKey : entries.SubKeys)
{
if (PrjFileNameMatch(subKey.Name.c_str(), searchExpression.c_str()))
{
dirInfo->FillDirEntry(subKey.Name.c_str());
}
}
// Store each registry value that matches searchExpression as a file entry.
for (auto val : entries.Values)
{
if (PrjFileNameMatch(val.Name.c_str(), searchExpression.c_str()))
{
dirInfo->FillFileEntry(val.Name.c_str(), val.Size);
}
}
return hr;
}
/*++
Description:
ProjFS invokes this callback to request the contents of a file.
To handle this callback, the provider issues one or more calls to WriteFileData() to give
ProjFS the file content. ProjFS will convert the on-disk placeholder into a hydrated placeholder,
populated with the file contents. Afterward, subsequent file reads will no longer invoke the
GetFileStream callback.
If multiple threads read the same placeholder file simultaneously, ProjFS ensures that the provider
receives only one GetFileStream callback.
If the provider is unable to process the request, it return an appropriate error code. The caller
who issued the read will receive an error, and the next file read for the same file will invoke
GetFileStream again.
Below is a list of example commands that will invoke GetFileStream callbacks.
Assume there's a file named 'testfile' in provider's namespace:
type testfile
echo 123>>testfile
echo 123>testfile
--*/
HRESULT RegfsProvider::GetFileData(
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ UINT64 ByteOffset,
_In_ UINT32 Length
)
{
wprintf(L"\n----> %hs: Path [%s] triggered by [%s]\n",
__FUNCTION__, CallbackData->FilePathName, CallbackData->TriggeringProcessImageFileName);
HRESULT hr = S_OK;
// We're going to need alignment information that is stored in the instance to service this
// callback.
PRJ_VIRTUALIZATION_INSTANCE_INFO instanceInfo;
hr = PrjGetVirtualizationInstanceInfo(_instanceHandle,
&instanceInfo);
if (FAILED(hr))
{
wprintf(L"<---- %hs: PrjGetVirtualizationInstanceInfo: 0x%08x\n",
__FUNCTION__, hr);
return hr;
}
// Allocate a buffer that adheres to the machine's memory alignment. We have to do this in case
// the caller who caused this callback to be invoked is performing non-cached I/O. For more
// details, see the topic "Providing File Data" in the ProjFS documentation.
void* writeBuffer = nullptr;
writeBuffer = PrjAllocateAlignedBuffer(_instanceHandle,
Length);
if (writeBuffer == nullptr)
{
wprintf(L"<---- %hs: Could not allocate write buffer.\n",
__FUNCTION__);
return E_OUTOFMEMORY;
}
// Read the data out of the registry.
if (!_regOps.ReadValue(CallbackData->FilePathName, reinterpret_cast<PBYTE>(writeBuffer), Length))
{
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
PrjFreeAlignedBuffer(writeBuffer);
wprintf(L"<---- %hs: Failed to read from registry.\n",
__FUNCTION__);
return hr;
}
// Call ProjFS to write the data we read from the registry into the on-disk placeholder.
hr = this->WriteFileData(&CallbackData->DataStreamId,
reinterpret_cast<PVOID>(writeBuffer),
ByteOffset,
Length);
if (FAILED(hr))
{
// If this callback returns an error, ProjFS will return this error code to the thread that
// issued the file read, and the target file will remain an empty placeholder.
wprintf(L"%hs: failed to write file for [%s]: 0x%08x\n",
__FUNCTION__, CallbackData->FilePathName, hr);
}
// Free the memory-aligned buffer we allocated.
PrjFreeAlignedBuffer(writeBuffer);
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, hr);
return hr;
}
/*++
Description:
ProjFS invokes this callback to deliver notifications of file system operations.
The provider can specify which notifications it wishes to receive by filling out an array of
PRJ_NOTIFICATION_MAPPING structures that it feeds to PrjStartVirtualizing in the PRJ_STARTVIRTUALIZING_OPTIONS
structure.
For the following notifications the provider can return a failure code. This will prevent the
associated file system operation from taking place.
PRJ_NOTIFICATION_FILE_OPENED
PRJ_NOTIFICATION_PRE_DELETE
PRJ_NOTIFICATION_PRE_RENAME
PRJ_NOTIFICATION_PRE_SET_HARDLINK
PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL
All other notifications are informational only.
See also the PRJ_NOTIFICATION_TYPE enum for more details about the notification types.
--*/
HRESULT RegfsProvider::Notify (
_In_ const PRJ_CALLBACK_DATA* CallbackData,
_In_ BOOLEAN IsDirectory,
_In_ PRJ_NOTIFICATION NotificationType,
_In_opt_ PCWSTR DestinationFileName,
_Inout_ PRJ_NOTIFICATION_PARAMETERS* NotificationParameters
)
{
HRESULT hr = S_OK;
wprintf(L"\n----> %hs: Path [%s] triggered by [%s]",
__FUNCTION__, CallbackData->FilePathName, CallbackData->TriggeringProcessImageFileName);
wprintf(L"\n---- Notification: 0x%08x\n", NotificationType);
switch (NotificationType)
{
case PRJ_NOTIFICATION_FILE_OPENED:
break;
case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_MODIFIED:
case PRJ_NOTIFICATION_FILE_OVERWRITTEN:
wprintf(L"\n ----- [%s] was modified\n", CallbackData->FilePathName);
break;
case PRJ_NOTIFICATION_NEW_FILE_CREATED:
wprintf(L"\n ----- [%s] was created\n", CallbackData->FilePathName);
break;
case PRJ_NOTIFICATION_FILE_RENAMED:
wprintf(L"\n ----- [%s] -> [%s]\n", CallbackData->FilePathName,
DestinationFileName);
break;
case PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED:
wprintf(L"\n ----- [%s] was deleted\n", CallbackData->FilePathName);
break;
case PRJ_NOTIFICATION_PRE_RENAME:
if (_readonlyNamespace)
{
// Block file renames.
hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
wprintf(L"\n ----- rename request for [%s] was rejected \n", CallbackData->FilePathName);
}
else
{
wprintf(L"\n ----- rename request for [%s] \n", CallbackData->FilePathName);
}
break;
case PRJ_NOTIFICATION_PRE_DELETE:
if (_readonlyNamespace)
{
// Block file deletion. We must return a particular NTSTATUS to ensure the file system
// properly recognizes that this is a deny-delete.
hr = HRESULT_FROM_NT(STATUS_CANNOT_DELETE);
wprintf(L"\n ----- delete request for [%s] was rejected \n", CallbackData->FilePathName);
}
else
{
wprintf(L"\n ----- delete request for [%s] \n", CallbackData->FilePathName);
}
break;
case PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL:
break;
default:
wprintf(L"%hs: Unexpected notification\n",
__FUNCTION__);
}
wprintf(L"<---- %hs: return 0x%08x\n",
__FUNCTION__, hr);
return hr;
}