다음을 통해 공유


파일 데이터 제공

공급자가 처음 가상화 루트를 만들면 로컬 시스템에서 비어 있습니다. 즉, 백업 데이터 저장소의 항목 중 어느 것도 아직 디스크에 캐시되지 않았습니다. 항목이 열리면 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_DIRECTORY 제외하면 공급자가 PRJ_PLACEHOLDER_INFO FileBasicInfo.FileAttributes 멤버에 설정한 FILE_ATTRIBUTE 플래그를 사용합니다. FileBasicInfo.IsDirectory 멤버의 제공된 값에 따라 FileBasicInfo.FileAttributes 멤버의 FILE_ATTRIBUTE_DIRECTORY 올바른 값을 설정합니다.

    백업 저장소가 기호 링크를 지원하는 경우 공급자는 PrjWritePlaceholderInfo2 를 사용하여 자리 표시자 데이터를 ProjFS로 보내야 합니다. PrjWritePlaceholderInfo2 는 공급자가 자리 표시자가 기호 링크이고 대상이 무엇인지 지정할 수 있는 추가 버퍼 입력을 지원합니다. 그렇지 않으면 PrjWritePlaceholderInfo에 대해 위에서 설명한 대로 동작합니다. 다음 샘플에서는 PrjWritePlaceholderInfo2 를 사용하여 기호 링크에 대한 지원을 제공하는 방법을 보여 줍니다.

    PrjWritePlaceholderInfo2는 Windows 10 버전 2004에서 지원됩니다. 공급자는 GetProcAddress를 사용하여 instance PrjWritePlaceholderInfo2가 있는지 확인해야 합니다.

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는 해당 항목에 대한 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에 대한 여러 호출로 분할할 수 있습니다. 그러나 공급자는 콜백을 완료하기 전에 요청된 전체 범위를 제공해야 합니다. 예를 들어 콜백이 길이 10,485,760에 대해 byteOffset 0에서 10MB의 데이터를 요청하는 경우 공급자는 각각 1MB를 보내는 PrjWriteFileData에 대한 10개 호출에서 데이터를 제공하도록 선택할 수 있습니다.

또한 공급자는 요청된 범위보다 더 많은 파일 길이까지 자유롭게 제공할 수 있습니다. 공급자가 제공하는 범위가 요청된 범위를 포함해야 합니다. 예를 들어 콜백이 길이 1,052,672에 대해 byteOffset 4096에서 1MB의 데이터를 요청하고 파일의 총 크기가 10MB인 경우 공급자는 오프셋 0부터 2MB의 데이터를 반환하도록 선택할 수 있습니다.

버퍼 맞춤 고려 사항

ProjFS는 데이터를 로컬 파일 시스템에 쓰도록 요구하는 호출자의 FILE_OBJECT 사용합니다. 그러나 ProjFS는 버퍼링된 I/O 또는 버퍼링되지 않은 I/O에 대해 해당 FILE_OBJECT 열렸는지 여부를 제어할 수 없습니다. 버퍼되지 않은 I/O에 대해 FILE_OBJECT 연 경우 파일에 대한 읽기 및 쓰기는 특정 맞춤 요구 사항을 준수해야 합니다. 공급자는 다음 두 가지 작업을 수행하여 이러한 맞춤 요구 사항을 충족할 수 있습니다.

  1. PrjAllocateAlignedBuffer를 사용하여 PrjWriteFileData의 버퍼 매개 변수를 전달할 버퍼를 할당합니다.
  2. PrjWriteFileDatabyteOffset길이 매개 변수가 스토리지 디바이스 맞춤 요구 사항의 정수 배수인지 확인합니다(byteOffset + 길이가 파일의 끝과 같으면 length 매개 변수가 이 요구 사항을 충족할 필요가 없음). 공급자는 PrjGetVirtualizationInstanceInfo 를 사용하여 스토리지 디바이스의 맞춤 요구 사항을 검색할 수 있습니다.

ProjFS는 적절한 맞춤을 계산하기 위해 공급자에게 둡니다. 이는 PRJ_GET_FILE_DATA_CB 콜백을 처리할 때 공급자가 콜백을 완료하기 전에 요청된 총 데이터의 일부를 반환하는 여러 PrjWriteFileData 호출에서 요청된 데이터를 반환하도록 선택할 수 있기 때문입니다.

공급자가 PrjWriteFileData 에 대한 단일 호출을 사용하여 전체 파일(예: byteOffset = 0에서 길이 = 파일 크기까지)을 작성하거나 PRJ_GET_FILE_DATA_CB 콜백에서 요청된 정확한 범위를 반환하려는 경우 공급자는 정렬 계산을 수행할 필요가 없습니다. 그러나 버퍼가 스토리지 디바이스의 맞춤 요구 사항을 충족하는지 확인하려면 여전히 PrjAllocateAlignedBuffer를 사용해야 합니다.

버퍼링된 I/O와 버퍼링되지 않은 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;
}