inline HRESULT RemoveDirectoryRecursiveNoThrow()

in include/wil/filesystem.h [241:343]


    inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None) WI_NOEXCEPT
    {
        wil::unique_hlocal_string path;
        PATHCCH_OPTIONS combineOptions = PATHCCH_NONE;

        if (is_extended_length_path(inputPath))
        {
            path = wil::make_hlocal_string_nothrow(inputPath);
            RETURN_IF_NULL_ALLOC(path);
            // PathAllocCombine will convert extended length paths to regular paths if shorter than
            // MAX_PATH, avoid that behavior to provide access inputPath with non-normalized names.
            combineOptions = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH;
        }
        else
        {
            // For regular paths normalize here to get consistent results when searching and deleting.
            RETURN_IF_FAILED(wil::GetFullPathNameW(inputPath, path));
            combineOptions = PATHCCH_ALLOW_LONG_PATHS;
        }

        wil::unique_hlocal_string searchPath;
        RETURN_IF_FAILED(::PathAllocCombine(path.get(), L"*", combineOptions, &searchPath));

        WIN32_FIND_DATAW fd;
        wil::unique_hfind findHandle(::FindFirstFileW(searchPath.get(), &fd));
        RETURN_LAST_ERROR_IF(!findHandle);

        for (;;)
        {
            // skip "." and ".."
            if (!(WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY) && path_is_dot_or_dotdot(fd.cFileName)))
            {
                // Need to form an extended length path to provide the ability to delete paths > MAX_PATH
                // and files with non-normalized names (dots or spaces at the end).
                wil::unique_hlocal_string pathToDelete;
                RETURN_IF_FAILED(::PathAllocCombine(path.get(), fd.cFileName,
                    PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &pathToDelete));
                if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
                {
                    // Get a handle to the directory to delete, preventing it from being replaced to prevent writes which could be used
                    // to bypass permission checks, and verify that it is not a name surrogate (e.g. symlink, mount point, etc).
                    wil::unique_hfile recursivelyDeletableDirectoryHandle = TryCreateFileCanRecurseIntoDirectory(pathToDelete.get(), &fd);
                    if (recursivelyDeletableDirectoryHandle)
                    {
                        RemoveDirectoryOptions localOptions = options;
                        RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory)));
                    }
                    else if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_REPARSE_POINT))
                    {
                        // This is a directory reparse point that should not be recursed. Delete it without traversing into it.
                        RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(pathToDelete.get()));
                    }
                    else
                    {
                        // Failed to grab a handle to the file or to read its attributes. This is not safe to recurse.
                        RETURN_WIN32(::GetLastError());
                    }
                }
                else
                {
                    // Try a DeleteFile.  Some errors may be recoverable.
                    if (!::DeleteFileW(pathToDelete.get()))
                    {
                        // Fail for anything other than ERROR_ACCESS_DENIED with option to RemoveReadOnly available
                        bool potentiallyFixableReadOnlyProblem =
                            WI_IsFlagSet(options, RemoveDirectoryOptions::RemoveReadOnly) && ::GetLastError() == ERROR_ACCESS_DENIED;
                        RETURN_LAST_ERROR_IF(!potentiallyFixableReadOnlyProblem);

                        // Fail if the file does not have read-only set, likely just an ACL problem
                        DWORD fileAttr = ::GetFileAttributesW(pathToDelete.get());
                        RETURN_LAST_ERROR_IF(!WI_IsFlagSet(fileAttr, FILE_ATTRIBUTE_READONLY));

                        // Remove read-only flag, setting to NORMAL if completely empty
                        WI_ClearFlag(fileAttr, FILE_ATTRIBUTE_READONLY);
                        if (fileAttr == 0)
                        {
                            fileAttr = FILE_ATTRIBUTE_NORMAL;
                        }

                        // Set the new attributes and try to delete the file again, returning any failure
                        ::SetFileAttributesW(pathToDelete.get(), fileAttr);
                        RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
                    }
                }
            }

            if (!::FindNextFileW(findHandle.get(), &fd))
            {
                auto const err = ::GetLastError();
                if (err == ERROR_NO_MORE_FILES)
                {
                    break;
                }
                RETURN_WIN32(err);
            }
        }

        if (WI_IsFlagClear(options, RemoveDirectoryOptions::KeepRootDirectory))
        {
            RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
        }
        return S_OK;
    }