Перечисление файлов и каталогов
Когда поставщик впервые создает корневой каталог виртуализации, он пуст в локальной системе. То есть ни один из элементов резервного хранилища данных еще не кэширован на диск. При открытии каждого элемента он кэшируется, но до открытия элемента он существует только в резервном хранилище данных.
Так как неоткрытые элементы не имеют локального присутствия, обычное перечисление каталогов не показывает их существование пользователю. Поэтому поставщик должен участвовать в перечислении каталогов, возвращая в ProjFS сведения об элементах в резервном хранилище данных. ProjFS объединяет сведения от поставщика с данными в локальной файловой системе, чтобы представить пользователю единый список содержимого каталога.
Обратные вызовы перечисления каталогов
Для участия в перечислении каталогов поставщик должен реализовать обратные вызовы PRJ_START_DIRECTORY_ENUMERATION_CB, PRJ_GET_DIRECTORY_ENUMERATION_CB и PRJ_END_DIRECTORY_ENUMERATION_CB . При перечислении каталога в корневом каталоге виртуализации поставщика ProjFS выполняет следующие действия:
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.
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; }
ProjFS вызывает обратный вызов PRJ_END_DIRECTORY_ENUMERATION_CB поставщика для завершения сеанса перечисления.
Поставщик должен выполнить любую очистку, необходимую для завершения сеанса перечисления, например освободить память, выделенную в рамках обработки PRJ_START_DIRECTORY_ENUMERATION_CB.