Предоставление данных файлов

Когда поставщик впервые создает корневой каталог виртуализации, он пуст в локальной системе. То есть ни один из элементов резервного хранилища данных еще не кэширован на диск. При открытии элементов 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 поставщика, поставщик выполняет следующие действия:

  1. Поставщик определяет, существует ли запрошенное имя в его резервном хранилище. Поставщик должен использовать PrjFileNameCompare в качестве процедуры сравнения при поиске в резервном хранилище, чтобы определить, существует ли запрошенное имя в резервном хранилище. Если это не так, поставщик возвращает HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND) из обратного вызова.

  2. Если запрошенное имя существует в резервном хранилище, поставщик заполняет структуру PRJ_PLACEHOLDER_INFO метаданными файловой системы элемента и вызывает PrjWritePlaceholderInfo для отправки данных в ProjFS. ProjFS будет использовать эти сведения для создания заполнителя в локальной файловой системе для элемента.

    ProjFS будет использовать любые FILE_ATTRIBUTE флаги, которые поставщик устанавливает в элементе FileBasicInfo.FileAttributes PRJ_PLACEHOLDER_INFO, за исключением FILE_ATTRIBUTE_DIRECTORY; он задает правильное значение для FILE_ATTRIBUTE_DIRECTORY в элементе FileBasicInfo.FileAttributes в соответствии с предоставленным значением элемента FileBasicInfo.IsDirectory .

    Если резервное хранилище поддерживает символьные ссылки, поставщик должен использовать PrjWritePlaceholderInfo2 для отправки данных заполнителей в ProjFS. PrjWritePlaceholderInfo2 поддерживает дополнительные входные данные буфера, которые позволяют поставщику указать, что заполнитель является символьной ссылкой и каким является его целевой объект. В противном случае он ведет себя так, как описано выше для PrjWritePlaceholderInfo. В следующем примере показано, как использовать PrjWritePlaceholderInfo2 для поддержки символьных ссылок.

    Обратите внимание, что PrjWritePlaceholderInfo2 поддерживается с Windows 10 версии 2004. Поставщик должен проверять существование 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 вызывает этот обратный вызов, член FilePathName параметра callbackData предоставляет имя файла при создании его заполнителя. То есть, если файл был переименован с момента создания заполнителя, обратный вызов предоставляет исходное (предварительное переименование) имя, а не текущее (после переименования). При необходимости поставщик может использовать член VersionInfo параметра callbackData , чтобы определить, какие данные файла запрашиваются.

Дополнительные сведения об использовании элемента VersionInfo PRJ_CALLBACK_DATA см. в документации по PRJ_PLACEHOLDER_VERSION_INFO и в разделе Обработка изменений представления .

Поставщик может разделить диапазон данных, запрашиваемых в PRJ_GET_FILE_DATA_CB обратном вызове, на несколько вызовов PrjWriteFileData, каждый из которых предоставляет часть запрошенного диапазона. Однако поставщик должен предоставить весь запрошенный диапазон перед завершением обратного вызова. Например, если обратный вызов запрашивает 10 МБ данных из byteOffset 0 для длины 10 485 760, поставщик может предоставить данные в 10 вызовах PrjWriteFileData, каждый из которых отправляет 1 МБ.

Поставщик также может предоставить больше, чем запрошенный диапазон, вплоть до длины файла. Диапазон, который предоставляет поставщик, должен охватывать запрошенный диапазон. Например, если обратный вызов запрашивает 1 МБ данных из byteOffset 4096 длиной 1 052 672 и файл имеет общий размер 10 МБ, поставщик может вернуть 2 МБ данных, начиная со смещения 0.

Рекомендации по выравниванию буфера

ProjFS использует FILE_OBJECT от вызывающей стороны, которая требует, чтобы данные записывались в локальную файловую систему. Однако ProjFS не может контролировать, была ли эта FILE_OBJECT открыта для буферизованных или небуферизованных операций ввода-вывода. Если FILE_OBJECT был открыт для небуферированных операций ввода-вывода, операции чтения и записи в файл должны соответствовать определенным требованиям к выравниванию. Поставщик может выполнить эти требования к выравниванию, выполнив два действия:

  1. Используйте PrjAllocateAlignedBuffer, чтобы выделить буфер для передачи в параметре bufferPrjWriteFileData.
  2. Убедитесь, что параметры byteOffset и lengthprjWriteFileData являются целыми числами, кратными требованию к выравниванию запоминающего устройства (обратите внимание, что параметр length не должен соответствовать этому требованию, еслидлинаbyteOffset + равна концу файла). Поставщик может использовать PrjGetVirtualizationInstanceInfo для получения требования к выравниванию устройства хранения.

ProjFS оставляет поставщику возможность вычислить правильное выравнивание. Это связано с тем, что при обработке PRJ_GET_FILE_DATA_CB обратного вызова поставщик может выбрать возврат запрошенных данных в нескольких вызовах PrjWriteFileData , каждый из которых возвращает часть общего объема запрошенных данных, прежде чем завершить обратный вызов.

Если поставщик собирается использовать один вызов PrjWriteFileData для записи всего файла, т. е. от byteOffset = 0 до length = size файла, или для возврата точного диапазона, запрошенного в обратном вызове PRJ_GET_FILE_DATA_CB , поставщику не нужно выполнять вычисления выравнивания. Однако он по-прежнему должен использовать PrjAllocateAlignedBuffer , чтобы убедиться, что буфер соответствует требованиям к выравниванию устройства хранения.

Дополнительные сведения о буферизованных и небуферированных ввода-выводах см. в разделе Буферизация файлов .

//  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;
}