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

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

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

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

プロバイダーは、受信する通知を指定すると、1 つ以上の PRJ_NOTIFICATION_MAPPING 構造体の配列を使用します。 これらは"通知マッピング" を定義します。 通知マッピングは、"通知ルート" と呼ばれるディレクトリと、そのディレクトリとその子孫に対して ProjFS が送信する必要があるビット マスクとして表される一連の通知とのペアリングです。 通知マッピングは、1 つのファイルに対して確立することもできます。 通知マッピングで指定されたファイルまたはディレクトリは、プロバイダーが 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に設定して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 - 既存のファイルが上書きされました。たとえば、 createFileWCREATE_ALWAYSdwCreationDisposition フラグで呼び出します。
  • 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;
}