Fornecer dados de arquivo
Quando um provedor cria uma raiz de virtualização pela primeira vez, ele fica vazio no sistema local. Ou seja, nenhum dos itens no repositório de dados de backup ainda foi armazenado em cache no disco. À medida que os itens são abertos, o ProjFS solicita informações do provedor para permitir que espaços reservados para esses itens sejam criados no sistema de arquivos local. À medida que o conteúdo do item é acessado, o ProjFS solicita esses conteúdos do provedor. O resultado é que, da perspectiva do usuário, arquivos e diretórios virtualizados aparecem semelhantes aos arquivos e diretórios normais que já residem no sistema de arquivos local.
Criação de espaço reservado
Quando um aplicativo tenta abrir um identificador para um arquivo virtualizado, o ProjFS chama o PRJ_GET_PLACEHOLDER_INFO_CB retorno de chamada para cada item do caminho que ainda não existe no disco. Por exemplo, se um aplicativo tentar abrir C:\virtRoot\dir1\dir2\file.txt
, mas apenas o caminho C:\virtRoot\dir1
existir no disco, o provedor receberá um retorno de chamada para C:\virtRoot\dir1\dir2
, em seguida, para C:\virtRoot\dir1\dir2\file.txt
.
Quando o ProjFS chama o retorno de chamada PRJ_GET_PLACEHOLDER_INFO_CB do provedor, o provedor executa as seguintes ações:
O provedor determina se o nome solicitado existe em seu repositório de backup. O provedor deve usar PrjFileNameCompare como a rotina de comparação ao pesquisar seu repositório de backup para determinar se o nome solicitado existe no repositório de backup. Se isso não acontecer, o provedor retornará HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) do retorno de chamada.
Se o nome solicitado existir no repositório de backup, o provedor preencherá uma estrutura PRJ_PLACEHOLDER_INFO com os metadados do sistema de arquivos do item e chamará PrjWritePlaceholderInfo para enviar os dados ao ProjFS. O ProjFS usará essas informações para criar um espaço reservado no sistema de arquivos local para o item.
O ProjFS usará qualquer FILE_ATTRIBUTE sinaliza os conjuntos de provedores no membro FileBasicInfo.FileAttributes do PRJ_PLACEHOLDER_INFO exceto para FILE_ATTRIBUTE_DIRECTORY; ele definirá o valor correto para FILE_ATTRIBUTE_DIRECTORY no membro FileBasicInfo.FileAttributes de acordo com o valor fornecido do membro FileBasicInfo.IsDirectory .
Se o repositório de backup der suporte a links simbólicos, o provedor deverá usar PrjWritePlaceholderInfo2 para enviar os dados de espaço reservado para o ProjFS. PrjWritePlaceholderInfo2 dá suporte a uma entrada de buffer extra que permite ao provedor especificar que o espaço reservado é um link simbólico e qual é seu destino. Caso contrário, ele se comporta conforme descrito acima para PrjWritePlaceholderInfo. O exemplo a seguir ilustra como usar PrjWritePlaceholderInfo2 para fornecer suporte a links simbólicos.
Observe que PrjWritePlaceholderInfo2 tem suporte a partir de Windows 10, versão 2004. Um provedor deve investigar a existência de PrjWritePlaceholderInfo2, por exemplo, usando 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;
}
Fornecendo conteúdo do arquivo
Quando o ProjFS precisa garantir que um arquivo virtualizado contenha dados, como quando um aplicativo tenta ler do arquivo, o ProjFS chama o PRJ_GET_FILE_DATA_CB retorno de chamada para esse item para solicitar que o provedor forneça o conteúdo do arquivo. O provedor recupera os dados do arquivo de seu repositório de backup e usa PrjWriteFileData para enviar os dados para o sistema de arquivos local.
Quando o ProjFS chama esse retorno de chamada, o membro FilePathName do parâmetro callbackData fornece o nome que o arquivo tinha quando seu espaço reservado foi criado. Ou seja, se o arquivo tiver sido renomeado desde que seu espaço reservado foi criado, o retorno de chamada fornecerá o nome original (pré-renomeação), não o nome atual (pós-renomeação). Se necessário, o provedor pode usar o membro VersionInfo do parâmetro callbackData para determinar quais dados do arquivo estão sendo solicitados.
Para obter mais informações sobre como o membro VersionInfo do PRJ_CALLBACK_DATA pode ser usado, consulte a documentação para PRJ_PLACEHOLDER_VERSION_INFO e o tópico Manipulando alterações de exibição.
O provedor tem permissão para dividir o intervalo de dados solicitado no PRJ_GET_FILE_DATA_CB retorno de chamada em várias chamadas para PrjWriteFileData, cada uma fornecendo uma parte do intervalo solicitado. No entanto, o provedor deve fornecer todo o intervalo solicitado antes de concluir o retorno de chamada. Por exemplo, se o retorno de chamada solicitar 10 MB de dados de byteOffset 0 para o comprimento 10.485.760, o provedor poderá optar por fornecer os dados em 10 chamadas para PrjWriteFileData, cada uma enviando 1 MB.
O provedor também é livre para fornecer mais do que o intervalo solicitado, até o comprimento do arquivo. O intervalo fornecido pelo provedor deve abranger o intervalo solicitado. Por exemplo, se o retorno de chamada solicitar 1 MB de dados do byteOffset 4096 para o comprimento 1.052.672 e o arquivo tiver um tamanho total de 10 MB, o provedor poderá optar por retornar 2 MB de dados começando no deslocamento 0.
Considerações sobre o alinhamento do buffer
O ProjFS usa o FILE_OBJECT do chamador que exige que os dados escrevam os dados no sistema de arquivos local. No entanto, o ProjFS não pode controlar se essa FILE_OBJECT foi aberta para E/S armazenada em buffer ou não. Se o FILE_OBJECT foi aberto para E/S não armazenada em buffer, as leituras e gravações no arquivo deverão aderir a determinados requisitos de alinhamento. O provedor pode atender a esses requisitos de alinhamento fazendo duas coisas:
- Use PrjAllocateAlignedBuffer para alocar o buffer para passar o parâmetro de buffer de PrjWriteFileData.
- Verifique se os parâmetros byteOffset e length de PrjWriteFileData são múltiplos inteiros do requisito de alinhamento do dispositivo de armazenamento (observe que o parâmetro length não precisa atender a esse requisito se ocomprimentobyteOffset + for igual ao final do arquivo). O provedor pode usar PrjGetVirtualizationInstanceInfo para recuperar o requisito de alinhamento do dispositivo de armazenamento.
O ProjFS deixa o provedor para calcular o alinhamento adequado. Isso ocorre porque, ao processar um retorno de chamada PRJ_GET_FILE_DATA_CB, o provedor pode optar por retornar os dados solicitados em várias chamadas PrjWriteFileData , cada uma retornando parte do total de dados solicitados, antes de concluir o retorno de chamada.
Se o provedor usar uma única chamada para PrjWriteFileData para gravar o arquivo inteiro, ou seja, de byteOffset = 0 para tamanho = tamanho do arquivo ou para retornar o intervalo exato solicitado no retorno de chamada PRJ_GET_FILE_DATA_CB , o provedor não precisa fazer nenhum cálculo de alinhamento. No entanto, ele ainda deve usar PrjAllocateAlignedBuffer para garantir que o buffer atenda aos requisitos de alinhamento do dispositivo de armazenamento.
Consulte o tópico Buffer de arquivos para obter mais informações sobre E/S em buffer versus sem buffer.
// 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;
}