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


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

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

Так как неоткрытые элементы не имеют локального присутствия, обычное перечисление каталогов не показывает их существование пользователю. Поэтому поставщик должен участвовать в перечислении каталогов, возвращая в 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 обратном вызове.

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

    Если поставщик возвращает 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 для этого enumerationId.

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

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

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

    Поставщик должен записать значение searchExpression при первом вызове этого обратного вызова. Он использует записанное значение при любом последующем вызове обратного вызова в том же сеансе перечисления, если PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN не задано в поле Флаги параметра 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; он установит правильное значение для 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.