提供檔案資料
當提供者第一次建立虛擬化根時,本機系統上是空的。 也就是說,備份資料存放區中尚未將任何專案快取到磁片。 當專案開啟時,ProjFS 會向提供者要求資訊,以允許在本機檔案系統中建立這些專案的預留位置。 當存取專案內容時,ProjFS 會向提供者要求這些內容。 結果是從使用者的觀點來看,虛擬化檔案和目錄看起來類似于本機檔案系統上已位於一般檔案和目錄。
預留位置建立
當應用程式嘗試開啟虛擬化檔案的控制碼時,ProjFS 會針對磁片上尚未存在之路徑的每個專案呼叫 PRJ_GET_PLACEHOLDER_INFO_CB 回呼。 例如,如果應用程式嘗試開啟 C:\virtRoot\dir1\dir2\file.txt
,但只有路徑 C:\virtRoot\dir1
存在於磁片上,則提供者會收到 的 C:\virtRoot\dir1\dir2
回呼,然後針對 C:\virtRoot\dir1\dir2\file.txt
。
當 ProjFS 呼叫提供者 的PRJ_GET_PLACEHOLDER_INFO_CB回 呼時,提供者會執行下列動作:
提供者會判斷要求的名稱是否存在於其備份存放區中。 提供者應該在搜尋備份存放區時,使用 PrjFileNameCompare 作為比較常式,以判斷要求的名稱是否存在於備份存放區中。 如果沒有,提供者會從回呼傳回HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND) 。
如果要求的名稱存在於備份存放區中,提供者會以專案的檔案系統中繼資料填入 PRJ_PLACEHOLDER_INFO 結構,並呼叫 PrjWritePlaceholderInfo 將資料傳送至 ProjFS 。 ProjFS 會使用該資訊在專案的本機檔案系統中建立預留位置。
ProjFS 會使用提供者在PRJ_PLACEHOLDER_INFO 的 FileBasicInfo.FileAttributes成員中設定的任何FILE_ATTRIBUTE旗標,但FILE_ATTRIBUTE_DIRECTORY除外;它會根據FileBasicInfo.IsDirectory成員所提供的值,在FileBasicInfo.FileAttributes成員中設定FILE_ATTRIBUTE_DIRECTORY的正確值。
如果支援存放區支援符號連結,提供者必須使用 PrjWritePlaceholderInfo2 將預留位置資料傳送至 ProjFS。 PrjWritePlaceholderInfo2 支援額外的緩衝區輸入,可讓提供者指定預留位置是符號連結及其目標。 否則,其行為如上所述 ,適用于 PrjWritePlaceholderInfo。 下列範例說明如何使用 PrjWritePlaceholderInfo2 來支援符號連結。
請注意,自 Windows 10 2004 版起,支援PrjWritePlaceholderInfo2。 提供者應該探查 PrjWritePlaceholderInfo2是否存在,例如使用 GetProcAddress。
HRESULT
MyGetPlaceholderInfoCallback(
_In_ const PRJ_CALLBACK_DATA* callbackData
)
{
// MyGetItemInfo is a routine the provider might implement to get
// information from its backing store for a given file path. The first
// parameter is an _In_ parameter that supplies the name to look for.
// If the item exists the routine provides the file information in the
// remaining parameters, all of which are annotated _Out_:
// * 2nd parameter: the name as it appears in the backing store
// * 3rd-9th parameters: basic file info
// * 10th parameter: if the item is a symbolic link, a pointer to a
// NULL-terminated string identifying the link's target
//
// Note that the routine returns the name that is in the backing
// store. This is because the input file path may not be in the same
// case as what is in the backing store. The provider should create
// the placeholder with the name it has in the backing store.
//
// Note also that this example does not provide anything beyond basic
// file information and a possible symbolic link target.
HRESULT hr;
WCHAR* backingStoreName = NULL;
WCHAR* symlinkTarget = NULL;
PRJ_PLACEHOLDER_INFO placeholderInfo = {};
hr = MyGetItemInfo(callbackData->FilePathName,
&backingStoreName,
&placeholderInfo.FileBasicInfo.IsDirectory,
&placeholderInfo.FileBasicInfo.FileSize,
&placeholderInfo.FileBasicInfo.CreationTime,
&placeholderInfo.FileBasicInfo.LastAccessTime,
&placeholderInfo.FileBasicInfo.LastWriteTime,
&placeholderInfo.FileBasicInfo.ChangeTime,
&placeholderInfo.FileBasicInfo.FileAttributes,
&symlinkTarget);
if (FAILED(hr))
{
// If callbackData->FilePathName doesn't exist in our backing store then
// MyGetItemInfo should HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).
// If this is some other error, e.g. E_OUTOFMEMORY because MyGetItemInfo
// couldn't allocate space for backingStoreName, we return that.
return hr;
}
// If the file path is for a symbolic link, pass that in with the placeholder info.
if (symlinkTarget != NULL)
{
PRJ_EXTENDED_INFO extraInfo = {};
extraInfo.InfoType = PRJ_EXT_INFO_SYMLINK;
extraInfo.Symlink.TargetName = symlinkTarget;
// If this returns an error we'll want to return that error from the callback.
hr = PrjWritePlaceholderInfo2(callbackData->NamespaceVirtualizationContext,
backingStoreName,
&placeholderInfo,
sizeof(placeholderInfo),
&extraInfo);
}
else
{
// If this returns an error we'll want to return that error from the callback.
hr = PrjWritePlaceholderInfo(callbackData->NamespaceVirtualizationContext,
backingStoreName,
&placeholderInfo,
sizeof(placeholderInfo));
}
free(backingStoreName);
if (symlinkTarget != NULL)
{
free(symlinkTarget);
}
return hr;
}
提供檔案內容
當 ProjFS 需要確保虛擬化檔案包含資料時,例如當應用程式嘗試從檔案讀取時,ProjFS 會呼叫該專案 的PRJ_GET_FILE_DATA_CB 回呼,要求提供者提供檔案的內容。 提供者會從其備份存放區擷取檔案的資料,並使用 PrjWriteFileData 將資料傳送至本機檔案系統。
當 ProjFS 呼叫此回呼時,callbackData參數的FilePathName成員會提供建立檔案預留位置時所擁有的名稱。 也就是說,如果檔案自建立預留位置之後重新命名,回呼會提供原始 (預先重新命名) 名稱,而不是目前的 (重新命名後) 名稱。 如有必要,提供者可以使用callbackData參數的VersionInfo成員來判斷所要求的檔案資料。
如需如何使用 PRJ_CALLBACK_DATA VersionInfo 成員的詳細資訊,請參閱 PRJ_PLACEHOLDER_VERSION_INFO 的檔和 處理檢視變更 主題。
允許提供者將 PRJ_GET_FILE_DATA_CB 回呼中要求的資料範圍分割成 多個 PrjWriteFileData呼叫,每個呼叫都會提供要求範圍的一部分。 不過,提供者必須先提供整個要求的範圍,才能完成回呼。 例如,如果回呼從 byteOffset 0 要求 10 MB 的資料 長度 為 10,485,760,則提供者可以選擇在 10 次 呼叫 PrjWriteFileData中提供資料,每一次傳送 1 MB。
提供者也可以免費提供超過要求的範圍,最多可達檔案的長度。 提供者提供的範圍必須涵蓋要求的範圍。 例如,如果回呼從 byteOffset 4096 要求 1 MB 的資料 長度 為 1,052,672,而檔案的大小總計為 10 MB,提供者可以選擇從位移 0 開始傳回 2 MB 的資料。
緩衝區對齊考慮
ProjFS 會使用呼叫端的 FILE_OBJECT ,要求資料將資料寫入本機檔案系統。 不過,ProjFS 無法控制已針對緩衝或未緩衝 I/O 開啟該FILE_OBJECT。 如果已針對未緩衝的 I/O 開啟FILE_OBJECT,則對檔案的讀取和寫入必須符合特定對齊需求。 提供者可以透過執行兩件事來符合這些對齊需求:
- 使用 PrjAllocateAlignedBuffer 來配置緩衝區,以傳入 PrjWriteFileData的 緩衝區 參數。
- 請確定PrjWriteFileData的byteOffset和length參數是儲存裝置對齊需求的整數倍數, (請注意,如果byteOffset + 長度等於檔案) 的結尾,則長度參數不需要符合此需求。 提供者可以使用 PrjGetVirtualizationInstanceInfo 來擷取存放裝置的對齊需求。
ProjFS 會讓提供者計算適當的對齊方式。 這是因為在處理 PRJ_GET_FILE_DATA_CB回 呼時,提供者可能會選擇在多個 PrjWriteFileData 呼叫之間傳回要求的資料,每個呼叫都會傳回要求的總計資料一部分,再完成回呼。
如果提供者將使用 PrjWriteFileData 的單一呼叫來寫入整個檔案,也就是從 byteOffset = 0 到 長度 = 檔案大小,或傳回 PRJ_GET_FILE_DATA_CB回 呼中要求的確切範圍,提供者就不需要執行任何對齊計算。 不過,它仍然必須使用 PrjAllocateAlignedBuffer ,以確保 緩衝區 符合存放裝置的對齊需求。
如需緩衝與未緩衝 I/O 的詳細資訊,請參閱 檔案緩衝 主題。
// BlockAlignTruncate(): Aligns P on the previous V boundary (V must be != 0).
#define BlockAlignTruncate(P,V) ((P) & (0-((UINT64)(V))))
// This sample illustrates both returning the entire requested range in a
// single call to PrjWriteFileData(), and splitting it up into smaller
// units. Note that the provider must return all the requested data before
// completing the PRJ_GET_FILE_DATA_CB callback with S_OK.
HRESULT
MyGetFileDataCallback(
_In_ const PRJ_CALLBACK_DATA* callbackData,
_In_ UINT64 byteOffset,
_In_ UINT32 length
)
{
HRESULT hr;
// For the purposes of this sample our provider has a 1 MB limit to how
// much data it can return at once (perhaps its backing store imposes such
// a limit).
UINT64 writeStartOffset;
UINT32 writeLength;
if (length <= 1024*1024)
{
// The range requested in the callback is less than 1MB, so we can return
// the data in a single call, without doing any alignment calculations.
writeStartOffset = byteOffset;
writeLength = length;
}
else
{
// The range requested is more than 1MB. Retrieve the device alignment
// and calculate a transfer size that conforms to the device alignment and
// is <= 1MB.
PRJ_VIRTUALIZATION_INSTANCE_INFO instanceInfo;
UINT32 infoSize = sizeof(instanceInfo);
hr = PrjGetVirtualizationInstanceInfo(callbackData->NamespaceVirtualizationContext,
&infoSize,
&instanceInfo);
if (FAILED(hr))
{
return hr;
}
// The first transfer will start at the beginning of the requested range,
// which is guaranteed to have the correct alignment.
writeStartOffset = byteOffset;
// Ensure our transfer size is aligned to the device alignment, and is
// no larger than 1 MB (note this assumes the device alignment is less
// than 1 MB).
UINT64 writeEndOffset = BlockAlignTruncate(writeStartOffset + 1024*1024,
instanceInfo->WriteAlignment);
assert(writeEndOffset > 0);
assert(writeEndOffset > writeStartOffset);
writeLength = writeEndOffset - writeStartOffset;
}
// Allocate a buffer that adheres to the needed memory alignment.
void* writeBuffer = NULL;
writeBuffer = PrjAllocateAlignedBuffer(callbackData->NamespaceVirtualizationContext,
writeLength);
if (writeBuffer == NULL)
{
return E_OUTOFMEMORY;
}
do
{
// MyGetFileDataFromStore is a routine the provider might implement to copy
// data for the specified file from the provider's backing store to a
// buffer. The routine finds the file located at callbackData->FilePathName
// and copies writeLength bytes of its data, starting at writeStartOffset,
// to the buffer pointed to by writeBuffer.
hr = MyGetFileDataFromStore(callbackData->FilePathName,
writeStartOffset,
writeLength,
writeBuffer);
if (FAILED(hr))
{
PrjFreeAlignedBuffer(writeBuffer);
return hr;
}
// Write the data to the file in the local file system.
hr = PrjWriteFileData(callbackData->NamespaceVirtualizationContext,
callbackData->DataStreamId,
writeBuffer,
writeStartOffset,
writeLength);
if (FAILED(hr))
{
PrjFreeAlignedBuffer(writeBuffer);
return hr;
}
// The length parameter to the callback is guaranteed to be either
// correctly aligned or to result in a write to the end of the file.
length -= writeLength;
if (length < writeLength)
{
writeLength = length;
}
}
while (writeLength > 0);
PrjFreeAlignedBuffer(writeBuffer);
return hr;
}