다음을 통해 공유


파일 및 디렉터리 열거

공급자가 처음 가상화 루트를 만들면 로컬 시스템에서 비어 있습니다. 즉, 백업 데이터 저장소의 항목 중 아직 디스크에 캐시되지 않았습니다. 각 항목이 열리면 캐시되지만 항목이 열릴 때까지는 백업 데이터 저장소에만 존재합니다.

미개봉된 항목에는 로컬이 없으므로 일반 디렉터리 열거형은 사용자에게 해당 존재를 표시하지 않습니다. 따라서 공급자는 지원 데이터 저장소의 항목에 대한 정보를 ProjFS에 반환하여 디렉터리 열거에 참여해야 합니다. ProjFS는 공급자의 정보를 로컬 파일 시스템에 있는 정보와 병합하여 사용자에게 디렉터리 콘텐츠의 통합 목록을 제공합니다.

디렉터리 열거형 콜백

디렉터리 열거형에 참여하려면 공급자가 PRJ_START_DIRECTORY_ENUMERATION_CB, PRJ_GET_DIRECTORY_ENUMERATION_CBPRJ_END_DIRECTORY_ENUMERATION_CB 콜백을 구현해야 합니다. 공급자의 가상화 루트 아래의 디렉터리가 열거되면 ProjFS는 다음 작업을 수행합니다.

  1. ProjFS는 공급자의 PRJ_START_DIRECTORY_ENUMERATION_CB 콜백을 호출하여 열거형 세션을 시작합니다.

    이 콜백 처리의 일부로 공급자는 열거형을 수행할 준비를 해야 합니다. 예를 들어 백업 데이터 저장소에서 열거형의 진행률을 추적하는 데 필요한 메모리를 할당해야 합니다.

    ProjFS는 콜백의 callbackData 매개 변수의 FilePathName 멤버에서 열거되는 디렉터리를 식별합니다. 가상화 루트를 기준으로 경로로 지정됩니다. 예를 들어 가상화 루트가 에 C:\virtRoot있고 열거 C:\virtRoot\dir1\dir2되는 디렉터리가 인 경우 FilePathName 멤버에는 가 포함 dir1\dir2됩니다. 공급자는 지원 데이터 저장소에서 해당 경로를 열거할 준비를 합니다. 공급자의 지원 저장소 기능에 따라 PRJ_START_DIRECTORY_ENUMERATION_CB 콜백에서 백업 저장소 열거를 수행하는 것이 합리적일 수 있습니다.

    동일한 디렉터리에서 여러 열거형이 병렬로 발생할 수 있으므로 각 열거형 콜백에는 공급자가 콜백 호출을 단일 열거형 세션에 연결할 수 있도록 하는 enumerationId 인수가 있습니다.

    공급자가 PRJ_START_DIRECTORY_ENUMERATION_CB 콜백에서 S_OK 반환하는 경우 ProjFS가 enumerationId에 대해 동일한 값으로 PRJ_END_DIRECTORY_ENUMERATION_CB 콜백을 호출할 때까지 열거형 세션 정보를 유지해야 합니다. 공급자가 PRJ_START_DIRECTORY_ENUMERATION_CB 오류를 반환하는 경우 ProjFS는 해당 enumerationId에 대한 PRJ_END_DIRECTORY_ENUMERATION_CB 콜백을 호출하지 않습니다.

  2. ProjFS는 공급자의 PRJ_GET_DIRECTORY_ENUMERATION_CB 콜백을 한 번 이상 호출하여 공급자로부터 디렉터리 콘텐츠를 가져옵니다.

    이 콜백에 대한 응답으로 공급자는 백업 데이터 저장소에서 정렬된 항목 목록을 반환합니다.

    콜백은 공급자가 열거형 결과를 scope 데 사용하는 searchExpression 매개 변수에 검색 식을 제공할 수 있습니다. searchExpression이 NULL이면 공급자는 열거 중인 디렉터리의 모든 항목을 반환합니다. NULL이 아닌 경우 공급자는 PrjDoesNameContainWildCards 를 사용하여 식에 와일드카드가 있는지 여부를 확인할 수 있습니다. 있는 경우 공급자는 PrjFileNameMatch 를 사용하여 디렉터리의 항목이 검색 식과 일치하는지 여부를 확인합니다.

    공급자는 이 콜백의 첫 번째 호출에서 searchExpression 값을 캡처해야 합니다. 콜백 콜백의 callbackData 매개 변수의 Flags 필드에 PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN 설정되지 않는 한 동일한 열거형 세션에서 콜백을 호출할 때 캡처된 값을 사용합니다. 이 경우 searchExpression 값을 다시 캡처해야 합니다.

    지원 저장소에 일치하는 항목이 없으면 공급자는 콜백에서 S_OK 반환합니다. 그렇지 않으면 공급자는 백업 저장소에서 일치하는 각 항목의 형식을 PRJ_FILE_BASIC_INFO 구조로 지정한 다음 PrjFillDirEntryBuffer 를 사용하여 콜백의 dirEntryBufferHandle 매개 변수에서 제공하는 버퍼를 채웁니다. 공급자는 항목을 모두 추가할 때까지 또는 PrjFillDirEntryBuffer 가 HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)를 반환할 때까지 항목을 계속 추가합니다. 그런 다음 공급자는 콜백에서 S_OK 반환합니다. 공급자는 올바른 정렬 순서로 dirEntryBufferHandle 버퍼에 항목을 추가해야 합니다. 공급자는 PrjFileNameCompare 를 사용하여 올바른 정렬 순서를 결정해야 합니다.

    공급자는 PRJ_FILE_BASIC_INFOIsDirectory 멤버에 대한 값을 제공해야 합니다. IsDirectory가 FALSE이면 공급자는 FileSize 멤버에 대한 값도 제공해야 합니다.

    공급자가 CreationTime, LastAccessTime, LastWriteTimeChangeTime 멤버에 대한 값을 제공하지 않으면 ProjFS는 현재 시스템 시간을 사용합니다.

    ProjFS는 FILE_ATTRIBUTE_DIRECTORY 제외하고 FileAttributes 멤버에서 공급자가 설정하는 모든 FILE_ATTRIBUTE 플래그를 사용합니다. IsDirectory 멤버의 제공된 값에 따라 FileAttributes 멤버의 FILE_ATTRIBUTE_DIRECTORY 올바른 값을 설정합니다.

    공급자가 추가할 항목이 부족하기 전에 PrjFillDirEntryBuffer 가 HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)를 반환하는 경우 공급자는 오류를 받았을 때 추가하려는 항목을 추적해야 합니다. 다음에 ProjFS가 동일한 열거 세션에 대한 PRJ_GET_DIRECTORY_ENUMERATION_CB 콜백을 호출할 때 공급자는 추가하려는 항목으로 dirEntryBufferHandle 버퍼에 항목 추가를 다시 시작해야 합니다.

    백업 저장소가 기호 링크를 지원하는 경우 공급자는 PrjFillDirEntryBuffer2 를 사용하여 콜백의 dirEntryBufferHandle 매개 변수에서 제공하는 버퍼를 채워야 합니다. PrjFillDirEntryBuffer2 는 공급자가 열거형 항목이 기호 링크이고 대상이 무엇인지 지정할 수 있는 추가 버퍼 입력을 지원합니다. 그렇지 않으면 PrjFillDirEntryBuffer에 대해 위에서 설명한 대로 동작합니다. 다음 샘플에서는 PrjFillDirEntryBuffer2 를 사용하여 기호 링크에 대한 지원을 보여 줍니다.

    PrjFillDirEntryBuffer2는 Windows 10 버전 2004를 기준으로 지원됩니다. 공급자는 GetProcAddress를 사용하여 instance PrjFillDirEntryBuffer2의 존재를 검색해야 합니다.

    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 일부로 할당된 메모리 할당 취소와 같이 열거 세션을 종료하는 데 필요한 정리를 수행해야 합니다.