次の方法で共有


ファイルシステム操作通知

列挙の処理、ファイル メタデータの返却、およびファイル コンテンツの取得に必要なコールバックに加えて、プロバイダーはオプションで PRJ_NOTIFICATION_CB コールバックを PrjStartVirtualizing への呼び出しに登録できます。 このコールバックは、インスタンスの仮想化ルートの下にあるファイルとディレクトリに対して実行されたファイル システム操作の通知を受け取ります。 通知の一部は「操作後」通知であり、操作が完了したことをプロバイダーに通知します。 その他の通知は「操作前」通知であり、操作が行われる前にプロバイダーに通知されることを意味します。 操作前の通知では、プロバイダーはコールバックからエラー コードを返すことで操作を拒否できます。 これにより、操作は失敗し、エラー コードが返されます。

受信する通知を指定する方法

プロバイダーが仮想化インスタンスを起動するときに PRJ_NOTIFICATION_CB の実装を提供する場合は、受信する通知も指定する必要があります。 プロバイダーが登録できる通知は、 PRJ_NOTIFICATION 列挙型で定義されます。

プロバイダーが受信する通知を指定する場合、1 つ以上の PRJ_NOTIFICATION_MAPPING 構造体の配列を使用して指定します。 これらは「通知マッピング」を定義します。 通知マッピングは、「通知ルート」と呼ばれるディレクトリと、ProjFS がそのディレクトリとその子孫に送信する必要があるビット マスクとして表現される一連の通知との間のペアリングです。 通知マッピングは単一のファイルに対しても確立できます。 通知マッピングで指定されたファイルまたはディレクトリは、プロバイダーが PrjStartVirtualizing を呼び出す時点で既に存在している必要はありません。プロバイダーは任意のパスを指定でき、マッピングは作成された場合に適用されます。

プロバイダーが複数の通知マッピングを指定し、その一部が他のマッピングの子孫である場合、マッピングは降順で指定する必要があります。 より深いレベルの通知マッピングは、その子孫のより高いレベルのマッピングをオーバーライドします。

プロバイダーが通知マッピングのセットを指定しない場合、ProjFS はデフォルトで、仮想化ルートの下にあるすべてのファイルとディレクトリに対して PRJ_NOTIFY_FILE_OPENED、PRJ_NOTIFY_NEW_FILE_CREATED、および PRJ_NOTIFY_FILE_OVERWRITTEN を送信します。

次のコード スニペットは、仮想化インスタンスを起動するときに通知のセットを登録する方法を示しています。

PRJ_CALLBACKS callbackTable;

// Supply required callbacks.
callbackTable.StartDirectoryEnumerationCallback = MyStartEnumCallback;
callbackTable.EndDirectoryEnumerationCallback = MyEndEnumCallback;
callbackTable.GetDirectoryEnumerationCallback = MyGetEnumCallback;
callbackTable.GetPlaceholderInfoCallback = MyGetPlaceholderInfoCallback;
callbackTable.GetFileDataCallback = MyGetFileDataCallback;

// The rest of the callbacks are optional.  We want notifications, so specify
// our notification callback.
callbackTable.QueryFileNameCallback = nullptr;
callbackTable.NotificationCallback = MyNotificationCallback;
callbackTable.CancelCommandCallback = nullptr;

// Assume the provider has created a new virtualization root at C:\VirtRoot and
// initialized it with this layout:
//
//     C:\VirtRoot
//     +--- baz
//     \--- foo
//          +--- subdir1
//          \--- subdir2
//
// The provider wants these notifications:
// * Notification of new file/directory creates for all locations within the
//   virtualization root that are not subject to one of the following more
//   specific notification mappings.
// * Notification of new file/directory creates, file opens, post-renames, and
//   pre- and post-deletes for C:\VirtRoot\foo
// * No notifications for C:\VirtRoot\foo\subdir1
PRJ_STARTVIRTUALIZING_OPTIONS startOpts = {};
PRJ_NOTIFICATION_MAPPING notificationMappings[3];

// Configure default notifications - notify of new files for most of the tree.
notificationMappings[0].NotificationRoot = L"";
notificationMappings[0].NotificationBitMask = PRJ_NOTIFY_NEW_FILE_CREATED;

// Override default notifications - notify of new files, opened files, and file
// deletes for C:\VirtRoot\foo and its descendants.
notificationMappings[1].NotificationRoot = L"foo";
notificationMappings[1].NotificationBitMask = PRJ_NOTIFY_NEW_FILE_CREATED |
                                              PRJ_NOTIFY_FILE_OPENED |
                                              PRJ_NOTIFY_PRE_DELETE |
                                              PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED |
                                              PRJ_NOTIFY_FILE_RENAMED;

// Override all notifications for C:\VirtRoot\foo\subdir1 and its descendants.
notificationMappings[2].NotificationRoot = L"foo\\subdir1";
notificationMappings[2].NotificationBitMask = PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS;

startOpts.NotificationMappings = notificationMappings;
startOpts.NotificationMappingsCount = ARRAYSIZE(notificationMapping);

// Start the instance and provide our notification mappings.
HRESULT hr;
PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT instanceHandle;
hr = PrjStartVirtualizing(rootName,
                          &callbackTable,
                          nullptr,
                          &startOpts,
                          &instanceHandle);
if (FAILED(hr))
{
    wprintf(L"Failed to start the virtualization instance (0x%08x)\n", hr);
    return;
}

通知の受信

ProjFS は、プロバイダーの PRJ_NOTIFICATION_CB コールバックを呼び出して、ファイル システム操作の通知を送信します。 これは、プロバイダーが登録した操作ごとに実行されます。 上記の例のように、プロバイダーが PRJ_NOTIFY_NEW_FILE_CREATED | PRJ_NOTIFY_FILE_OPENED | PRJ_NOTIFY_PRE_DELETE | PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED | PRJ_NOTIFY_FILE_RENAMED に登録されていて、アプリケーションが通知ルートに新しいファイルを作成した場合、ProjFS は新しいファイルのパス名と PRJ_NOTIFICATION_NEW_FILE_CREATED に設定された notification パラメータを使用して PRJ_NOTIFICATION_CB コールバックを呼び出します。

ProjFS は、関連するファイル システム操作が行われる前に、次のリストの通知を送信します。 プロバイダーが PRJ_NOTIFICATION_CB コールバックから失敗コードを返す場合、ファイル システム操作は失敗し、プロバイダーが指定した失敗コードが返されます。

  • PRJ_NOTIFICATION_PRE_DELETE - ファイルが削除されようとしています。
  • PRJ_NOTIFICATION_PRE_RENAME - ファイルの名前が変更されようとしています。
  • PRJ_NOTIFICATION_PRE_SET_HARDLINK - ファイルのハードリンクが作成されようとしています。
  • PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL - ファイルがプレースホルダーから完全なファイルに拡張されようとしており、その内容が変更される可能性があることを示しています。

ProjFS は、関連するファイル システム操作が完了した後に、次のリストの通知を送信します。

  • PRJ_NOTIFICATION_FILE_OPENED - 既存のファイルが開かれました。
    • この通知はファイルが開かれた後に送信された場合でも、プロバイダーは失敗コードを返すことができます。 応答として、ProjFS はオープンをキャンセルし、呼び出し元に失敗を返します。
  • PRJ_NOTIFICATION_NEW_FILE_CREATED - 新しいファイルが作成されました。
  • PRJ_NOTIFICATION_FILE_OVERWRITTEN - たとえば、 CREATE_ALWAYS dwCreationDisposition フラグを指定して CreateFileW を呼び出すことによって、既存のファイルが上書きされました。
  • PRJ_NOTIFICATION_FILE_RENAMED - ファイルの名前が変更されました。
  • PRJ_NOTIFICATION_HARDLINK_CREATED - ファイルの ハード リンク が作成されました。
  • PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION - ファイル ハンドルが閉じられており、ファイルの内容は変更されておらず、ファイルは削除されていません。
  • PRJ_NOTIFICATION_FILE_HANLDE_CLOSED_FILE_MODIFIED - ファイル ハンドルが閉じられ、ファイルの内容が変更されました。
  • PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED - ファイル ハンドルが閉じられ、ハンドルを閉じる際にファイルが削除されました。

各通知の詳細については、 PRJ_NOTIFICATIONのドキュメントを参照してください。

PRJ_NOTIFICATION_CB コールバックの notificationParameters パラメータは、特定の通知の追加パラメータを指定します。 プロバイダーが PRJ_NOTIFICATION_FILE_OPENED、PRJ_NOTIFICATION_NEW_FILE_CREATED、PRJ_NOTIFICATION_FILE_OVERWRITTEN、または PRJ_NOTIFICATION_FILE_RENAMED 通知を受信した場合、プロバイダーはファイルに対して受信する新しい通知セットを指定できます。 詳細については、 PRJ_NOTIFICATION_CBのドキュメントを参照してください。

次のサンプルは、プロバイダーが上記のコード スニペットで登録した通知を処理する方法の簡単な例を示しています。

#include <windows.h>

HRESULT
MyNotificationCallback(
    _In_ const PRJ_CALLBACK_DATA* callbackData,
    _In_ BOOLEAN isDirectory,
    _In_ PRJ_NOTIFICATION notification,
    _In_opt_z_ PCWSTR destinationFileName,
    _Inout_ PRJ_NOTIFICATION_PARAMETERS* operationParameters
    )
{
    HRESULT hr = S_OK;

    switch (notification)
    {
    case PRJ_NOTIFY_NEW_FILE_CREATED:

        wprintf(L"A new %ls was created at [%ls].\n",
                isDirectory ? L"directory" : L"file",
                callbackData->FilePathName);

        // Let's stop getting notifications inside newly-created directories.
        if (isDirectory)
        {
            operationParameters->PostCreate.NotificationMask = PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS;
        }
        break;

    case PRJ_NOTIFY_FILE_OPENED:

        wprintf(L"Handle opened to %ls [%ls].\n",
                isDirectory ? L"directory": L"file",
                callbackData->FilePathName);
        break;

    case PRJ_NOTIFY_PRE_DELETE:

        wprintf(L"Attempt to delete %ls [%ls]: ",
                isDirectory ? L"directory": L"file",
                callbackData->FilePathName);

        // MyAllowDelete is a routine the provider might implement to decide
        // whether to allow a given file/directory to be deleted.
        if (!MyAllowDelete(callbackData->FilePathName, isDirectory))
        {
            wprintf(L"DENIED\n");
            hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
        }
        else
        {
            wprintf(L"ALLOWED\n");
        }
        break;

    case PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED:

        wprintf(L"The %ls [%ls] was deleted.\n",
                isDirectory ? L"directory": L"file",
                callbackData->FilePathName);
        break;

    case PRJ_NOTIFY_FILE_RENAMED:

        if (wcslen(callbackData->FilePathName) == 0)
        {
            // If callbackData->FilePathName is an empty string, then the item
            // that was renamed was originally in a location not under the
            // virtualization root.
            wprintf(L"A %ls was renamed into the virtualization root,\n",
                    isDirectory ? L"directory": L"file");
            wprintf(L"Its new location is [%ls].\n",
                    destinationFileName);
        }
        else if (wcslen(destinationFileName) == 0)
        {
            // If destinationFileName is an empty string, then the item that was
            // renamed is now in a location not under the virtualization root.
            wprintf(L"A %ls was renamed out of the virtualization root.\n",
                    isDirectory ? L"directory": L"file");
            wprintf(L"Its original location was [%ls].\n",
                    callbackData->FilePathName);
        }
        else
        {
            // If both names are specified, both the new and old names are under
            // the virtualization root (it is never the case that nether name is
            // specified).
            wprintf(L"A %ls [%ls] was renamed.\n",
                    isDirectory ? L"directory": L"file",
                    callbackData->FilePathName);
            wprintf(L"Its new name is [%ls].\n", destinationFileName);
        }
        break;

    default:
        wprintf(L"Unrecognized notification: 0x%08x");
    }

    // Note that this may be a failure code from MyAllowDelete().
    return hr;
}