Поделиться через


Перечисление файлов и каталогов

Когда поставщик сначала создает корневой каталог виртуализации, он пуст в локальной системе. То есть ни один из элементов в резервном хранилище данных пока не кэширован на диск. По мере открытия каждого элемента он кэшируется, но пока элемент не будет открыт, он существует только в резервном хранилище данных.

Так как нераскрытые элементы не имеют локального присутствия, обычное перечисление каталогов не показывает их существование пользователю. Поэтому поставщик должен участвовать в перечислении каталогов, возвращая сведения в ProjFS о элементах в резервном хранилище данных. ProjFS объединяет сведения от поставщика с тем, что находится в локальной файловой системе, чтобы представить пользователю единый список содержимого каталога.

Обратные вызовы перечисления каталогов

Для участия в перечислении каталогов поставщик должен реализовать обратные вызовы PRJ_START_DIRECTORY_ENUMERATION_CB, PRJ_GET_DIRECTORY_ENUMERATION_CBи PRJ_END_DIRECTORY_ENUMERATION_CB. При перечислении каталога в корне виртуализации поставщика ProjFS выполняет следующие действия:

  1. ProjFS вызывает обратный вызов поставщика PRJ_START_DIRECTORY_ENUMERATION_CB для запуска сеанса перечисления.

    При обработке этого обратного вызова поставщик должен подготовиться для выполнения перечисления. Например, он должен выделить необходимую память, которая может понадобиться для отслеживания процесса перечисления в поддерживающем хранилище данных.

    ProjFS идентифицирует каталог, который перечисляется в члене FilePathName параметра callbackData. Это указано как путь относительно корня виртуализации. Например, если корневой каталог виртуализации расположен в C:\virtRoot, а перечислимый каталог C:\virtRoot\dir1\dir2, элемент FilePathName будет содержать dir1\dir2. Поставщик подготовится к перечислению этого пути в резервном хранилище данных. В зависимости от возможностей резервного хранилища поставщика может потребоваться выполнить перечисление резервного хранилища в PRJ_START_DIRECTORY_ENUMERATION_CB обратном вызове.

    Так как несколько перечислений могут выполняться параллельно в одном каталоге, каждый обработчик перечисления имеет аргумент идентификатора перечисления, чтобы позволить провайдеру связать вызовы сессии обратного вызова в один сеанс перечисления.

    Если поставщик возвращает S_OK из обратного вызова PRJ_START_DIRECTORY_ENUMERATION_CB, он должен сохранять все сведения о сеансе перечисления до тех пор, пока ProjFS не вызовет обратный вызов PRJ_END_DIRECTORY_ENUMERATION_CB с тем же значением для enumerationId. Если поставщик возвращает ошибку из PRJ_START_DIRECTORY_ENUMERATION_CB, ProjFS не вызовет обратный вызов PRJ_END_DIRECTORY_ENUMERATION_CB для этого перечисления Id.

  2. ProjFS вызывает обратный вызов PRJ_GET_DIRECTORY_ENUMERATION_CB поставщика один или несколько раз, чтобы получить содержимое каталога от поставщика.

    В ответ на этот обратный вызов поставщик возвращает отсортированный список элементов из резервного хранилища данных.

    Поставщик может использовать выражение поиска, предоставленное в параметре searchExpression обратным вызовом, для ограничения результатов перечисления. Если searchExpression имеет значение NULL, поставщик возвращает все записи в перечисляемом каталоге. Если оно не равно NULL, поставщик может использовать PrjDoesNameContainWildCards для определения наличия подстановочных знаков в выражении. Если есть, поставщик использует PrjFileNameMatch, чтобы определить, соответствует ли запись в каталоге выражению поиска.

    Поставщик должен записать значение searchExpression при первом вызове этого коллбэка. Он использует захваченное значение при любом последующем вызове обратного вызова в рамках той же сессии перечисления, если PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN не задан в поле Flags параметра callbackData. В этом случае необходимо восстановить значение searchExpression.

    Если в резервном хранилище отсутствуют соответствующие записи, поставщик просто возвратит S_OK в результате обратного вызова. В противном случае поставщик форматирует каждую соответствующую запись из резервного хранилища в структуру PRJ_FILE_BASIC_INFO, а затем использует PrjFillDirEntryBuffer для заполнения буфера, предоставленного параметра обратного вызова dirEntryBufferHandle. Поставщик продолжает добавлять записи до тех пор, пока он не добавит их все или пока функцией PrjFillDirEntryBuffer не будет возвращено HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER). Затем поставщик возвращает S_OK из обратного вызова. Обратите внимание, что поставщик должен добавить записи в буфер dirEntryBufferHandle в правильном порядке сортировки. Поставщик должен использовать PrjFileNameCompare для определения правильного порядка сортировки.

    Поставщик должен указать значение для члена IsDirectoryPRJ_FILE_BASIC_INFO. Если IsDirectory имеет значение FALSE, поставщик также должен указать значение элемента FileSize.

    Если поставщик не предоставляет значения для членов CreationTime, LastAccessTime, LastWriteTimeи ChangeTime, ProjFS будет использовать текущее системное время.

    ProjFS будет использовать все флаги FILE_ATTRIBUTE, которые устанавливает поставщик в элементе FileAttributes, кроме FILE_ATTRIBUTE_DIRECTORY; ProjFS будет задавать правильное значение для FILE_ATTRIBUTE_DIRECTORY в элементе FileAttributes в соответствии с указанным значением элемента IsDirectory.

    Если PrjFillDirEntryBuffer возвращает HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) до того, как у поставщика закончатся записи для добавления, поставщик должен отслеживать запись, которую он пытался добавить, когда получил ошибку. При следующем вызове ProjFS обратного вызова PRJ_GET_DIRECTORY_ENUMERATION_CB для того же сеанса перечисления поставщик должен возобновить добавление записей в буфер dirEntryBufferHandle с записью, которую он пытается добавить.

    Если резервное хранилище поддерживает символьные ссылки, поставщик должен использовать PrjFillDirEntryBuffer2 для заполнения буфера, предоставленного параметром обратного вызова dirEntryBufferHandle. PrjFillDirEntryBuffer2 поддерживает дополнительный входной буфер, который позволяет поставщику указать, что запись перечисления является символьной ссылкой и какова её цель. В противном случае он ведет себя так, как описано выше для PrjFillDirEntryBuffer. В следующем примере используется PrjFillDirEntryBuffer2 для иллюстрации поддержки символьных ссылок.

    Обратите внимание, что PrjFillDirEntryBuffer2 поддерживается в Windows 10 версии 2004. Поставщик должен проверять наличие PrjFillDirEntryBuffer2, например с помощью 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 вызывает функцию обратного вызова PRJ_END_DIRECTORY_ENUMERATION_CB поставщика, чтобы завершить сеанс перечисления.

    Поставщик должен выполнить любую необходимую очистку для завершения сеанса перечисления, например, освободить память, выделенную в рамках обработки PRJ_START_DIRECTORY_ENUMERATION_CB.