ファイル データの提供

プロバイダーが最初に仮想化ルートを作成すると、ローカル システムでは空になります。 つまり、バッキング データ ストア内の項目がまだディスクにキャッシュされていません。 項目が開かれると、ProjFS はプロバイダーに情報を要求して、それらの項目のプレースホルダーをローカル ファイル システムに作成できるようにします。 アイテムのコンテンツにアクセスすると、ProjFS はプロバイダーにそれらのコンテンツを要求します。 その結果、ユーザーの観点からは、仮想化されたファイルとディレクトリは、ローカル ファイル システムに既に存在する通常のファイルやディレクトリと同様に表示されます。

プレースホルダーの作成

アプリケーションが仮想化されたファイルへのハンドルを開こうとすると、ProjFS は、ディスク上にまだ存在しないパスの各項目に対して PRJ_GET_PLACEHOLDER_INFO_CB コールバックを呼び出します。 たとえば、アプリケーションが を開C:\virtRoot\dir1\dir2\file.txtこうとしたが、ディスク上にパスC:\virtRoot\dir1のみが存在する場合、プロバイダーは のコールバックを受け取り、 のC:\virtRoot\dir1\dir2\file.txtコールバックC:\virtRoot\dir1\dir2を受け取ります。

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 を使用して、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 が確認する必要がある場合、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,485,760 の 10 MB のデータを要求する場合、プロバイダーは PrjWriteFileData への 10 回の呼び出しでデータを指定し、それぞれ 1 MB を送信することを選択できます。

プロバイダーは、ファイルの長さまで、要求された範囲を超える値を自由に指定することもできます。 プロバイダーが提供する範囲は、要求された範囲をカバーする必要があります。 たとえば、コールバックが byteOffset 4096 から 長さ 1,052,672 の 1 MB のデータを要求し、ファイルの合計サイズが 10 MB の場合、プロバイダーはオフセット 0 から 2 MB のデータを返すように選択できます。

バッファーの配置に関する考慮事項

ProjFS は、データをローカル ファイル システムに書き込む必要のある呼び出し元からの FILE_OBJECT を使用します。 ただし、ProjFS では、そのFILE_OBJECTがバッファー処理された I/O 用に開かれたか、バッファーされていない I/O に対して開かれたかを制御できません。 バッファーされていない I/O 用にFILE_OBJECTを開いた場合、ファイルへの読み取りと書き込みは、特定のアラインメント要件に準拠している必要があります。 プロバイダーは、次の 2 つの操作を行うことで、これらのアラインメント要件を満たすことができます。

  1. PrjAllocateAlignedBuffer を使用して、PrjWriteFileData の buffer パラメーターを渡すバッファーを割り当てます。
  2. PrjWriteFileDatabyteOffset パラメーターと length パラメーターが、ストレージ デバイスのアラインメント要件の整数倍数であることを確認します (byteOffset + の長さがファイルの末尾と等しい場合、length パラメーターはこの要件を満たす必要はありません)。 プロバイダーは 、PrjGetVirtualizationInstanceInfo を使用して、ストレージ デバイスの配置要件を取得できます。

ProjFS は、適切な配置を計算するためにプロバイダーに残します。 これは、 PRJ_GET_FILE_DATA_CB コールバックを処理する場合、プロバイダーは、コールバックを完了する前に、要求されたデータの合計の一部を返す複数の PrjWriteFileData 呼び出しで要求されたデータを返すように選択する可能性があるためです。

プロバイダーが PrjWriteFileData の 1 回の呼び出しを使用して、ファイル全体 ( つまり、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;
}