Enumerar arquivos e diretórios
Quando um provedor cria uma raiz de virtualização pela primeira vez, ele fica vazio no sistema local. Ou seja, nenhum dos itens no repositório de dados de backup ainda foi armazenado em cache no disco. À medida que cada item é aberto, ele é armazenado em cache, mas até que um item seja aberto, ele só existe no repositório de dados de backup.
Como os itens não abertos não têm presença local, a enumeração de diretório normal não revelaria sua existência ao usuário. Portanto, o provedor deve participar da enumeração de diretório retornando informações ao ProjFS sobre itens em seu repositório de dados de backup. O ProjFS mescla as informações do provedor com o que está no sistema de arquivos local para apresentar ao usuário uma lista unificada do conteúdo de um diretório.
Retornos de chamada de enumeração de diretório
Para participar da enumeração de diretório, o provedor deve implementar os retornos de chamada PRJ_START_DIRECTORY_ENUMERATION_CB, PRJ_GET_DIRECTORY_ENUMERATION_CB e PRJ_END_DIRECTORY_ENUMERATION_CB . Quando um diretório na raiz de virtualização do provedor é enumerado, o ProjFS executa as seguintes ações:
O ProjFS chama o retorno de chamada PRJ_START_DIRECTORY_ENUMERATION_CB do provedor para iniciar uma sessão de enumeração.
Como parte do processamento desse retorno de chamada, o provedor deve se preparar para executar a enumeração. Por exemplo, ele deve alocar qualquer memória que possa precisar para acompanhar o progresso da enumeração em seu repositório de dados de backup.
O ProjFS identifica o diretório que está sendo enumerado no membro FilePathName do parâmetro callbackData do retorno de chamada. Isso é especificado como um caminho relativo à raiz de virtualização. Por exemplo, se a raiz de virtualização estiver localizada em
C:\virtRoot
e o diretório que está sendo enumerado forC:\virtRoot\dir1\dir2
, o membro FilePathName conterádir1\dir2
. O provedor se preparará para enumerar esse caminho em seu armazenamento de dados de backup. Dependendo dos recursos do repositório de backup de um provedor, pode fazer sentido executar a enumeração do repositório de backup no PRJ_START_DIRECTORY_ENUMERATION_CB retorno de chamada.Como várias enumerações podem ocorrer em paralelo no mesmo diretório, cada retorno de chamada de enumeração tem um argumento enumerationId para permitir que o provedor associe as invocações de retorno de chamada a uma única sessão de enumeração.
Se o provedor retornar S_OK do retorno de chamada PRJ_START_DIRECTORY_ENUMERATION_CB , ele deverá manter todas as informações de sessão de enumeração que tiver até que o ProjFS invoque o retorno de chamada PRJ_END_DIRECTORY_ENUMERATION_CB com o mesmo valor para enumerationId. Se o provedor retornar um erro de PRJ_START_DIRECTORY_ENUMERATION_CB, o ProjFS não invocará o retorno de chamada PRJ_END_DIRECTORY_ENUMERATION_CB para essa enumerationId.
O ProjFS chama o retorno de chamada PRJ_GET_DIRECTORY_ENUMERATION_CB do provedor uma ou mais vezes para obter o conteúdo do diretório do provedor.
Em resposta a esse retorno de chamada, o provedor retorna uma lista classificada de itens de seu repositório de dados de backup.
O retorno de chamada pode fornecer uma expressão de pesquisa em seu parâmetro searchExpression que o provedor usa para definir o escopo dos resultados da enumeração. Se searchExpression for NULL, o provedor retornará todas as entradas no diretório que está sendo enumerado. Se não for NULL, o provedor poderá usar PrjDoesNameContainWildCards para determinar se há curingas na expressão. Se houver, o provedor usará PrjFileNameMatch para determinar se uma entrada no diretório corresponde à expressão de pesquisa.
O provedor deve capturar o valor de searchExpression na primeira invocação desse retorno de chamada. Ele usa o valor capturado em qualquer invocação subsequente do retorno de chamada na mesma sessão de enumeração, a menos que PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN seja definido no campo Flags do parâmetro callbackData do retorno de chamada. Nesse caso, ele deve recapturar o valor de searchExpression.
Se não houver entradas correspondentes em seu repositório de backup, o provedor simplesmente retornará S_OK do retorno de chamada. Caso contrário, o provedor formata cada entrada correspondente de seu repositório de backup em uma estrutura PRJ_FILE_BASIC_INFO e, em seguida, usa PrjFillDirEntryBuffer para preencher o buffer fornecido pelo parâmetro dirEntryBufferHandle do retorno de chamada. O provedor continua adicionando entradas até que tenha adicionado todas elas ou até que PrjFillDirEntryBuffer retorne HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER). Em seguida, o provedor retorna S_OK do retorno de chamada. Observe que o provedor deve adicionar as entradas ao buffer dirEntryBufferHandle na ordem de classificação correta. O provedor deve usar PrjFileNameCompare para determinar a ordem de classificação correta.
O provedor deve fornecer um valor para o membro IsDirectory do PRJ_FILE_BASIC_INFO. Se IsDirectory for FALSE, o provedor também deverá fornecer um valor para o membro FileSize .
Se o provedor não fornecer valores para os membros CreationTime, LastAccessTime, LastWriteTime e ChangeTime , o ProjFS usará a hora atual do sistema.
O ProjFS usará qualquer FILE_ATTRIBUTE sinaliza os conjuntos de provedores no membro FileAttributes , exceto para FILE_ATTRIBUTE_DIRECTORY; ele definirá o valor correto para FILE_ATTRIBUTE_DIRECTORY no membro FileAttributes de acordo com o valor fornecido do membro IsDirectory .
Se PrjFillDirEntryBuffer retornar HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) antes que o provedor fique sem entradas para adicioná-lo, o provedor deverá acompanhar a entrada que estava tentando adicionar quando recebeu o erro. Na próxima vez que o ProjFS chamar o retorno de chamada PRJ_GET_DIRECTORY_ENUMERATION_CB para a mesma sessão de enumeração, o provedor deverá retomar a adição de entradas ao buffer dirEntryBufferHandle com a entrada que estava tentando adicionar.
Se o repositório de backup der suporte a links simbólicos, o provedor deverá usar PrjFillDirEntryBuffer2 para preencher o buffer fornecido pelo parâmetro dirEntryBufferHandle do retorno de chamada. PrjFillDirEntryBuffer2 dá suporte a uma entrada de buffer extra que permite ao provedor especificar que a entrada de enumeração é um link simbólico e qual é seu destino. Caso contrário, ele se comporta conforme descrito acima para PrjFillDirEntryBuffer. O exemplo a seguir usa PrjFillDirEntryBuffer2 para ilustrar o suporte a links simbólicos.
Observe que PrjFillDirEntryBuffer2 tem suporte a partir de Windows 10, versão 2004. Um provedor deve investigar a existência de PrjFillDirEntryBuffer2, por exemplo, 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; }
O ProjFS chama o retorno de chamada PRJ_END_DIRECTORY_ENUMERATION_CB do provedor para encerrar a sessão de enumeração.
O provedor deve executar qualquer limpeza que precise fazer para encerrar a sessão de enumeração, como desalocar a memória alocada como parte do processamento PRJ_START_DIRECTORY_ENUMERATION_CB.