Partager via


Notifications d’opération de système de fichiers

Outre les rappels requis pour la gestion de l’énumération, le renvoie des métadonnées de fichier et le contenu du fichier, un fournisseur peut éventuellement inscrire un rappel PRJ_NOTIFICATION_CB dans son appel à PrjStartVirtualizing. Ce rappel reçoit les notifications des opérations de système de fichiers effectuées sur les fichiers et les répertoires sous la racine de virtualisation de l’instance. Certaines des notifications sont des notifications « post-opération », qui indiquent au fournisseur qu’une opération vient de se terminer. Les autres sont des notifications de « pré-opération », ce qui signifie que le fournisseur est averti avant que l’opération ne se produise. Dans une notification de pré-opération, le fournisseur peut refuser l’opération en renvoyant un code d’erreur à partir du rappel. Cela provoque l’échec de l’opération avec le code d’erreur renvoyé.

Comment spécifier les notifications à recevoir

Si le fournisseur fournit une implémentation de PRJ_NOTIFICATION_CB lorsqu’il lance une instance de virtualisation, il doit également spécifier les notifications qu’il souhaite recevoir. Celles pour lesquelles le fournisseur peut s’inscrire sont définies dans l’énumération PRJ_NOTIFICATION.

Le fournisseur spécifie les notifications qu’il souhaite recevoir dans un tableau composé d’une ou de plusieurs structures PRJ_NOTIFICATION_MAPPING . Celles-ci définissent des « mappages de notification ». Un mappage de notification est un jumelage entre un répertoire, appelé « racine de notification » et un ensemble de notifications, exprimé sous la forme d’un masque de bits, que ProjFS doit envoyer pour ce répertoire et ses descendants. Un mappage de notification peut également être établi pour un seul fichier. Un fichier ou un répertoire spécifié dans un mappage de notification ne doit pas nécessairement exister au moment où le fournisseur appelle PrjStartVirtualizing. Il peut spécifier n’importe quel chemin et le mappage s’y appliquera s’il est créé.

S’il spécifie plusieurs mappages de notification et que certains sont descendants d’autres, les mappages doivent être spécifiés en profondeur décroissante. Les mappages de notification à des niveaux plus profonds remplacent ceux de niveau supérieur pour leurs descendants.

Si le fournisseur ne spécifie pas un ensemble de mappages de notification, ProjFS lui envoie par défaut PRJ_NOTIFY_FILE_OPENED, PRJ_NOTIFY_NEW_FILE_CREATED et PRJ_NOTIFY_FILE_OVERWRITTEN pour tous les fichiers et répertoires sous la racine de virtualisation.

L’extrait de code suivant montre comment inscrire un ensemble de notifications lors du lancement d’une instance de virtualisation.

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;
}

Réception des notifications

ProjFS envoie des notifications d’opérations de système de fichiers en appelant le rappel PRJ_NOTIFICATION_CB du fournisseur. Il effectue cette opération pour chaque opération pour laquelle le fournisseur est inscrit. Si, comme dans l’exemple précédent, le fournisseur inscrit pour PRJ_NOTIFY_NEW_FILE_CREATED | PRJ_NOTIFY_FILE_OPENED | PRJ_NOTIFY_PRE_DELETE | PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED | PRJ_NOTIFY_FILE_RENAMED et une application a créé un fichier dans la racine de notification, ProjFS appelle le rappel PRJ_NOTIFICATION_CB avec le nom du chemin d’accès du nouveau fichier et le paramètre de notification défini sur PRJ_NOTIFICATION_NEW_FILE_CREATED.

ProjFS envoie les notifications de la liste suivante avant que l’opération de système de fichiers associée n’ait lieu. Si le fournisseur renvoie un code d’échec à partir du rappel PRJ_NOTIFICATION_CB, l’opération du système de fichiers échoue et renvoie le code d’échec spécifié par le fournisseur.

  • PRJ_NOTIFICATION_PRE_DELETE : Le fichier est sur le point d’être supprimé.
  • PRJ_NOTIFICATION_PRE_RENAME : le fichier est sur le point d’être renommé.
  • PRJ_NOTIFICATION_PRE_SET_HARDLINK : un lien fixe est sur le point d’être créé pour le fichier.
  • PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL : un fichier est sur le point d’être étendu d’un espace réservé à un fichier complet, ce qui indique que son contenu est susceptible d’être modifié.

ProjFS envoie les notifications dans la liste suivante une fois l’opération du système de fichiers associée terminée.

  • PRJ_NOTIFICATION_FILE_OPENED : un fichier existant a été ouvert.
    • Même si cette notification est envoyée après l’ouverture du fichier, le fournisseur peut renvoyer un code d’échec. En réponse, ProjFS annule l’ouverture et renvoie l’échec à l’appelant.
  • PRJ_NOTIFICATION_NEW_FILE_CREATED : un nouveau fichier a été créé.
  • PRJ_NOTIFICATION_FILE_OVERWRITTEN : un fichier existant a été remplacé, par exemple en appelant CreateFileW avec l’indicateur CREATE_ALWAYS dwCreationDisposition.
  • PRJ_NOTIFICATION_FILE_RENAMED : un fichier a été renommé.
  • PRJ_NOTIFICATION_HARDLINK_CREATED : un lien fixe a été créé pour un fichier.
  • PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION : un handle de fichier a été fermé et le contenu du fichier n’a pas été modifié, et le fichier n’a pas été supprimé.
  • PRJ_NOTIFICATION_FILE_HANLDE_CLOSED_FILE_MODIFIED : un handle de fichier a été fermé et le contenu du fichier a été modifié.
  • PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED : un handle de fichier a été fermé et le fichier a été supprimé dans le cadre de la fermeture du handle.

Pour en savoir plus sur chaque notification, reportez-vous à la documentation de PRJ_NOTIFICATION.

Le paramètre notificationParameters du rappel PRJ_NOTIFICATION_CB spécifie des paramètres supplémentaires pour certaines notifications. Si un fournisseur reçoit une notification PRJ_NOTIFICATION_FILE_OPENED, PRJ_NOTIFICATION_NEW_FILE_CREATED, PRJ_NOTIFICATION_FILE_OVERWRITTEN, ou PRJ_NOTIFICATION_FILE_RENAMED, il peut spécifier un nouvel ensemble de notifications à recevoir pour le fichier. Pour en savoir plus, consultez la documentation de PRJ_NOTIFICATION_CB.

Dans l’exemple suivant, vous trouverez des illustrations simples de la manière dont un fournisseur peut traiter les notifications qu’il a enregistrées dans l’extrait de code ci-dessus.

#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;
}