Partilhar via


Fornecendo dados de arquivo

Quando um provedor cria pela primeira vez uma raiz de virtualização, ela fica vazia no sistema local. Ou seja, nenhum dos itens no armazenamento 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 esse conteúdo ao provedor. O resultado é que, da perspetiva do usuário, os arquivos e diretórios virtualizados parecem semelhantes aos arquivos e diretórios normais que já residem no sistema de arquivos local.

Criação de espaço reservado

Quando uma aplicação tenta abrir um identificador para um ficheiro virtualizado, o ProjFS chama o retorno de chamada PRJ_GET_PLACEHOLDER_INFO_CB para cada item do caminho que ainda não existe no disco. Por exemplo, se uma aplicação 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\dir2e, em seguida, para C:\virtRoot\dir1\dir2\file.txt.

Quando o ProjFS chama o callback PRJ_GET_PLACEHOLDER_INFO_CB do provedor, o provedor executa as seguintes ações:

  1. O provedor determina se o nome solicitado existe em seu armazenamento de backup. O provedor deve usar PrjFileNameCompare como a rotina de comparação ao pesquisar o seu armazenamento de suporte para verificar se o nome solicitado existe no armazenamento de suporte. Caso contrário, o provedor retorna HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) do retorno de chamada.

  2. Se o nome solicitado existir no armazenamento de backup, o provedor preencherá uma estrutura de PRJ_PLACEHOLDER_INFO com os metadados do sistema de arquivos do item e chamará PrjWritePlaceholderInfo para enviar os dados para o ProjFS. O ProjFS usará essas informações para criar um espaço reservado no sistema de arquivos local para o item.

    O ProjFS usará quaisquer sinalizadores FILE_ATTRIBUTE que o provedor definir no membro FileBasicInfo.FileAttributes do PRJ_PLACEHOLDER_INFO, exceto FILE_ATTRIBUTE_DIRECTORY; o ProjFS definirá o valor correto para FILE_ATTRIBUTE_DIRECTORY no membro FileBasicInfo.FileAttributes de acordo com o valor fornecido no membro FileBasicInfo.IsDirectory.

    Se o armazenamento de suporte oferecer suporte a links simbólicos, o provedor deverá usar PrjWritePlaceholderInfo2 para enviar os dados do espaço reservado para o ProjFS. PrjWritePlaceholderInfo2 suporta 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 como descrito acima para PrjWritePlaceholderInfo. O exemplo a seguir ilustra como usar PrjWritePlaceholderInfo2 para fornecer suporte para links simbólicos.

    Observe que PrjWritePlaceholderInfo2 é suportado a partir do 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 o arquivo, o ProjFS chama o retorno de chamada PRJ_GET_FILE_DATA_CB para esse item para solicitar que o provedor forneça o conteúdo do arquivo. O provedor recupera os dados do arquivo de seu armazenamento de backup e usa PrjWriteFileData para enviar os dados para o sistema de arquivos local.

Quando o ProjFS chama esta função de retorno, o membro FilePathName do parâmetro callbackData fornece o nome que o arquivo tinha quando o seu espaço reservado foi criado. Ou seja, se o arquivo tiver sido renomeado desde que o seu marcador de posição foi criado, o callback fornece o nome original (pré-renomeação), e não o nome atual (pós-renomeação). Se necessário, o provedor pode usar o VersionInfo membro do parâmetro callbackData para determinar quais dados do arquivo estão sendo solicitados.

Para obter mais informações sobre como o membro PRJ_CALLBACK_DATA VersionInfo pode ser usado, consulte a documentação de PRJ_PLACEHOLDER_VERSION_INFO e o tópico Manipulação de Alterações no Modo de Exibição.

O provedor pode dividir o intervalo de dados solicitado no retorno de chamada PRJ_GET_FILE_DATA_CB 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 callback solicitar 10 MB de dados de byteOffset 0 para comprimento 10.485.760, o provedor pode 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. A gama fornecida pelo fornecedor deve abranger a gama solicitada. Por exemplo, se o callback solicitar 1 MB de dados do byteOffset 4096 com um comprimento de 1.052.672, e o arquivo tiver um tamanho total de 10 MB, o provedor poderá optar por retornar 2 MB de dados a partir do offset 0.

Considerações sobre alinhamento de buffer

O ProjFS utiliza o FILE_OBJECT do chamador que solicita os dados para gravar os dados no sistema de arquivos local. No entanto, o ProjFS não pode controlar se esse FILE_OBJECT foi aberto para E/S em buffer ou sem buffer. Se o FILE_OBJECT foi aberto para E/S sem buffer, as leituras e gravações no arquivo devem atender a determinados requisitos de alinhamento. O provedor pode atender a esses requisitos de alinhamento fazendo duas coisas:

  1. Use PrjAllocateAlignedBuffer para alocar o buffer para passar no parâmetro buffer do PrjWriteFileData's.
  2. Certifique-se de que o byteOffset e os parâmetros de comprimento de de PrjWriteFileData sejam múltiplos inteiros do requisito de alinhamento do dispositivo de armazenamento (observe que o parâmetro de comprimento de não precisa atender a esse requisito se byteOffset + comprimento for igual ao final do arquivo). O provedor pode usar PrjGetVirtualizationInstanceInfo para recuperar o requisito de alinhamento do dispositivo de armazenamento.

O ProjFS deixa a cargo do provedor 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 através de várias chamadas PrjWriteFileData, cada uma retornando parte do total dos dados solicitados, antes de concluir o retorno de chamada.

Se o provedor vai usar uma única chamada para PrjWriteFileData para gravar o arquivo inteiro, ou seja, de byteOffset = 0 para comprimento = 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, ainda deve usar PrjAllocateAlignedBuffer para garantir que o buffer atenda aos requisitos de alinhamento do dispositivo de armazenamento.

Consulte o tópico sobre a bufferização de ficheiros para obter mais informações sobre E/S em buffer vs. E/S 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;
}