Condividi tramite


Enumerazione di file e directory

Quando un provider crea prima una radice di virtualizzazione, è vuota nel sistema locale. Ovvero, nessuno degli elementi nell'archivio dati di backup è ancora stato memorizzato nella cache su disco. Quando ogni elemento viene aperto viene memorizzato nella cache, ma finché non viene aperto un elemento esiste solo nell'archivio dati di backup.

Poiché gli elementi non aperti non hanno presenza locale, l'enumerazione della directory normale non rivelerebbe la loro esistenza all'utente. Pertanto, il provider deve partecipare all'enumerazione della directory restituendo informazioni a ProjFS sugli elementi nell'archivio dati di backup. ProjFS unisce le informazioni del provider con ciò che si trova nel file system locale per presentare all'utente un elenco unificato del contenuto di una directory.

Callback dell'enumerazione directory

Per partecipare all'enumerazione directory, il provider deve implementare i callback PRJ_START_DIRECTORY_ENUMERATION_CB, PRJ_GET_DIRECTORY_ENUMERATION_CB e PRJ_END_DIRECTORY_ENUMERATION_CB. Quando una directory nella radice di virtualizzazione del provider viene enumerata, ProjFS esegue le azioni seguenti:

  1. ProjFS chiama il callback PRJ_START_DIRECTORY_ENUMERATION_CB del provider per avviare una sessione di enumerazione.

    Durante l'elaborazione di questo callback, il provider deve preparare l'enumerazione. Ad esempio, deve allocare qualsiasi memoria che potrebbe dover tenere traccia dello stato di avanzamento dell'enumerazione nell'archivio dati di backup.

    ProjFS identifica la directory enumerata nel membro FilePathName del parametro callbackData del callback . Viene specificato come percorso relativo alla radice di virtualizzazione. Ad esempio, se la radice di virtualizzazione si trova in C:\virtRoote la directory da enumerare è C:\virtRoot\dir1\dir2, il membro FilePathName conterrà dir1\dir2. Il provider prepara l'enumerazione del percorso nell'archivio dati di backup. A seconda delle funzionalità dell'archivio di backup di un provider, può essere opportuno eseguire l'enumerazione dell'archivio di backup nell'PRJ_START_DIRECTORY_ENUMERATION_CB callback.

    Poiché più enumerazioni possono verificarsi in parallelo nella stessa directory, ogni callback di enumerazione ha un argomento enumerationId per consentire al provider di associare le chiamate di callback in una singola sessione di enumerazione.

    Se il provider restituisce S_OK dal callback PRJ_START_DIRECTORY_ENUMERATION_CB , deve mantenere tutte le informazioni sulla sessione di enumerazione che ha fino a quando ProjFS richiama il callback PRJ_END_DIRECTORY_ENUMERATION_CB con lo stesso valore per enumerationId. Se il provider restituisce un errore da PRJ_START_DIRECTORY_ENUMERATION_CB, ProjFS non richiama il callback PRJ_END_DIRECTORY_ENUMERATION_CB per tale enumerationId.

  2. ProjFS chiama il callback PRJ_GET_DIRECTORY_ENUMERATION_CB del provider una o più volte per ottenere il contenuto della directory dal provider.

    In risposta a questo callback, il provider restituisce un elenco ordinato di elementi dall'archivio dati di backup.

    Il callback può fornire un'espressione di ricerca nel parametro searchExpression usato dal provider per definire l'ambito dei risultati dell'enumerazione. Se searchExpression è NULL, il provider restituisce tutte le voci nella directory enumerata. Se non è NULL, il provider può usare PrjDoesNameContainWildCards per determinare se nell'espressione sono presenti caratteri jolly. In caso contrario, il provider usa PrjFileNameMatch per determinare se una voce nella directory corrisponde all'espressione di ricerca.

    Il provider deve acquisire il valore di searchExpression nella prima chiamata di questo callback. Usa il valore acquisito in qualsiasi chiamata successiva del callback nella stessa sessione di enumerazione, a meno che PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN sia impostato nel campo Flags del parametro callbackData del callback del callback . In tal caso, dovrebbe ricaptare il valore di searchExpression.

    Se non sono presenti voci corrispondenti nel relativo archivio di backup, il provider restituisce semplicemente S_OK dal callback. In caso contrario, il provider formatta ogni voce corrispondente dall'archivio di backup in una struttura PRJ_FILE_BASIC_INFO e quindi usa PrjFillDirEntryBuffer per riempire il buffer fornito dal parametro dirEntryBufferHandle del callback. Il provider continua ad aggiungere voci fino a quando non vengono aggiunte tutte o fino a quando PrjFillDirEntryBuffer restituisce HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER). Il provider restituisce quindi S_OK dal callback. Si noti che il provider deve aggiungere le voci al buffer dirEntryBufferHandle nell'ordine di ordinamento corretto. Il provider deve usare PrjFileNameCompare per determinare l'ordine di ordinamento corretto.

    Il provider deve fornire un valore per il membro IsDirectory di PRJ_FILE_BASIC_INFO. Se IsDirectory è FALSE, il provider deve specificare anche un valore per il membro FileSize .

    Se il provider non fornisce valori per i membri CreationTime, LastAccessTime, LastWriteTime e ChangeTime , ProjFS userà l'ora di sistema corrente.

    ProjFS userà qualsiasi FILE_ATTRIBUTE contrassegni i set di provider nel membro FileAttributes ad eccezione di FILE_ATTRIBUTE_DIRECTORY; verrà impostato il valore corretto per FILE_ATTRIBUTE_DIRECTORY nel membro FileAttributes in base al valore specificato del membro IsDirectory .

    Se PrjFillDirEntryBuffer restituisce HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) prima dell'esecuzione delle voci da aggiungere al provider, il provider deve tenere traccia della voce che stava tentando di aggiungere quando ha ricevuto l'errore. La prossima volta che ProjFS chiama il callback PRJ_GET_DIRECTORY_ENUMERATION_CB per la stessa sessione di enumerazione, il provider deve riprendere l'aggiunta di voci al buffer dirEntryBufferHandle con la voce che stava tentando di aggiungere.

    Se l'archivio di backup supporta collegamenti simbolici, il provider deve usare PrjFillDirEntryBuffer2 per riempire il buffer fornito dal parametro dirEntryBufferHandle del callback. PrjFillDirEntryBuffer2 supporta un input buffer aggiuntivo che consente al provider di specificare che la voce di enumerazione è un collegamento simbolico e qual è la destinazione. In caso contrario, si comporta come descritto in precedenza per PrjFillDirEntryBuffer. L'esempio seguente usa PrjFillDirEntryBuffer2 per illustrare il supporto per i collegamenti simbolici.

    Si noti che PrjFillDirEntryBuffer2 è supportato a partire da Windows 10, versione 2004. Un provider deve eseguire il probe per l'esistenza di PrjFillDirEntryBuffer2, ad esempio usando GetProcAddress.

    typedef struct MY_ENUM_ENTRY MY_ENUM_ENTRY;
    
    typedef struct MY_ENUM_ENTRY {
        MY_ENUM_ENTRY* Next;
        PWSTR Name;
        BOOLEAN IsDirectory;
        INT64 FileSize;
        BOOLEAN IsSymlink;
        PWSTR SymlinkTarget;
    } MY_ENUM_ENTRY;
    
    typedef struct MY_ENUM_SESSION {
        GUID EnumerationId;
        PWSTR SearchExpression;
        USHORT SearchExpressionMaxLen;
        BOOLEAN SearchExpressionCaptured;
        MY_ENUM_ENTRY* EnumHead;
        MY_ENUM_ENTRY* LastEnumEntry;
        BOOLEAN EnumCompleted;
    } MY_ENUM_SESSION;
    
    HRESULT
    MyGetEnumCallback(
        _In_ const PRJ_CALLBACK_DATA* callbackData,
        _In_ const GUID* enumerationId,
        _In_opt_z_ PCWSTR searchExpression,
        _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle
        )
    {
        // MyGetEnumSession is a routine the provider might implement to find
        // information about the enumeration session that it first stored
        // when processing its PRJ_START_DIRECTORY_ENUMERATION_CB callback.
        //
        // In this example the PRJ_START_DIRECTORY_ENUMERATION_CB callback has
        // already retrieved a list of the items in the backing store located at
        // callbackData->FilePathName, sorted them using PrjFileNameCompare()
        // to determine collation order, and stored the list in session->EnumHead.
        MY_ENUM_SESSION* session = NULL;
        session = MyGetEnumSession(enumerationId);
    
        if (session == NULL)
        {
            return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
        }
    
        if (!session->SearchExpressionCaptured ||
            (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN))
        {
            if (searchExpression != NULL)
            {
                if (wcsncpy_s(session->SearchExpression,
                              session->SearchExpressionMaxLen,
                              searchExpression,
                              wcslen(searchExpression)))
                {
                    // Failed to copy the search expression; perhaps the provider
                    // could try reallocating session->SearchExpression.
                }
            }
            else
            {
                if (wcsncpy_s(session->SearchExpression,
                              session->SearchExpressionMaxLen,
                              "*",
                              1))
                {
                    // Failed to copy the search expression; perhaps the provider
                    // could try reallocating session->SearchExpression.
                }
            }
            session->SearchExpressionCaptured = TRUE;
        }
    
        MY_ENUM_ENTRY* enumHead = NULL;
    
        // We have to start the enumeration from the beginning if we aren't
        // continuing an existing session or if the caller is requesting a restart.
        if (((session->LastEnumEntry == NULL) &&
             !session->EnumCompleted) ||
            (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN))
        {
            // Ensure that if the caller is requesting a restart we reset our
            // bookmark of how far we got in the enumeration.
            session->LastEnumEntry = NULL;
    
            // In case we're restarting ensure we don't think we're done.
            session->EnumCompleted = FALSE;
    
            // We need to start considering items from the beginning of the list
            // retrieved from the backing store.
            enumHead = session->EnumHead;
        }
        else
        {
            // We are resuming an existing enumeration session.  Note that
            // session->LastEnumEntry may be NULL.  That is okay; it means
            // we got all the entries the last time this callback was invoked.
            // Returning S_OK without adding any new entries to the
            // dirEntryBufferHandle buffer signals ProjFS that the enumeration
            // has returned everything it can.
            enumHead = session->LastEnumEntry;
        }
    
        if (enumHead == NULL)
        {
            // There are no items to return.  Remember that we've returned everything
            // we can.
            session->EnumCompleted = TRUE;
        }
        else
        {
            MY_ENUM_ENTRY* thisEntry = enumHead;
            int entriesAdded = 0;
            while (thisEntry != NULL)
            {
                // We'll insert the entry into the return buffer if it matches
                // the search expression captured for this enumeration session.
                if (PrjFileNameMatch(thisEntry->Name, session->SearchExpression))
                {
                    PRJ_FILE_BASIC_INFO fileBasicInfo = {};
                    fileBasicInfo.IsDirectory = thisEntry->IsDirectory;
                    fileBasicInfo.FileSize = thisEntry->FileSize;
    
                    // If our backing store says this is really a symbolic link,
                    // set up to tell ProjFS that it is a symlink and what its
                    // target is.
                    PRJ_EXTENDED_INFO extraInfo = {};
                    if (thisEntry->IsSymlink)
                    {
                        extraInfo.InfoType = PRJ_EXT_INFO_SYMLINK;
                        extraInfo.Symlink.TargetName = thisEntry->SymlinkTarget;
                    }
    
                    // Format the entry for return to ProjFS.
                    HRESULT fillResult = PrjFillDirEntryBuffer2(dirEntryBufferHandle,
                                                                thisEntry->Name,
                                                                &fileBasicInfo,
                                                                thisEntry->IsSymlink ? &extraInfo : NULL);
    
                    if (fillResult == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
                    {
                        if (entriesAdded > 0)
                        {
                            // We couldn't add this entry to the buffer; remember where we left
                            // off for the next time we're called for this enumeration session.
                            session->LastEnumEntry = thisEntry;
                            return S_OK;
                        }
                        else
                        {
                            // The buffer should always have enough space for at least one entry, but
                            // we could not add even one entry. Return the error.
                            return fillResult;
                        }
                    }
                    else
                    {
                        // Any other error is unexpected. NOTE: Be sure to check for E_INVALIDARG to
                        // ensure you are calling PrjFillDirEntryBuffer2 correctly.
                        return fillResult;
                    }
    
                    ++entriesAdded;
                }
    
                thisEntry = thisEntry->Next;
            }
    
            // We reached the end of the list of entries; remember that we've returned
            // everything we can.
            session->EnumCompleted = TRUE;
        }
    
        return S_OK;
    }
    
  3. ProjFS chiama il callback PRJ_END_DIRECTORY_ENUMERATION_CB del provider per terminare la sessione di enumerazione.

    Il provider deve eseguire qualsiasi pulizia che deve eseguire per terminare la sessione di enumerazione, ad esempio deallocate memoria allocata come parte dell'elaborazione PRJ_START_DIRECTORY_ENUMERATION_CB.