mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 06:09:50 -06:00
update wil to 0b2d6c2d822bb301e7558a14ee66d567c14f5dc7
This commit is contained in:
192
Externals/WIL/include/wil/filesystem.h
vendored
192
Externals/WIL/include/wil/filesystem.h
vendored
@ -31,6 +31,7 @@ namespace wil
|
||||
return wcsncmp(path, L"\\\\?\\", 4) == 0;
|
||||
}
|
||||
|
||||
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
|
||||
//! Find the last segment of a path. Matches the behavior of shlwapi!PathFindFileNameW()
|
||||
//! note, does not support streams being specified like PathFindFileNameW(), is that a bug or a feature?
|
||||
inline PCWSTR find_last_path_segment(_In_ PCWSTR path)
|
||||
@ -51,6 +52,7 @@ namespace wil
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
//! Determine if the file name is one of the special "." or ".." names.
|
||||
inline bool path_is_dot_or_dotdot(_In_ PCWSTR fileName)
|
||||
@ -83,7 +85,7 @@ namespace wil
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
|
||||
|
||||
// PathCch.h APIs are only in desktop API for now.
|
||||
|
||||
@ -111,7 +113,7 @@ namespace wil
|
||||
{
|
||||
if (::CreateDirectoryW(path, nullptr) == FALSE)
|
||||
{
|
||||
DWORD const lastError = ::GetLastError();
|
||||
DWORD lastError = ::GetLastError();
|
||||
if (lastError == ERROR_PATH_NOT_FOUND)
|
||||
{
|
||||
size_t parentLength;
|
||||
@ -120,9 +122,16 @@ namespace wil
|
||||
wistd::unique_ptr<wchar_t[]> parent(new (std::nothrow) wchar_t[parentLength + 1]);
|
||||
RETURN_IF_NULL_ALLOC(parent.get());
|
||||
RETURN_IF_FAILED(StringCchCopyNW(parent.get(), parentLength + 1, path, parentLength));
|
||||
CreateDirectoryDeepNoThrow(parent.get()); // recurs
|
||||
RETURN_IF_FAILED(CreateDirectoryDeepNoThrow(parent.get())); // recurs
|
||||
}
|
||||
if (::CreateDirectoryW(path, nullptr) == FALSE)
|
||||
{
|
||||
lastError = ::GetLastError();
|
||||
if (lastError != ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
RETURN_WIN32(lastError);
|
||||
}
|
||||
}
|
||||
RETURN_IF_WIN32_BOOL_FALSE(::CreateDirectoryW(path, nullptr));
|
||||
}
|
||||
else if (lastError != ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
@ -183,13 +192,53 @@ namespace wil
|
||||
enum class RemoveDirectoryOptions
|
||||
{
|
||||
None = 0,
|
||||
KeepRootDirectory = 0x1
|
||||
KeepRootDirectory = 0x1,
|
||||
RemoveReadOnly = 0x2,
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(RemoveDirectoryOptions);
|
||||
|
||||
namespace details
|
||||
{
|
||||
// Reparse points should not be traversed in most recursive walks of the file system,
|
||||
// unless allowed through the appropriate reparse tag.
|
||||
inline bool CanRecurseIntoDirectory(const FILE_ATTRIBUTE_TAG_INFO& info)
|
||||
{
|
||||
return (WI_IsFlagSet(info.FileAttributes, FILE_ATTRIBUTE_DIRECTORY) &&
|
||||
(WI_IsFlagClear(info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT) ||
|
||||
(IsReparseTagDirectory(info.ReparseTag) || (info.ReparseTag == IO_REPARSE_TAG_WCI))));
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve a handle to a directory only if it is safe to recurse into.
|
||||
inline wil::unique_hfile TryCreateFileCanRecurseIntoDirectory(PCWSTR path, PWIN32_FIND_DATAW fileFindData, DWORD access = GENERIC_READ | /*DELETE*/ 0x00010000L, DWORD share = FILE_SHARE_READ)
|
||||
{
|
||||
wil::unique_hfile result(CreateFileW(path, access, share,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
|
||||
if (result)
|
||||
{
|
||||
FILE_ATTRIBUTE_TAG_INFO fati;
|
||||
if (GetFileInformationByHandleEx(result.get(), FileAttributeTagInfo, &fati, sizeof(fati)) &&
|
||||
details::CanRecurseIntoDirectory(fati))
|
||||
{
|
||||
if (fileFindData)
|
||||
{
|
||||
// Refresh the found file's data now that we have secured the directory from external manipulation.
|
||||
fileFindData->dwFileAttributes = fati.FileAttributes;
|
||||
fileFindData->dwReserved0 = fati.ReparseTag;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// If inputPath is a non-normalized name be sure to pass an extended length form to ensure
|
||||
// it can be addressed and deleted.
|
||||
inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None) WI_NOEXCEPT
|
||||
inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None, HANDLE deleteHandle = INVALID_HANDLE_VALUE) WI_NOEXCEPT
|
||||
{
|
||||
wil::unique_hlocal_string path;
|
||||
PATHCCH_OPTIONS combineOptions = PATHCCH_NONE;
|
||||
@ -228,14 +277,50 @@ namespace wil
|
||||
PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &pathToDelete));
|
||||
if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
|
||||
{
|
||||
RemoveDirectoryOptions localOptions = options;
|
||||
RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory)));
|
||||
// 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), recursivelyDeletableDirectoryHandle.get()));
|
||||
}
|
||||
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
|
||||
{
|
||||
// note: if pathToDelete is read-only this will fail, consider adding
|
||||
// RemoveDirectoryOptions::RemoveReadOnly to enable this behavior.
|
||||
RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +337,35 @@ namespace wil
|
||||
|
||||
if (WI_IsFlagClear(options, RemoveDirectoryOptions::KeepRootDirectory))
|
||||
{
|
||||
RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
|
||||
if (deleteHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
// DeleteFile and RemoveDirectory use POSIX delete, falling back to non-POSIX on most errors. Do the same here.
|
||||
FILE_DISPOSITION_INFO_EX fileInfoEx{};
|
||||
fileInfoEx.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS;
|
||||
if (!SetFileInformationByHandle(deleteHandle, FileDispositionInfoEx, &fileInfoEx, sizeof(fileInfoEx)))
|
||||
{
|
||||
auto const err = ::GetLastError();
|
||||
// The real error we're looking for is STATUS_CANNOT_DELETE, but that's mapped to ERROR_ACCESS_DENIED.
|
||||
if (err != ERROR_ACCESS_DENIED)
|
||||
{
|
||||
#endif
|
||||
FILE_DISPOSITION_INFO fileInfo{};
|
||||
fileInfo.DeleteFile = TRUE;
|
||||
RETURN_IF_WIN32_BOOL_FALSE(SetFileInformationByHandle(deleteHandle, FileDispositionInfo, &fileInfo, sizeof(fileInfo)));
|
||||
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_WIN32(err);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
@ -552,7 +665,7 @@ namespace wil
|
||||
OVERLAPPED m_overlapped{};
|
||||
TP_IO *m_tpIo = __nullptr;
|
||||
srwlock m_cancelLock;
|
||||
char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
|
||||
unsigned char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
|
||||
};
|
||||
|
||||
inline void delete_folder_change_reader_state(_In_opt_ folder_change_reader_state *storage) { delete storage; }
|
||||
@ -596,7 +709,6 @@ namespace wil
|
||||
auto readerState = static_cast<details::folder_change_reader_state *>(context);
|
||||
// WI_ASSERT(overlapped == &readerState->m_overlapped);
|
||||
|
||||
bool requeue = true;
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
for (auto const& info : create_next_entry_offset_iterator(reinterpret_cast<FILE_NOTIFY_INFORMATION *>(readerState->m_readBuffer)))
|
||||
@ -613,19 +725,17 @@ namespace wil
|
||||
}
|
||||
else
|
||||
{
|
||||
requeue = false;
|
||||
// No need to requeue
|
||||
return;
|
||||
}
|
||||
|
||||
if (requeue)
|
||||
// If the lock is held non-shared or the TP IO is nullptr, this
|
||||
// structure is being torn down. Otherwise, monitor for further
|
||||
// changes.
|
||||
auto autoLock = readerState->m_cancelLock.try_lock_shared();
|
||||
if (autoLock && readerState->m_tpIo)
|
||||
{
|
||||
// If the lock is held non-shared or the TP IO is nullptr, this
|
||||
// structure is being torn down. Otherwise, monitor for further
|
||||
// changes.
|
||||
auto autoLock = readerState->m_cancelLock.try_lock_shared();
|
||||
if (autoLock && readerState->m_tpIo)
|
||||
{
|
||||
readerState->StartIo(); // ignoring failure here
|
||||
}
|
||||
readerState->StartIo(); // ignoring failure here
|
||||
}
|
||||
}
|
||||
|
||||
@ -798,7 +908,7 @@ namespace wil
|
||||
|
||||
// Type unsafe version used in the implementation to avoid template bloat.
|
||||
inline HRESULT GetFileInfo(HANDLE fileHandle, FILE_INFO_BY_HANDLE_CLASS infoClass, size_t allocationSize,
|
||||
_Outptr_result_nullonfailure_ void **result)
|
||||
_Outptr_result_maybenull_ void **result)
|
||||
{
|
||||
*result = nullptr;
|
||||
|
||||
@ -875,6 +985,36 @@ namespace wil
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Verifies that the given file path is not a hard or a soft link. If the file is present at the path, returns
|
||||
// a handle to it without delete permissions to block an attacker from swapping the file.
|
||||
inline HRESULT CreateFileAndEnsureNotLinked(PCWSTR path, wil::unique_hfile& fileHandle)
|
||||
{
|
||||
// Open handles to the original path and to the final path and compare each file's information
|
||||
// to verify they are the same file. If they are different, the file is a soft link.
|
||||
fileHandle.reset(CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
|
||||
RETURN_LAST_ERROR_IF(!fileHandle);
|
||||
BY_HANDLE_FILE_INFORMATION fileInfo;
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetFileInformationByHandle(fileHandle.get(), &fileInfo));
|
||||
|
||||
// Open a handle without the reparse point flag to get the final path in case it is a soft link.
|
||||
wil::unique_hfile finalPathHandle(CreateFileW(path, 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
|
||||
RETURN_LAST_ERROR_IF(!finalPathHandle);
|
||||
BY_HANDLE_FILE_INFORMATION finalFileInfo;
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetFileInformationByHandle(finalPathHandle.get(), &finalFileInfo));
|
||||
finalPathHandle.reset();
|
||||
|
||||
// The low and high indices and volume serial number uniquely identify a file. These must match if they are the same file.
|
||||
const bool isSoftLink =
|
||||
((fileInfo.nFileIndexLow != finalFileInfo.nFileIndexLow) ||
|
||||
(fileInfo.nFileIndexHigh != finalFileInfo.nFileIndexHigh) ||
|
||||
(fileInfo.dwVolumeSerialNumber != finalFileInfo.dwVolumeSerialNumber));
|
||||
|
||||
// Return failure if it is a soft link or a hard link (number of links greater than 1).
|
||||
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME), (isSoftLink || fileInfo.nNumberOfLinks > 1));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#ifdef _CPPUNWIND
|
||||
/** Get file information for a fixed sized structure, throws on failure.
|
||||
~~~
|
||||
@ -902,7 +1042,7 @@ namespace wil
|
||||
return result;
|
||||
}
|
||||
#endif // _CPPUNWIND
|
||||
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
|
||||
}
|
||||
|
||||
#endif // __WIL_FILESYSTEM_INCLUDED
|
||||
|
Reference in New Issue
Block a user