파일 시스템 작업 알림

공급자는 열거형 처리, 파일 메타데이터 및 파일 콘텐츠 반환에 필요한 콜백 외에도 PrjStartVirtualizing 호출에 PRJ_NOTIFICATION_CB 콜백을 선택적으로 등록할 수 있습니다. 이 콜백은 instance 가상화 루트 아래의 파일 및 디렉터리에서 수행되는 파일 시스템 작업에 대한 알림을 받습니다. 일부 알림은 작업이 방금 완료되었음을 공급자에게 알리는 "사후 작업" 알림입니다. 다른 알림은 "사전 작업" 알림입니다. 즉, 작업이 발생하기 전에 공급자에게 알림을 받습니다. 사전 작업 알림에서 공급자는 콜백에서 오류 코드를 반환하여 작업을 거부할 수 있습니다. 이로 인해 반환된 오류 코드로 인해 작업이 실패합니다.

받을 알림을 지정하는 방법

공급자가 가상화 instance 시작할 때 PRJ_NOTIFICATION_CB 구현을 제공하는 경우 수신하려는 알림도 지정해야 합니다. 공급자가 등록할 수 있는 알림은 PRJ_NOTIFICATION 열거형에 정의됩니다.

공급자가 수신하려는 알림을 지정하면 하나 이상의 PRJ_NOTIFICATION_MAPPING 구조체 배열을 사용합니다. "알림 매핑"을 정의합니다. 알림 매핑은 "알림 루트"라고 하는 디렉터리와 비트 마스크로 표현되는 알림 집합 간의 페어링이며, ProjFS는 해당 디렉터리와 해당 하위 항목에 대해 보내야 합니다. 단일 파일에 대한 알림 매핑을 설정할 수도 있습니다. 알림 매핑에 지정된 파일 또는 디렉터리는 공급자가 PrjStartVirtualizing을 호출할 때 이미 존재할 필요가 없으며, 공급자는 경로를 지정할 수 있으며, 생성된 경우 매핑이 적용됩니다.

공급자가 여러 알림 매핑을 지정하고 일부는 다른 항목의 하위 항목인 경우 매핑을 내림차순 깊이로 지정해야 합니다. 더 깊은 수준의 알림 매핑은 하위 항목에 대해 상위 수준 매핑을 재정의합니다.

공급자가 알림 매핑 집합을 지정하지 않으면 ProjFS는 기본적으로 가상화 루트 아래의 모든 파일 및 디렉터리에 대해 PRJ_NOTIFY_FILE_OPENED, PRJ_NOTIFY_NEW_FILE_CREATED 및 PRJ_NOTIFY_FILE_OVERWRITTEN 보냅니다.

다음 코드 조각에서는 가상화 instance 시작할 때 알림 집합을 등록하는 방법을 보여 줍니다.

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 - 예를 들어 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;
}