The Webview Univeral Flutter package for Appeto SDK.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

964 lignes
43 KiB

  1. //*********************************************************
  2. //
  3. // Copyright (c) Microsoft. All rights reserved.
  4. // This code is licensed under the MIT License.
  5. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
  6. // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  7. // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  8. // PARTICULAR PURPOSE AND NONINFRINGEMENT.
  9. //
  10. //*********************************************************
  11. #ifndef __WIL_FILESYSTEM_INCLUDED
  12. #define __WIL_FILESYSTEM_INCLUDED
  13. #ifdef _KERNEL_MODE
  14. #error This header is not supported in kernel-mode.
  15. #endif
  16. #include <new>
  17. #include <combaseapi.h> // Needed for CoTaskMemFree() used in output of some helpers.
  18. #include <winbase.h> // LocalAlloc
  19. #include <PathCch.h>
  20. #include "result.h"
  21. #include "win32_helpers.h"
  22. #include "resource.h"
  23. namespace wil
  24. {
  25. //! Determines if a path is an extended length path that can be used to access paths longer than MAX_PATH.
  26. inline bool is_extended_length_path(_In_ PCWSTR path)
  27. {
  28. return wcsncmp(path, L"\\\\?\\", 4) == 0;
  29. }
  30. //! Find the last segment of a path. Matches the behavior of shlwapi!PathFindFileNameW()
  31. //! note, does not support streams being specified like PathFindFileNameW(), is that a bug or a feature?
  32. inline PCWSTR find_last_path_segment(_In_ PCWSTR path)
  33. {
  34. auto const pathLength = wcslen(path);
  35. // If there is a trailing slash ignore that in the search.
  36. auto const limitedLength = ((pathLength > 0) && (path[pathLength - 1] == L'\\')) ? (pathLength - 1) : pathLength;
  37. PCWSTR result;
  38. auto const offset = FindStringOrdinal(FIND_FROMEND, path, static_cast<int>(limitedLength), L"\\", 1, TRUE);
  39. if (offset == -1)
  40. {
  41. result = path + pathLength; // null terminator
  42. }
  43. else
  44. {
  45. result = path + offset + 1; // just past the slash
  46. }
  47. return result;
  48. }
  49. //! Determine if the file name is one of the special "." or ".." names.
  50. inline bool path_is_dot_or_dotdot(_In_ PCWSTR fileName)
  51. {
  52. return ((fileName[0] == L'.') &&
  53. ((fileName[1] == L'\0') || ((fileName[1] == L'.') && (fileName[2] == L'\0'))));
  54. }
  55. //! Returns the drive number, if it has one. Returns true if there is a drive number, false otherwise. Supports regular and extended length paths.
  56. inline bool try_get_drive_letter_number(_In_ PCWSTR path, _Out_ int* driveNumber)
  57. {
  58. if (path[0] == L'\\' && path[1] == L'\\' && path[2] == L'?' && path[3] == L'\\')
  59. {
  60. path += 4;
  61. }
  62. if (path[0] && (path[1] == L':'))
  63. {
  64. if ((path[0] >= L'a') && (path[0] <= L'z'))
  65. {
  66. *driveNumber = path[0] - L'a';
  67. return true;
  68. }
  69. else if ((path[0] >= L'A') && (path[0] <= L'Z'))
  70. {
  71. *driveNumber = path[0] - L'A';
  72. return true;
  73. }
  74. }
  75. *driveNumber = -1;
  76. return false;
  77. }
  78. #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
  79. // PathCch.h APIs are only in desktop API for now.
  80. // Compute the substring in the input value that is the parent folder path.
  81. // returns:
  82. // true + parentPathLength - path has a parent starting at the beginning path and of parentPathLength length.
  83. // false, no parent path, the input is a root path.
  84. inline bool try_get_parent_path_range(_In_ PCWSTR path, _Out_ size_t* parentPathLength)
  85. {
  86. *parentPathLength = 0;
  87. bool hasParent = false;
  88. PCWSTR rootEnd;
  89. if (SUCCEEDED(PathCchSkipRoot(path, &rootEnd)) && (*rootEnd != L'\0'))
  90. {
  91. auto const lastSegment = find_last_path_segment(path);
  92. *parentPathLength = lastSegment - path;
  93. hasParent = (*parentPathLength != 0);
  94. }
  95. return hasParent;
  96. }
  97. // Creates directories for the specified path, creating parent paths
  98. // as needed.
  99. inline HRESULT CreateDirectoryDeepNoThrow(PCWSTR path) WI_NOEXCEPT
  100. {
  101. if (::CreateDirectoryW(path, nullptr) == FALSE)
  102. {
  103. DWORD const lastError = ::GetLastError();
  104. if (lastError == ERROR_PATH_NOT_FOUND)
  105. {
  106. size_t parentLength;
  107. if (try_get_parent_path_range(path, &parentLength))
  108. {
  109. wistd::unique_ptr<wchar_t[]> parent(new (std::nothrow) wchar_t[parentLength + 1]);
  110. RETURN_IF_NULL_ALLOC(parent.get());
  111. RETURN_IF_FAILED(StringCchCopyNW(parent.get(), parentLength + 1, path, parentLength));
  112. CreateDirectoryDeepNoThrow(parent.get()); // recurs
  113. }
  114. RETURN_IF_WIN32_BOOL_FALSE(::CreateDirectoryW(path, nullptr));
  115. }
  116. else if (lastError != ERROR_ALREADY_EXISTS)
  117. {
  118. RETURN_WIN32(lastError);
  119. }
  120. }
  121. return S_OK;
  122. }
  123. #ifdef WIL_ENABLE_EXCEPTIONS
  124. inline void CreateDirectoryDeep(PCWSTR path)
  125. {
  126. THROW_IF_FAILED(CreateDirectoryDeepNoThrow(path));
  127. }
  128. #endif // WIL_ENABLE_EXCEPTIONS
  129. //! A strongly typed version of the Win32 API GetFullPathNameW.
  130. //! Return a path in an allocated buffer for handling long paths.
  131. //! Optionally return the pointer to the file name part.
  132. template <typename string_type, size_t stackBufferLength = 256>
  133. HRESULT GetFullPathNameW(PCWSTR file, string_type& path, _Outptr_opt_ PCWSTR* filePart = nullptr)
  134. {
  135. wil::assign_null_to_opt_param(filePart);
  136. const auto hr = AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
  137. [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
  138. {
  139. // Note that GetFullPathNameW() is not limited to MAX_PATH
  140. // but it does take a fixed size buffer.
  141. *valueLengthNeededWithNull = ::GetFullPathNameW(file, static_cast<DWORD>(valueLength), value, nullptr);
  142. RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
  143. WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
  144. if (*valueLengthNeededWithNull < valueLength)
  145. {
  146. (*valueLengthNeededWithNull)++; // it fit, account for the null
  147. }
  148. return S_OK;
  149. });
  150. if (SUCCEEDED(hr) && filePart)
  151. {
  152. *filePart = wil::find_last_path_segment(details::string_maker<string_type>::get(path));
  153. }
  154. return hr;
  155. }
  156. #ifdef WIL_ENABLE_EXCEPTIONS
  157. //! A strongly typed version of the Win32 API of GetFullPathNameW.
  158. //! Return a path in an allocated buffer for handling long paths.
  159. //! Optionally return the pointer to the file name part.
  160. template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
  161. string_type GetFullPathNameW(PCWSTR file, _Outptr_opt_ PCWSTR* filePart = nullptr)
  162. {
  163. string_type result;
  164. THROW_IF_FAILED((GetFullPathNameW<string_type, stackBufferLength>(file, result, filePart)));
  165. return result;
  166. }
  167. #endif
  168. enum class RemoveDirectoryOptions
  169. {
  170. None = 0,
  171. KeepRootDirectory = 0x1
  172. };
  173. DEFINE_ENUM_FLAG_OPERATORS(RemoveDirectoryOptions);
  174. namespace details
  175. {
  176. // Reparse points should not be traversed in most recursive walks of the file system,
  177. // unless allowed through the appropriate reparse tag.
  178. inline bool CanRecurseIntoDirectory(const FILE_ATTRIBUTE_TAG_INFO& info)
  179. {
  180. return (WI_IsFlagSet(info.FileAttributes, FILE_ATTRIBUTE_DIRECTORY) &&
  181. (WI_IsFlagClear(info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT) ||
  182. (IsReparseTagDirectory(info.ReparseTag) || (info.ReparseTag == IO_REPARSE_TAG_WCI))));
  183. }
  184. // Retrieve a handle to a directory only if it is safe to recurse into.
  185. inline wil::unique_hfile TryCreateFileCanRecurseIntoDirectory(PCWSTR path, PWIN32_FIND_DATAW fileFindData)
  186. {
  187. wil::unique_hfile result(CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
  188. nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
  189. if (result)
  190. {
  191. FILE_ATTRIBUTE_TAG_INFO fati;
  192. if (GetFileInformationByHandleEx(result.get(), FileAttributeTagInfo, &fati, sizeof(fati)) &&
  193. CanRecurseIntoDirectory(fati))
  194. {
  195. if (fileFindData)
  196. {
  197. // Refresh the found file's data now that we have secured the directory from external manipulation.
  198. fileFindData->dwFileAttributes = fati.FileAttributes;
  199. fileFindData->dwReserved0 = fati.ReparseTag;
  200. }
  201. }
  202. else
  203. {
  204. result.reset();
  205. }
  206. }
  207. return result;
  208. }
  209. }
  210. // If inputPath is a non-normalized name be sure to pass an extended length form to ensure
  211. // it can be addressed and deleted.
  212. inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None) WI_NOEXCEPT
  213. {
  214. wil::unique_hlocal_string path;
  215. PATHCCH_OPTIONS combineOptions = PATHCCH_NONE;
  216. if (is_extended_length_path(inputPath))
  217. {
  218. path = wil::make_hlocal_string_nothrow(inputPath);
  219. RETURN_IF_NULL_ALLOC(path);
  220. // PathAllocCombine will convert extended length paths to regular paths if shorter than
  221. // MAX_PATH, avoid that behavior to provide access inputPath with non-normalized names.
  222. combineOptions = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH;
  223. }
  224. else
  225. {
  226. // For regular paths normalize here to get consistent results when searching and deleting.
  227. RETURN_IF_FAILED(wil::GetFullPathNameW(inputPath, path));
  228. combineOptions = PATHCCH_ALLOW_LONG_PATHS;
  229. }
  230. wil::unique_hlocal_string searchPath;
  231. RETURN_IF_FAILED(::PathAllocCombine(path.get(), L"*", combineOptions, &searchPath));
  232. WIN32_FIND_DATAW fd;
  233. wil::unique_hfind findHandle(::FindFirstFileW(searchPath.get(), &fd));
  234. RETURN_LAST_ERROR_IF(!findHandle);
  235. for (;;)
  236. {
  237. // skip "." and ".."
  238. if (!(WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY) && path_is_dot_or_dotdot(fd.cFileName)))
  239. {
  240. // Need to form an extended length path to provide the ability to delete paths > MAX_PATH
  241. // and files with non-normalized names (dots or spaces at the end).
  242. wil::unique_hlocal_string pathToDelete;
  243. RETURN_IF_FAILED(::PathAllocCombine(path.get(), fd.cFileName,
  244. PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &pathToDelete));
  245. if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
  246. {
  247. // Get a handle to the directory to delete, preventing it from being replaced to prevent writes which could be used
  248. // to bypass permission checks, and verify that it is not a name surrogate (e.g. symlink, mount point, etc).
  249. wil::unique_hfile recursivelyDeletableDirectoryHandle = details::TryCreateFileCanRecurseIntoDirectory(pathToDelete.get(), &fd);
  250. if (recursivelyDeletableDirectoryHandle)
  251. {
  252. RemoveDirectoryOptions localOptions = options;
  253. RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory)));
  254. }
  255. else if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_REPARSE_POINT))
  256. {
  257. // This is a directory reparse point that should not be recursed. Delete it without traversing into it.
  258. RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(pathToDelete.get()));
  259. }
  260. else
  261. {
  262. // Failed to grab a handle to the file or to read its attributes. This is not safe to recurse.
  263. RETURN_WIN32(::GetLastError());
  264. }
  265. }
  266. else
  267. {
  268. // note: if pathToDelete is read-only this will fail, consider adding
  269. // RemoveDirectoryOptions::RemoveReadOnly to enable this behavior.
  270. RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
  271. }
  272. }
  273. if (!::FindNextFileW(findHandle.get(), &fd))
  274. {
  275. auto const err = ::GetLastError();
  276. if (err == ERROR_NO_MORE_FILES)
  277. {
  278. break;
  279. }
  280. RETURN_WIN32(err);
  281. }
  282. }
  283. if (WI_IsFlagClear(options, RemoveDirectoryOptions::KeepRootDirectory))
  284. {
  285. RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
  286. }
  287. return S_OK;
  288. }
  289. #ifdef WIL_ENABLE_EXCEPTIONS
  290. inline void RemoveDirectoryRecursive(PCWSTR path, RemoveDirectoryOptions options = RemoveDirectoryOptions::None)
  291. {
  292. THROW_IF_FAILED(RemoveDirectoryRecursiveNoThrow(path, options));
  293. }
  294. #endif // WIL_ENABLE_EXCEPTIONS
  295. // Range based for that supports Win32 structures that use NextEntryOffset as the basis of traversing
  296. // a result buffer that contains data. This is used in the following FileIO calls:
  297. // FileStreamInfo, FILE_STREAM_INFO
  298. // FileIdBothDirectoryInfo, FILE_ID_BOTH_DIR_INFO
  299. // FileFullDirectoryInfo, FILE_FULL_DIR_INFO
  300. // FileIdExtdDirectoryInfo, FILE_ID_EXTD_DIR_INFO
  301. // ReadDirectoryChangesW, FILE_NOTIFY_INFORMATION
  302. template <typename T>
  303. struct next_entry_offset_iterator
  304. {
  305. // Fulfill std::iterator_traits requirements
  306. using difference_type = ptrdiff_t;
  307. using value_type = T;
  308. using pointer = const T*;
  309. using reference = const T&;
  310. #ifdef _XUTILITY_
  311. using iterator_category = ::std::forward_iterator_tag;
  312. #endif
  313. next_entry_offset_iterator(T *iterable = __nullptr) : current_(iterable) {}
  314. // range based for requires operator!=, operator++ and operator* to do its work
  315. // on the type returned from begin() and end(), provide those here.
  316. bool operator!=(const next_entry_offset_iterator& other) const { return current_ != other.current_; }
  317. next_entry_offset_iterator& operator++()
  318. {
  319. current_ = (current_->NextEntryOffset != 0) ?
  320. reinterpret_cast<T *>(reinterpret_cast<unsigned char*>(current_) + current_->NextEntryOffset) :
  321. __nullptr;
  322. return *this;
  323. }
  324. next_entry_offset_iterator operator++(int)
  325. {
  326. auto copy = *this;
  327. ++(*this);
  328. return copy;
  329. }
  330. reference operator*() const WI_NOEXCEPT { return *current_; }
  331. pointer operator->() const WI_NOEXCEPT { return current_; }
  332. next_entry_offset_iterator<T> begin() { return *this; }
  333. next_entry_offset_iterator<T> end() { return next_entry_offset_iterator<T>(); }
  334. T* current_;
  335. };
  336. template <typename T>
  337. next_entry_offset_iterator<T> create_next_entry_offset_iterator(T* p)
  338. {
  339. return next_entry_offset_iterator<T>(p);
  340. }
  341. #pragma region Folder Watcher
  342. // Example use in exception based code:
  343. // auto watcher = wil::make_folder_watcher(folder.Path().c_str(), true, wil::allChangeEvents, []()
  344. // {
  345. // // respond
  346. // });
  347. //
  348. // Example use in result code based code:
  349. // wil::unique_folder_watcher watcher;
  350. // THROW_IF_FAILED(watcher.create(folder, true, wil::allChangeEvents, []()
  351. // {
  352. // // respond
  353. // }));
  354. enum class FolderChangeEvent : DWORD
  355. {
  356. ChangesLost = 0, // requies special handling, reset state as events were lost
  357. Added = FILE_ACTION_ADDED,
  358. Removed = FILE_ACTION_REMOVED,
  359. Modified = FILE_ACTION_MODIFIED,
  360. RenameOldName = FILE_ACTION_RENAMED_OLD_NAME,
  361. RenameNewName = FILE_ACTION_RENAMED_NEW_NAME,
  362. };
  363. enum class FolderChangeEvents : DWORD
  364. {
  365. None = 0,
  366. FileName = FILE_NOTIFY_CHANGE_FILE_NAME,
  367. DirectoryName = FILE_NOTIFY_CHANGE_DIR_NAME,
  368. Attributes = FILE_NOTIFY_CHANGE_ATTRIBUTES,
  369. FileSize = FILE_NOTIFY_CHANGE_SIZE,
  370. LastWriteTime = FILE_NOTIFY_CHANGE_LAST_WRITE,
  371. Security = FILE_NOTIFY_CHANGE_SECURITY,
  372. All = FILE_NOTIFY_CHANGE_FILE_NAME |
  373. FILE_NOTIFY_CHANGE_DIR_NAME |
  374. FILE_NOTIFY_CHANGE_ATTRIBUTES |
  375. FILE_NOTIFY_CHANGE_SIZE |
  376. FILE_NOTIFY_CHANGE_LAST_WRITE |
  377. FILE_NOTIFY_CHANGE_SECURITY
  378. };
  379. DEFINE_ENUM_FLAG_OPERATORS(FolderChangeEvents);
  380. /// @cond
  381. namespace details
  382. {
  383. struct folder_watcher_state
  384. {
  385. folder_watcher_state(wistd::function<void()> &&callback) : m_callback(wistd::move(callback))
  386. {
  387. }
  388. wistd::function<void()> m_callback;
  389. // Order is important, need to close the thread pool wait before the change handle.
  390. unique_hfind_change m_findChangeHandle;
  391. unique_threadpool_wait m_threadPoolWait;
  392. };
  393. inline void delete_folder_watcher_state(_In_opt_ folder_watcher_state *storage) { delete storage; }
  394. typedef resource_policy<folder_watcher_state *, decltype(&details::delete_folder_watcher_state),
  395. details::delete_folder_watcher_state, details::pointer_access_none> folder_watcher_state_resource_policy;
  396. }
  397. /// @endcond
  398. template <typename storage_t, typename err_policy = err_exception_policy>
  399. class folder_watcher_t : public storage_t
  400. {
  401. public:
  402. // forward all base class constructors...
  403. template <typename... args_t>
  404. explicit folder_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {}
  405. // HRESULT or void error handling...
  406. typedef typename err_policy::result result;
  407. // Exception-based constructors
  408. folder_watcher_t(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  409. {
  410. static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
  411. create(folderToWatch, isRecursive, filter, wistd::move(callback));
  412. }
  413. result create(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  414. {
  415. return err_policy::HResult(create_common(folderToWatch, isRecursive, filter, wistd::move(callback)));
  416. }
  417. private:
  418. // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
  419. // to __stdcall
  420. static void __stdcall callback(PTP_CALLBACK_INSTANCE /*Instance*/, void *context, TP_WAIT *pThreadPoolWait, TP_WAIT_RESULT /*result*/)
  421. {
  422. auto watcherState = static_cast<details::folder_watcher_state *>(context);
  423. watcherState->m_callback();
  424. // Rearm the wait. Should not fail with valid parameters.
  425. FindNextChangeNotification(watcherState->m_findChangeHandle.get());
  426. SetThreadpoolWait(pThreadPoolWait, watcherState->m_findChangeHandle.get(), __nullptr);
  427. }
  428. // This function exists to avoid template expansion of this code based on err_policy.
  429. HRESULT create_common(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  430. {
  431. wistd::unique_ptr<details::folder_watcher_state> watcherState(new(std::nothrow) details::folder_watcher_state(wistd::move(callback)));
  432. RETURN_IF_NULL_ALLOC(watcherState);
  433. watcherState->m_findChangeHandle.reset(FindFirstChangeNotificationW(folderToWatch, isRecursive, static_cast<DWORD>(filter)));
  434. RETURN_LAST_ERROR_IF(!watcherState->m_findChangeHandle);
  435. watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(&folder_watcher_t::callback, watcherState.get(), __nullptr));
  436. RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait);
  437. this->reset(watcherState.release()); // no more failures after this, pass ownership
  438. SetThreadpoolWait(this->get()->m_threadPoolWait.get(), this->get()->m_findChangeHandle.get(), __nullptr);
  439. return S_OK;
  440. }
  441. };
  442. typedef unique_any_t<folder_watcher_t<details::unique_storage<details::folder_watcher_state_resource_policy>, err_returncode_policy>> unique_folder_watcher_nothrow;
  443. inline unique_folder_watcher_nothrow make_folder_watcher_nothrow(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback) WI_NOEXCEPT
  444. {
  445. unique_folder_watcher_nothrow watcher;
  446. watcher.create(folderToWatch, isRecursive, filter, wistd::move(callback));
  447. return watcher; // caller must test for success using if (watcher)
  448. }
  449. #ifdef WIL_ENABLE_EXCEPTIONS
  450. typedef unique_any_t<folder_watcher_t<details::unique_storage<details::folder_watcher_state_resource_policy>, err_exception_policy>> unique_folder_watcher;
  451. inline unique_folder_watcher make_folder_watcher(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  452. {
  453. return unique_folder_watcher(folderToWatch, isRecursive, filter, wistd::move(callback));
  454. }
  455. #endif // WIL_ENABLE_EXCEPTIONS
  456. #pragma endregion
  457. #pragma region Folder Reader
  458. // Example use for throwing:
  459. // auto reader = wil::make_folder_change_reader(folder.Path().c_str(), true, wil::FolderChangeEvents::All,
  460. // [](wil::FolderChangeEvent event, PCWSTR fileName)
  461. // {
  462. // switch (event)
  463. // {
  464. // case wil::FolderChangeEvent::ChangesLost: break;
  465. // case wil::FolderChangeEvent::Added: break;
  466. // case wil::FolderChangeEvent::Removed: break;
  467. // case wil::FolderChangeEvent::Modified: break;
  468. // case wil::FolderChangeEvent::RenamedOldName: break;
  469. // case wil::FolderChangeEvent::RenamedNewName: break;
  470. // });
  471. //
  472. // Example use for non throwing:
  473. // wil::unique_folder_change_reader_nothrow reader;
  474. // THROW_IF_FAILED(reader.create(folder, true, wil::FolderChangeEvents::All,
  475. // [](wil::FolderChangeEvent event, PCWSTR fileName)
  476. // {
  477. // // handle changes
  478. // }));
  479. //
  480. // @cond
  481. namespace details
  482. {
  483. struct folder_change_reader_state
  484. {
  485. folder_change_reader_state(bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  486. : m_callback(wistd::move(callback)), m_isRecursive(isRecursive), m_filter(filter)
  487. {
  488. }
  489. ~folder_change_reader_state()
  490. {
  491. if (m_tpIo != __nullptr)
  492. {
  493. TP_IO *tpIo = m_tpIo;
  494. // Indicate to the callback function that this object is being torn
  495. // down.
  496. {
  497. auto autoLock = m_cancelLock.lock_exclusive();
  498. m_tpIo = __nullptr;
  499. }
  500. // Cancel IO to terminate the file system monitoring operation.
  501. if (m_folderHandle)
  502. {
  503. CancelIoEx(m_folderHandle.get(), &m_overlapped);
  504. }
  505. // Wait for callbacks to complete.
  506. //
  507. // N.B. This is a blocking call and must not be made within a
  508. // callback or within a lock which is taken inside the
  509. // callback.
  510. WaitForThreadpoolIoCallbacks(tpIo, TRUE);
  511. CloseThreadpoolIo(tpIo);
  512. }
  513. }
  514. HRESULT StartIo()
  515. {
  516. // Unfortunately we have to handle ref-counting of IOs on behalf of the
  517. // thread pool.
  518. StartThreadpoolIo(m_tpIo);
  519. HRESULT hr = ReadDirectoryChangesW(m_folderHandle.get(), m_readBuffer, sizeof(m_readBuffer),
  520. m_isRecursive, static_cast<DWORD>(m_filter), __nullptr, &m_overlapped, __nullptr) ?
  521. S_OK : HRESULT_FROM_WIN32(::GetLastError());
  522. if (FAILED(hr))
  523. {
  524. // This operation does not have the usual semantic of returning
  525. // ERROR_IO_PENDING.
  526. // WI_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING));
  527. // If the operation failed for whatever reason, ensure the TP
  528. // ref counts are accurate.
  529. CancelThreadpoolIo(m_tpIo);
  530. }
  531. return hr;
  532. }
  533. // void (wil::FolderChangeEvent event, PCWSTR fileName)
  534. wistd::function<void(FolderChangeEvent, PCWSTR)> m_callback;
  535. unique_handle m_folderHandle;
  536. BOOL m_isRecursive = FALSE;
  537. FolderChangeEvents m_filter = FolderChangeEvents::None;
  538. OVERLAPPED m_overlapped{};
  539. TP_IO *m_tpIo = __nullptr;
  540. srwlock m_cancelLock;
  541. char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
  542. };
  543. inline void delete_folder_change_reader_state(_In_opt_ folder_change_reader_state *storage) { delete storage; }
  544. typedef resource_policy<folder_change_reader_state *, decltype(&details::delete_folder_change_reader_state),
  545. details::delete_folder_change_reader_state, details::pointer_access_none> folder_change_reader_state_resource_policy;
  546. }
  547. /// @endcond
  548. template <typename storage_t, typename err_policy = err_exception_policy>
  549. class folder_change_reader_t : public storage_t
  550. {
  551. public:
  552. // forward all base class constructors...
  553. template <typename... args_t>
  554. explicit folder_change_reader_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {}
  555. // HRESULT or void error handling...
  556. typedef typename err_policy::result result;
  557. // Exception-based constructors
  558. folder_change_reader_t(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  559. {
  560. static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
  561. create(folderToWatch, isRecursive, filter, wistd::move(callback));
  562. }
  563. result create(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  564. {
  565. return err_policy::HResult(create_common(folderToWatch, isRecursive, filter, wistd::move(callback)));
  566. }
  567. wil::unique_hfile& folder_handle() { return this->get()->m_folderHandle; }
  568. private:
  569. // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
  570. // to __stdcall
  571. static void __stdcall callback(PTP_CALLBACK_INSTANCE /* Instance */, void *context, void * /*overlapped*/,
  572. ULONG result, ULONG_PTR /* BytesTransferred */, TP_IO * /* Io */)
  573. {
  574. auto readerState = static_cast<details::folder_change_reader_state *>(context);
  575. // WI_ASSERT(overlapped == &readerState->m_overlapped);
  576. bool requeue = true;
  577. if (result == ERROR_SUCCESS)
  578. {
  579. for (auto const& info : create_next_entry_offset_iterator(reinterpret_cast<FILE_NOTIFY_INFORMATION *>(readerState->m_readBuffer)))
  580. {
  581. wchar_t realtiveFileName[MAX_PATH];
  582. StringCchCopyNW(realtiveFileName, ARRAYSIZE(realtiveFileName), info.FileName, info.FileNameLength / sizeof(info.FileName[0]));
  583. readerState->m_callback(static_cast<FolderChangeEvent>(info.Action), realtiveFileName);
  584. }
  585. }
  586. else if (result == ERROR_NOTIFY_ENUM_DIR)
  587. {
  588. readerState->m_callback(FolderChangeEvent::ChangesLost, __nullptr);
  589. }
  590. else
  591. {
  592. requeue = false;
  593. }
  594. if (requeue)
  595. {
  596. // If the lock is held non-shared or the TP IO is nullptr, this
  597. // structure is being torn down. Otherwise, monitor for further
  598. // changes.
  599. auto autoLock = readerState->m_cancelLock.try_lock_shared();
  600. if (autoLock && readerState->m_tpIo)
  601. {
  602. readerState->StartIo(); // ignoring failure here
  603. }
  604. }
  605. }
  606. // This function exists to avoid template expansion of this code based on err_policy.
  607. HRESULT create_common(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  608. {
  609. wistd::unique_ptr<details::folder_change_reader_state> readerState(new(std::nothrow) details::folder_change_reader_state(
  610. isRecursive, filter, wistd::move(callback)));
  611. RETURN_IF_NULL_ALLOC(readerState);
  612. readerState->m_folderHandle.reset(CreateFileW(folderToWatch,
  613. FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
  614. __nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, __nullptr));
  615. RETURN_LAST_ERROR_IF(!readerState->m_folderHandle);
  616. readerState->m_tpIo = CreateThreadpoolIo(readerState->m_folderHandle.get(), &folder_change_reader_t::callback, readerState.get(), __nullptr);
  617. RETURN_LAST_ERROR_IF_NULL(readerState->m_tpIo);
  618. RETURN_IF_FAILED(readerState->StartIo());
  619. this->reset(readerState.release());
  620. return S_OK;
  621. }
  622. };
  623. typedef unique_any_t<folder_change_reader_t<details::unique_storage<details::folder_change_reader_state_resource_policy>, err_returncode_policy>> unique_folder_change_reader_nothrow;
  624. inline unique_folder_change_reader_nothrow make_folder_change_reader_nothrow(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter,
  625. wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback) WI_NOEXCEPT
  626. {
  627. unique_folder_change_reader_nothrow watcher;
  628. watcher.create(folderToWatch, isRecursive, filter, wistd::move(callback));
  629. return watcher; // caller must test for success using if (watcher)
  630. }
  631. #ifdef WIL_ENABLE_EXCEPTIONS
  632. typedef unique_any_t<folder_change_reader_t<details::unique_storage<details::folder_change_reader_state_resource_policy>, err_exception_policy>> unique_folder_change_reader;
  633. inline unique_folder_change_reader make_folder_change_reader(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter,
  634. wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  635. {
  636. return unique_folder_change_reader(folderToWatch, isRecursive, filter, wistd::move(callback));
  637. }
  638. #endif // WIL_ENABLE_EXCEPTIONS
  639. #pragma endregion
  640. //! Dos and VolumeGuid paths are always extended length paths with the \\?\ prefix.
  641. enum class VolumePrefix
  642. {
  643. Dos = VOLUME_NAME_DOS, // Extended Dos Device path form, e.g. \\?\C:\Users\Chris\AppData\Local\Temp\wil8C31.tmp
  644. VolumeGuid = VOLUME_NAME_GUID, // \\?\Volume{588fb606-b95b-4eae-b3cb-1e49861aaf18}\Users\Chris\AppData\Local\Temp\wil8C31.tmp
  645. // The following are special paths which can't be used with Win32 APIs, but are useful in other scenarios.
  646. None = VOLUME_NAME_NONE, // Path without the volume root, e.g. \Users\Chris\AppData\Local\Temp\wil8C31.tmp
  647. NtObjectName = VOLUME_NAME_NT, // Unique name used by Object Manager, e.g. \Device\HarddiskVolume4\Users\Chris\AppData\Local\Temp\wil8C31.tmp
  648. };
  649. enum class PathOptions
  650. {
  651. Normalized = FILE_NAME_NORMALIZED,
  652. Opened = FILE_NAME_OPENED,
  653. };
  654. DEFINE_ENUM_FLAG_OPERATORS(PathOptions);
  655. /** A strongly typed version of the Win32 API GetFinalPathNameByHandleW.
  656. Get the full path name in different forms
  657. Use this instead + VolumePrefix::None instead of GetFileInformationByHandleEx(FileNameInfo) to
  658. get that path form. */
  659. template <typename string_type, size_t stackBufferLength = 256>
  660. HRESULT GetFinalPathNameByHandleW(HANDLE fileHandle, string_type& path,
  661. wil::VolumePrefix volumePrefix = wil::VolumePrefix::Dos, wil::PathOptions options = wil::PathOptions::Normalized)
  662. {
  663. return AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
  664. [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
  665. {
  666. *valueLengthNeededWithNull = ::GetFinalPathNameByHandleW(fileHandle, value, static_cast<DWORD>(valueLength),
  667. static_cast<DWORD>(volumePrefix) | static_cast<DWORD>(options));
  668. RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
  669. WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
  670. if (*valueLengthNeededWithNull < valueLength)
  671. {
  672. (*valueLengthNeededWithNull)++; // it fit, account for the null
  673. }
  674. return S_OK;
  675. });
  676. }
  677. #ifdef WIL_ENABLE_EXCEPTIONS
  678. /** A strongly typed version of the Win32 API GetFinalPathNameByHandleW.
  679. Get the full path name in different forms. Use this + VolumePrefix::None
  680. instead of GetFileInformationByHandleEx(FileNameInfo) to get that path form. */
  681. template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
  682. string_type GetFinalPathNameByHandleW(HANDLE fileHandle,
  683. wil::VolumePrefix volumePrefix = wil::VolumePrefix::Dos, wil::PathOptions options = wil::PathOptions::Normalized)
  684. {
  685. string_type result;
  686. THROW_IF_FAILED((GetFinalPathNameByHandleW<string_type, stackBufferLength>(fileHandle, result, volumePrefix, options)));
  687. return result;
  688. }
  689. #endif
  690. //! A strongly typed version of the Win32 API of GetCurrentDirectoryW.
  691. //! Return a path in an allocated buffer for handling long paths.
  692. template <typename string_type, size_t stackBufferLength = 256>
  693. HRESULT GetCurrentDirectoryW(string_type& path)
  694. {
  695. return AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
  696. [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
  697. {
  698. *valueLengthNeededWithNull = ::GetCurrentDirectoryW(static_cast<DWORD>(valueLength), value);
  699. RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
  700. WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
  701. if (*valueLengthNeededWithNull < valueLength)
  702. {
  703. (*valueLengthNeededWithNull)++; // it fit, account for the null
  704. }
  705. return S_OK;
  706. });
  707. }
  708. #ifdef WIL_ENABLE_EXCEPTIONS
  709. //! A strongly typed version of the Win32 API of GetCurrentDirectoryW.
  710. //! Return a path in an allocated buffer for handling long paths.
  711. template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
  712. string_type GetCurrentDirectoryW()
  713. {
  714. string_type result;
  715. THROW_IF_FAILED((GetCurrentDirectoryW<string_type, stackBufferLength>(result)));
  716. return result;
  717. }
  718. #endif
  719. // TODO: add support for these and other similar APIs.
  720. // GetShortPathNameW()
  721. // GetLongPathNameW()
  722. // GetWindowsDirectory()
  723. // GetTempDirectory()
  724. /// @cond
  725. namespace details
  726. {
  727. template <FILE_INFO_BY_HANDLE_CLASS infoClass> struct MapInfoClassToInfoStruct; // failure to map is a usage error caught by the compiler
  728. #define MAP_INFOCLASS_TO_STRUCT(InfoClass, InfoStruct, IsFixed, Extra) \
  729. template <> struct MapInfoClassToInfoStruct<InfoClass> \
  730. { \
  731. typedef InfoStruct type; \
  732. static bool const isFixed = IsFixed; \
  733. static size_t const extraSize = Extra; \
  734. };
  735. MAP_INFOCLASS_TO_STRUCT(FileBasicInfo, FILE_BASIC_INFO, true, 0);
  736. MAP_INFOCLASS_TO_STRUCT(FileStandardInfo, FILE_STANDARD_INFO, true, 0);
  737. MAP_INFOCLASS_TO_STRUCT(FileNameInfo, FILE_NAME_INFO, false, 32);
  738. MAP_INFOCLASS_TO_STRUCT(FileRenameInfo, FILE_RENAME_INFO, false, 32);
  739. MAP_INFOCLASS_TO_STRUCT(FileDispositionInfo, FILE_DISPOSITION_INFO, true, 0);
  740. MAP_INFOCLASS_TO_STRUCT(FileAllocationInfo, FILE_ALLOCATION_INFO, true, 0);
  741. MAP_INFOCLASS_TO_STRUCT(FileEndOfFileInfo, FILE_END_OF_FILE_INFO, true, 0);
  742. MAP_INFOCLASS_TO_STRUCT(FileStreamInfo, FILE_STREAM_INFO, false, 32);
  743. MAP_INFOCLASS_TO_STRUCT(FileCompressionInfo, FILE_COMPRESSION_INFO, true, 0);
  744. MAP_INFOCLASS_TO_STRUCT(FileAttributeTagInfo, FILE_ATTRIBUTE_TAG_INFO, true, 0);
  745. MAP_INFOCLASS_TO_STRUCT(FileIdBothDirectoryInfo, FILE_ID_BOTH_DIR_INFO, false, 4096);
  746. MAP_INFOCLASS_TO_STRUCT(FileIdBothDirectoryRestartInfo, FILE_ID_BOTH_DIR_INFO, true, 0);
  747. MAP_INFOCLASS_TO_STRUCT(FileIoPriorityHintInfo, FILE_IO_PRIORITY_HINT_INFO, true, 0);
  748. MAP_INFOCLASS_TO_STRUCT(FileRemoteProtocolInfo, FILE_REMOTE_PROTOCOL_INFO, true, 0);
  749. MAP_INFOCLASS_TO_STRUCT(FileFullDirectoryInfo, FILE_FULL_DIR_INFO, false, 4096);
  750. MAP_INFOCLASS_TO_STRUCT(FileFullDirectoryRestartInfo, FILE_FULL_DIR_INFO, true, 0);
  751. #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
  752. MAP_INFOCLASS_TO_STRUCT(FileStorageInfo, FILE_STORAGE_INFO, true, 0);
  753. MAP_INFOCLASS_TO_STRUCT(FileAlignmentInfo, FILE_ALIGNMENT_INFO, true, 0);
  754. MAP_INFOCLASS_TO_STRUCT(FileIdInfo, FILE_ID_INFO, true, 0);
  755. MAP_INFOCLASS_TO_STRUCT(FileIdExtdDirectoryInfo, FILE_ID_EXTD_DIR_INFO, false, 4096);
  756. MAP_INFOCLASS_TO_STRUCT(FileIdExtdDirectoryRestartInfo, FILE_ID_EXTD_DIR_INFO, true, 0);
  757. #endif
  758. // Type unsafe version used in the implementation to avoid template bloat.
  759. inline HRESULT GetFileInfo(HANDLE fileHandle, FILE_INFO_BY_HANDLE_CLASS infoClass, size_t allocationSize,
  760. _Outptr_result_nullonfailure_ void **result)
  761. {
  762. *result = nullptr;
  763. wistd::unique_ptr<char[]> resultHolder(new(std::nothrow) char[allocationSize]);
  764. RETURN_IF_NULL_ALLOC(resultHolder);
  765. for (;;)
  766. {
  767. if (GetFileInformationByHandleEx(fileHandle, infoClass, resultHolder.get(), static_cast<DWORD>(allocationSize)))
  768. {
  769. *result = resultHolder.release();
  770. break;
  771. }
  772. else
  773. {
  774. DWORD const lastError = ::GetLastError();
  775. if (lastError == ERROR_MORE_DATA)
  776. {
  777. allocationSize *= 2;
  778. resultHolder.reset(new(std::nothrow) char[allocationSize]);
  779. RETURN_IF_NULL_ALLOC(resultHolder);
  780. }
  781. else if (lastError == ERROR_NO_MORE_FILES) // for folder enumeration cases
  782. {
  783. break;
  784. }
  785. else if (lastError == ERROR_INVALID_PARAMETER) // operation not supported by file system
  786. {
  787. return HRESULT_FROM_WIN32(lastError);
  788. }
  789. else
  790. {
  791. RETURN_WIN32(lastError);
  792. }
  793. }
  794. }
  795. return S_OK;
  796. }
  797. }
  798. /// @endcond
  799. /** Get file information for a variable sized structure, returns an HRESULT.
  800. ~~~
  801. wistd::unique_ptr<FILE_NAME_INFO> fileNameInfo;
  802. RETURN_IF_FAILED(GetFileInfoNoThrow<FileNameInfo>(fileHandle, fileNameInfo));
  803. ~~~
  804. */
  805. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<!details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  806. HRESULT GetFileInfoNoThrow(HANDLE fileHandle, wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> &result) WI_NOEXCEPT
  807. {
  808. void *rawResult;
  809. HRESULT hr = details::GetFileInfo(fileHandle, infoClass,
  810. sizeof(typename details::MapInfoClassToInfoStruct<infoClass>::type) + details::MapInfoClassToInfoStruct<infoClass>::extraSize,
  811. &rawResult);
  812. result.reset(static_cast<typename details::MapInfoClassToInfoStruct<infoClass>::type*>(rawResult));
  813. RETURN_HR_IF_EXPECTED(hr, hr == E_INVALIDARG); // operation not supported by file system
  814. RETURN_IF_FAILED(hr);
  815. return S_OK;
  816. }
  817. /** Get file information for a fixed sized structure, returns an HRESULT.
  818. ~~~
  819. FILE_BASIC_INFO fileBasicInfo;
  820. RETURN_IF_FAILED(GetFileInfoNoThrow<FileBasicInfo>(fileHandle, &fileBasicInfo));
  821. ~~~
  822. */
  823. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  824. HRESULT GetFileInfoNoThrow(HANDLE fileHandle, _Out_ typename details::MapInfoClassToInfoStruct<infoClass>::type *result) WI_NOEXCEPT
  825. {
  826. const HRESULT hr = GetFileInformationByHandleEx(fileHandle, infoClass, result, sizeof(*result)) ?
  827. S_OK : HRESULT_FROM_WIN32(::GetLastError());
  828. RETURN_HR_IF_EXPECTED(hr, hr == E_INVALIDARG); // operation not supported by file system
  829. RETURN_IF_FAILED(hr);
  830. return S_OK;
  831. }
  832. #ifdef _CPPUNWIND
  833. /** Get file information for a fixed sized structure, throws on failure.
  834. ~~~
  835. auto fileBasicInfo = GetFileInfo<FileBasicInfo>(fileHandle);
  836. ~~~
  837. */
  838. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  839. typename details::MapInfoClassToInfoStruct<infoClass>::type GetFileInfo(HANDLE fileHandle)
  840. {
  841. typename details::MapInfoClassToInfoStruct<infoClass>::type result;
  842. THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, &result));
  843. return result;
  844. }
  845. /** Get file information for a variable sized structure, throws on failure.
  846. ~~~
  847. auto fileBasicInfo = GetFileInfo<FileNameInfo>(fileHandle);
  848. ~~~
  849. */
  850. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<!details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  851. wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> GetFileInfo(HANDLE fileHandle)
  852. {
  853. wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> result;
  854. THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, result));
  855. return result;
  856. }
  857. #endif // _CPPUNWIND
  858. #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
  859. }
  860. #endif // __WIL_FILESYSTEM_INCLUDED