You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

909 lines
40KB

  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. // If inputPath is a non-normalized name be sure to pass an extended length form to ensure
  175. // it can be addressed and deleted.
  176. inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None) WI_NOEXCEPT
  177. {
  178. wil::unique_hlocal_string path;
  179. PATHCCH_OPTIONS combineOptions = PATHCCH_NONE;
  180. if (is_extended_length_path(inputPath))
  181. {
  182. path = wil::make_hlocal_string_nothrow(inputPath);
  183. RETURN_IF_NULL_ALLOC(path);
  184. // PathAllocCombine will convert extended length paths to regular paths if shorter than
  185. // MAX_PATH, avoid that behavior to provide access inputPath with non-normalized names.
  186. combineOptions = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH;
  187. }
  188. else
  189. {
  190. // For regular paths normalize here to get consistent results when searching and deleting.
  191. RETURN_IF_FAILED(wil::GetFullPathNameW(inputPath, path));
  192. combineOptions = PATHCCH_ALLOW_LONG_PATHS;
  193. }
  194. wil::unique_hlocal_string searchPath;
  195. RETURN_IF_FAILED(::PathAllocCombine(path.get(), L"*", combineOptions, &searchPath));
  196. WIN32_FIND_DATAW fd;
  197. wil::unique_hfind findHandle(::FindFirstFileW(searchPath.get(), &fd));
  198. RETURN_LAST_ERROR_IF(!findHandle);
  199. for (;;)
  200. {
  201. // skip "." and ".."
  202. if (!(WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY) && path_is_dot_or_dotdot(fd.cFileName)))
  203. {
  204. // Need to form an extended length path to provide the ability to delete paths > MAX_PATH
  205. // and files with non-normalized names (dots or spaces at the end).
  206. wil::unique_hlocal_string pathToDelete;
  207. RETURN_IF_FAILED(::PathAllocCombine(path.get(), fd.cFileName,
  208. PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &pathToDelete));
  209. if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
  210. {
  211. RemoveDirectoryOptions localOptions = options;
  212. RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory)));
  213. }
  214. else
  215. {
  216. // note: if pathToDelete is read-only this will fail, consider adding
  217. // RemoveDirectoryOptions::RemoveReadOnly to enable this behavior.
  218. RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
  219. }
  220. }
  221. if (!::FindNextFileW(findHandle.get(), &fd))
  222. {
  223. auto const err = ::GetLastError();
  224. if (err == ERROR_NO_MORE_FILES)
  225. {
  226. break;
  227. }
  228. RETURN_WIN32(err);
  229. }
  230. }
  231. if (WI_IsFlagClear(options, RemoveDirectoryOptions::KeepRootDirectory))
  232. {
  233. RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
  234. }
  235. return S_OK;
  236. }
  237. #ifdef WIL_ENABLE_EXCEPTIONS
  238. inline void RemoveDirectoryRecursive(PCWSTR path, RemoveDirectoryOptions options = RemoveDirectoryOptions::None)
  239. {
  240. THROW_IF_FAILED(RemoveDirectoryRecursiveNoThrow(path, options));
  241. }
  242. #endif // WIL_ENABLE_EXCEPTIONS
  243. // Range based for that supports Win32 structures that use NextEntryOffset as the basis of traversing
  244. // a result buffer that contains data. This is used in the following FileIO calls:
  245. // FileStreamInfo, FILE_STREAM_INFO
  246. // FileIdBothDirectoryInfo, FILE_ID_BOTH_DIR_INFO
  247. // FileFullDirectoryInfo, FILE_FULL_DIR_INFO
  248. // FileIdExtdDirectoryInfo, FILE_ID_EXTD_DIR_INFO
  249. // ReadDirectoryChangesW, FILE_NOTIFY_INFORMATION
  250. template <typename T>
  251. struct next_entry_offset_iterator
  252. {
  253. // Fulfill std::iterator_traits requirements
  254. using difference_type = ptrdiff_t;
  255. using value_type = T;
  256. using pointer = const T*;
  257. using reference = const T&;
  258. #ifdef _XUTILITY_
  259. using iterator_category = ::std::forward_iterator_tag;
  260. #endif
  261. next_entry_offset_iterator(T *iterable = __nullptr) : current_(iterable) {}
  262. // range based for requires operator!=, operator++ and operator* to do its work
  263. // on the type returned from begin() and end(), provide those here.
  264. bool operator!=(const next_entry_offset_iterator& other) const { return current_ != other.current_; }
  265. next_entry_offset_iterator& operator++()
  266. {
  267. current_ = (current_->NextEntryOffset != 0) ?
  268. reinterpret_cast<T *>(reinterpret_cast<unsigned char*>(current_) + current_->NextEntryOffset) :
  269. __nullptr;
  270. return *this;
  271. }
  272. next_entry_offset_iterator operator++(int)
  273. {
  274. auto copy = *this;
  275. ++(*this);
  276. return copy;
  277. }
  278. reference operator*() const WI_NOEXCEPT { return *current_; }
  279. pointer operator->() const WI_NOEXCEPT { return current_; }
  280. next_entry_offset_iterator<T> begin() { return *this; }
  281. next_entry_offset_iterator<T> end() { return next_entry_offset_iterator<T>(); }
  282. T* current_;
  283. };
  284. template <typename T>
  285. next_entry_offset_iterator<T> create_next_entry_offset_iterator(T* p)
  286. {
  287. return next_entry_offset_iterator<T>(p);
  288. }
  289. #pragma region Folder Watcher
  290. // Example use in exception based code:
  291. // auto watcher = wil::make_folder_watcher(folder.Path().c_str(), true, wil::allChangeEvents, []()
  292. // {
  293. // // respond
  294. // });
  295. //
  296. // Example use in result code based code:
  297. // wil::unique_folder_watcher watcher;
  298. // THROW_IF_FAILED(watcher.create(folder, true, wil::allChangeEvents, []()
  299. // {
  300. // // respond
  301. // }));
  302. enum class FolderChangeEvent : DWORD
  303. {
  304. ChangesLost = 0, // requies special handling, reset state as events were lost
  305. Added = FILE_ACTION_ADDED,
  306. Removed = FILE_ACTION_REMOVED,
  307. Modified = FILE_ACTION_MODIFIED,
  308. RenameOldName = FILE_ACTION_RENAMED_OLD_NAME,
  309. RenameNewName = FILE_ACTION_RENAMED_NEW_NAME,
  310. };
  311. enum class FolderChangeEvents : DWORD
  312. {
  313. None = 0,
  314. FileName = FILE_NOTIFY_CHANGE_FILE_NAME,
  315. DirectoryName = FILE_NOTIFY_CHANGE_DIR_NAME,
  316. Attributes = FILE_NOTIFY_CHANGE_ATTRIBUTES,
  317. FileSize = FILE_NOTIFY_CHANGE_SIZE,
  318. LastWriteTime = FILE_NOTIFY_CHANGE_LAST_WRITE,
  319. Security = FILE_NOTIFY_CHANGE_SECURITY,
  320. All = FILE_NOTIFY_CHANGE_FILE_NAME |
  321. FILE_NOTIFY_CHANGE_DIR_NAME |
  322. FILE_NOTIFY_CHANGE_ATTRIBUTES |
  323. FILE_NOTIFY_CHANGE_SIZE |
  324. FILE_NOTIFY_CHANGE_LAST_WRITE |
  325. FILE_NOTIFY_CHANGE_SECURITY
  326. };
  327. DEFINE_ENUM_FLAG_OPERATORS(FolderChangeEvents);
  328. /// @cond
  329. namespace details
  330. {
  331. struct folder_watcher_state
  332. {
  333. folder_watcher_state(wistd::function<void()> &&callback) : m_callback(wistd::move(callback))
  334. {
  335. }
  336. wistd::function<void()> m_callback;
  337. // Order is important, need to close the thread pool wait before the change handle.
  338. unique_hfind_change m_findChangeHandle;
  339. unique_threadpool_wait m_threadPoolWait;
  340. };
  341. inline void delete_folder_watcher_state(_In_opt_ folder_watcher_state *storage) { delete storage; }
  342. typedef resource_policy<folder_watcher_state *, decltype(&details::delete_folder_watcher_state),
  343. details::delete_folder_watcher_state, details::pointer_access_none> folder_watcher_state_resource_policy;
  344. }
  345. /// @endcond
  346. template <typename storage_t, typename err_policy = err_exception_policy>
  347. class folder_watcher_t : public storage_t
  348. {
  349. public:
  350. // forward all base class constructors...
  351. template <typename... args_t>
  352. explicit folder_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {}
  353. // HRESULT or void error handling...
  354. typedef typename err_policy::result result;
  355. // Exception-based constructors
  356. folder_watcher_t(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  357. {
  358. static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
  359. create(folderToWatch, isRecursive, filter, wistd::move(callback));
  360. }
  361. result create(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  362. {
  363. return err_policy::HResult(create_common(folderToWatch, isRecursive, filter, wistd::move(callback)));
  364. }
  365. private:
  366. // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
  367. // to __stdcall
  368. static void __stdcall callback(PTP_CALLBACK_INSTANCE /*Instance*/, void *context, TP_WAIT *pThreadPoolWait, TP_WAIT_RESULT /*result*/)
  369. {
  370. auto watcherState = static_cast<details::folder_watcher_state *>(context);
  371. watcherState->m_callback();
  372. // Rearm the wait. Should not fail with valid parameters.
  373. FindNextChangeNotification(watcherState->m_findChangeHandle.get());
  374. SetThreadpoolWait(pThreadPoolWait, watcherState->m_findChangeHandle.get(), __nullptr);
  375. }
  376. // This function exists to avoid template expansion of this code based on err_policy.
  377. HRESULT create_common(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  378. {
  379. wistd::unique_ptr<details::folder_watcher_state> watcherState(new(std::nothrow) details::folder_watcher_state(wistd::move(callback)));
  380. RETURN_IF_NULL_ALLOC(watcherState);
  381. watcherState->m_findChangeHandle.reset(FindFirstChangeNotificationW(folderToWatch, isRecursive, static_cast<DWORD>(filter)));
  382. RETURN_LAST_ERROR_IF(!watcherState->m_findChangeHandle);
  383. watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(&folder_watcher_t::callback, watcherState.get(), __nullptr));
  384. RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait);
  385. this->reset(watcherState.release()); // no more failures after this, pass ownership
  386. SetThreadpoolWait(this->get()->m_threadPoolWait.get(), this->get()->m_findChangeHandle.get(), __nullptr);
  387. return S_OK;
  388. }
  389. };
  390. typedef unique_any_t<folder_watcher_t<details::unique_storage<details::folder_watcher_state_resource_policy>, err_returncode_policy>> unique_folder_watcher_nothrow;
  391. inline unique_folder_watcher_nothrow make_folder_watcher_nothrow(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback) WI_NOEXCEPT
  392. {
  393. unique_folder_watcher_nothrow watcher;
  394. watcher.create(folderToWatch, isRecursive, filter, wistd::move(callback));
  395. return watcher; // caller must test for success using if (watcher)
  396. }
  397. #ifdef WIL_ENABLE_EXCEPTIONS
  398. typedef unique_any_t<folder_watcher_t<details::unique_storage<details::folder_watcher_state_resource_policy>, err_exception_policy>> unique_folder_watcher;
  399. inline unique_folder_watcher make_folder_watcher(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
  400. {
  401. return unique_folder_watcher(folderToWatch, isRecursive, filter, wistd::move(callback));
  402. }
  403. #endif // WIL_ENABLE_EXCEPTIONS
  404. #pragma endregion
  405. #pragma region Folder Reader
  406. // Example use for throwing:
  407. // auto reader = wil::make_folder_change_reader(folder.Path().c_str(), true, wil::FolderChangeEvents::All,
  408. // [](wil::FolderChangeEvent event, PCWSTR fileName)
  409. // {
  410. // switch (event)
  411. // {
  412. // case wil::FolderChangeEvent::ChangesLost: break;
  413. // case wil::FolderChangeEvent::Added: break;
  414. // case wil::FolderChangeEvent::Removed: break;
  415. // case wil::FolderChangeEvent::Modified: break;
  416. // case wil::FolderChangeEvent::RenamedOldName: break;
  417. // case wil::FolderChangeEvent::RenamedNewName: break;
  418. // });
  419. //
  420. // Example use for non throwing:
  421. // wil::unique_folder_change_reader_nothrow reader;
  422. // THROW_IF_FAILED(reader.create(folder, true, wil::FolderChangeEvents::All,
  423. // [](wil::FolderChangeEvent event, PCWSTR fileName)
  424. // {
  425. // // handle changes
  426. // }));
  427. //
  428. // @cond
  429. namespace details
  430. {
  431. struct folder_change_reader_state
  432. {
  433. folder_change_reader_state(bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  434. : m_callback(wistd::move(callback)), m_isRecursive(isRecursive), m_filter(filter)
  435. {
  436. }
  437. ~folder_change_reader_state()
  438. {
  439. if (m_tpIo != __nullptr)
  440. {
  441. TP_IO *tpIo = m_tpIo;
  442. // Indicate to the callback function that this object is being torn
  443. // down.
  444. {
  445. auto autoLock = m_cancelLock.lock_exclusive();
  446. m_tpIo = __nullptr;
  447. }
  448. // Cancel IO to terminate the file system monitoring operation.
  449. if (m_folderHandle)
  450. {
  451. CancelIoEx(m_folderHandle.get(), &m_overlapped);
  452. }
  453. // Wait for callbacks to complete.
  454. //
  455. // N.B. This is a blocking call and must not be made within a
  456. // callback or within a lock which is taken inside the
  457. // callback.
  458. WaitForThreadpoolIoCallbacks(tpIo, TRUE);
  459. CloseThreadpoolIo(tpIo);
  460. }
  461. }
  462. HRESULT StartIo()
  463. {
  464. // Unfortunately we have to handle ref-counting of IOs on behalf of the
  465. // thread pool.
  466. StartThreadpoolIo(m_tpIo);
  467. HRESULT hr = ReadDirectoryChangesW(m_folderHandle.get(), m_readBuffer, sizeof(m_readBuffer),
  468. m_isRecursive, static_cast<DWORD>(m_filter), __nullptr, &m_overlapped, __nullptr) ?
  469. S_OK : HRESULT_FROM_WIN32(::GetLastError());
  470. if (FAILED(hr))
  471. {
  472. // This operation does not have the usual semantic of returning
  473. // ERROR_IO_PENDING.
  474. // WI_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING));
  475. // If the operation failed for whatever reason, ensure the TP
  476. // ref counts are accurate.
  477. CancelThreadpoolIo(m_tpIo);
  478. }
  479. return hr;
  480. }
  481. // void (wil::FolderChangeEvent event, PCWSTR fileName)
  482. wistd::function<void(FolderChangeEvent, PCWSTR)> m_callback;
  483. unique_handle m_folderHandle;
  484. BOOL m_isRecursive = FALSE;
  485. FolderChangeEvents m_filter = FolderChangeEvents::None;
  486. OVERLAPPED m_overlapped{};
  487. TP_IO *m_tpIo = __nullptr;
  488. srwlock m_cancelLock;
  489. char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
  490. };
  491. inline void delete_folder_change_reader_state(_In_opt_ folder_change_reader_state *storage) { delete storage; }
  492. typedef resource_policy<folder_change_reader_state *, decltype(&details::delete_folder_change_reader_state),
  493. details::delete_folder_change_reader_state, details::pointer_access_none> folder_change_reader_state_resource_policy;
  494. }
  495. /// @endcond
  496. template <typename storage_t, typename err_policy = err_exception_policy>
  497. class folder_change_reader_t : public storage_t
  498. {
  499. public:
  500. // forward all base class constructors...
  501. template <typename... args_t>
  502. explicit folder_change_reader_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {}
  503. // HRESULT or void error handling...
  504. typedef typename err_policy::result result;
  505. // Exception-based constructors
  506. folder_change_reader_t(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  507. {
  508. static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
  509. create(folderToWatch, isRecursive, filter, wistd::move(callback));
  510. }
  511. result create(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  512. {
  513. return err_policy::HResult(create_common(folderToWatch, isRecursive, filter, wistd::move(callback)));
  514. }
  515. wil::unique_hfile& folder_handle() { return this->get()->m_folderHandle; }
  516. private:
  517. // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
  518. // to __stdcall
  519. static void __stdcall callback(PTP_CALLBACK_INSTANCE /* Instance */, void *context, void * /*overlapped*/,
  520. ULONG result, ULONG_PTR /* BytesTransferred */, TP_IO * /* Io */)
  521. {
  522. auto readerState = static_cast<details::folder_change_reader_state *>(context);
  523. // WI_ASSERT(overlapped == &readerState->m_overlapped);
  524. bool requeue = true;
  525. if (result == ERROR_SUCCESS)
  526. {
  527. for (auto const& info : create_next_entry_offset_iterator(reinterpret_cast<FILE_NOTIFY_INFORMATION *>(readerState->m_readBuffer)))
  528. {
  529. wchar_t realtiveFileName[MAX_PATH];
  530. StringCchCopyNW(realtiveFileName, ARRAYSIZE(realtiveFileName), info.FileName, info.FileNameLength / sizeof(info.FileName[0]));
  531. readerState->m_callback(static_cast<FolderChangeEvent>(info.Action), realtiveFileName);
  532. }
  533. }
  534. else if (result == ERROR_NOTIFY_ENUM_DIR)
  535. {
  536. readerState->m_callback(FolderChangeEvent::ChangesLost, __nullptr);
  537. }
  538. else
  539. {
  540. requeue = false;
  541. }
  542. if (requeue)
  543. {
  544. // If the lock is held non-shared or the TP IO is nullptr, this
  545. // structure is being torn down. Otherwise, monitor for further
  546. // changes.
  547. auto autoLock = readerState->m_cancelLock.try_lock_shared();
  548. if (autoLock && readerState->m_tpIo)
  549. {
  550. readerState->StartIo(); // ignoring failure here
  551. }
  552. }
  553. }
  554. // This function exists to avoid template expansion of this code based on err_policy.
  555. HRESULT create_common(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  556. {
  557. wistd::unique_ptr<details::folder_change_reader_state> readerState(new(std::nothrow) details::folder_change_reader_state(
  558. isRecursive, filter, wistd::move(callback)));
  559. RETURN_IF_NULL_ALLOC(readerState);
  560. readerState->m_folderHandle.reset(CreateFileW(folderToWatch,
  561. FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
  562. __nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, __nullptr));
  563. RETURN_LAST_ERROR_IF(!readerState->m_folderHandle);
  564. readerState->m_tpIo = CreateThreadpoolIo(readerState->m_folderHandle.get(), &folder_change_reader_t::callback, readerState.get(), __nullptr);
  565. RETURN_LAST_ERROR_IF_NULL(readerState->m_tpIo);
  566. RETURN_IF_FAILED(readerState->StartIo());
  567. this->reset(readerState.release());
  568. return S_OK;
  569. }
  570. };
  571. 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;
  572. inline unique_folder_change_reader_nothrow make_folder_change_reader_nothrow(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter,
  573. wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback) WI_NOEXCEPT
  574. {
  575. unique_folder_change_reader_nothrow watcher;
  576. watcher.create(folderToWatch, isRecursive, filter, wistd::move(callback));
  577. return watcher; // caller must test for success using if (watcher)
  578. }
  579. #ifdef WIL_ENABLE_EXCEPTIONS
  580. 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;
  581. inline unique_folder_change_reader make_folder_change_reader(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter,
  582. wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
  583. {
  584. return unique_folder_change_reader(folderToWatch, isRecursive, filter, wistd::move(callback));
  585. }
  586. #endif // WIL_ENABLE_EXCEPTIONS
  587. #pragma endregion
  588. //! Dos and VolumeGuid paths are always extended length paths with the \\?\ prefix.
  589. enum class VolumePrefix
  590. {
  591. Dos = VOLUME_NAME_DOS, // Extended Dos Device path form, e.g. \\?\C:\Users\Chris\AppData\Local\Temp\wil8C31.tmp
  592. VolumeGuid = VOLUME_NAME_GUID, // \\?\Volume{588fb606-b95b-4eae-b3cb-1e49861aaf18}\Users\Chris\AppData\Local\Temp\wil8C31.tmp
  593. // The following are special paths which can't be used with Win32 APIs, but are useful in other scenarios.
  594. None = VOLUME_NAME_NONE, // Path without the volume root, e.g. \Users\Chris\AppData\Local\Temp\wil8C31.tmp
  595. NtObjectName = VOLUME_NAME_NT, // Unique name used by Object Manager, e.g. \Device\HarddiskVolume4\Users\Chris\AppData\Local\Temp\wil8C31.tmp
  596. };
  597. enum class PathOptions
  598. {
  599. Normalized = FILE_NAME_NORMALIZED,
  600. Opened = FILE_NAME_OPENED,
  601. };
  602. DEFINE_ENUM_FLAG_OPERATORS(PathOptions);
  603. /** A strongly typed version of the Win32 API GetFinalPathNameByHandleW.
  604. Get the full path name in different forms
  605. Use this instead + VolumePrefix::None instead of GetFileInformationByHandleEx(FileNameInfo) to
  606. get that path form. */
  607. template <typename string_type, size_t stackBufferLength = 256>
  608. HRESULT GetFinalPathNameByHandleW(HANDLE fileHandle, string_type& path,
  609. wil::VolumePrefix volumePrefix = wil::VolumePrefix::Dos, wil::PathOptions options = wil::PathOptions::Normalized)
  610. {
  611. return AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
  612. [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
  613. {
  614. *valueLengthNeededWithNull = ::GetFinalPathNameByHandleW(fileHandle, value, static_cast<DWORD>(valueLength),
  615. static_cast<DWORD>(volumePrefix) | static_cast<DWORD>(options));
  616. RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
  617. WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
  618. if (*valueLengthNeededWithNull < valueLength)
  619. {
  620. (*valueLengthNeededWithNull)++; // it fit, account for the null
  621. }
  622. return S_OK;
  623. });
  624. }
  625. #ifdef WIL_ENABLE_EXCEPTIONS
  626. /** A strongly typed version of the Win32 API GetFinalPathNameByHandleW.
  627. Get the full path name in different forms. Use this + VolumePrefix::None
  628. instead of GetFileInformationByHandleEx(FileNameInfo) to get that path form. */
  629. template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
  630. string_type GetFinalPathNameByHandleW(HANDLE fileHandle,
  631. wil::VolumePrefix volumePrefix = wil::VolumePrefix::Dos, wil::PathOptions options = wil::PathOptions::Normalized)
  632. {
  633. string_type result;
  634. THROW_IF_FAILED((GetFinalPathNameByHandleW<string_type, stackBufferLength>(fileHandle, result, volumePrefix, options)));
  635. return result;
  636. }
  637. #endif
  638. //! A strongly typed version of the Win32 API of GetCurrentDirectoryW.
  639. //! Return a path in an allocated buffer for handling long paths.
  640. template <typename string_type, size_t stackBufferLength = 256>
  641. HRESULT GetCurrentDirectoryW(string_type& path)
  642. {
  643. return AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
  644. [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
  645. {
  646. *valueLengthNeededWithNull = ::GetCurrentDirectoryW(static_cast<DWORD>(valueLength), value);
  647. RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
  648. WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
  649. if (*valueLengthNeededWithNull < valueLength)
  650. {
  651. (*valueLengthNeededWithNull)++; // it fit, account for the null
  652. }
  653. return S_OK;
  654. });
  655. }
  656. #ifdef WIL_ENABLE_EXCEPTIONS
  657. //! A strongly typed version of the Win32 API of GetCurrentDirectoryW.
  658. //! Return a path in an allocated buffer for handling long paths.
  659. template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
  660. string_type GetCurrentDirectoryW()
  661. {
  662. string_type result;
  663. THROW_IF_FAILED((GetCurrentDirectoryW<string_type, stackBufferLength>(result)));
  664. return result;
  665. }
  666. #endif
  667. // TODO: add support for these and other similar APIs.
  668. // GetShortPathNameW()
  669. // GetLongPathNameW()
  670. // GetWindowsDirectory()
  671. // GetTempDirectory()
  672. /// @cond
  673. namespace details
  674. {
  675. template <FILE_INFO_BY_HANDLE_CLASS infoClass> struct MapInfoClassToInfoStruct; // failure to map is a usage error caught by the compiler
  676. #define MAP_INFOCLASS_TO_STRUCT(InfoClass, InfoStruct, IsFixed, Extra) \
  677. template <> struct MapInfoClassToInfoStruct<InfoClass> \
  678. { \
  679. typedef InfoStruct type; \
  680. static bool const isFixed = IsFixed; \
  681. static size_t const extraSize = Extra; \
  682. };
  683. MAP_INFOCLASS_TO_STRUCT(FileBasicInfo, FILE_BASIC_INFO, true, 0);
  684. MAP_INFOCLASS_TO_STRUCT(FileStandardInfo, FILE_STANDARD_INFO, true, 0);
  685. MAP_INFOCLASS_TO_STRUCT(FileNameInfo, FILE_NAME_INFO, false, 32);
  686. MAP_INFOCLASS_TO_STRUCT(FileRenameInfo, FILE_RENAME_INFO, false, 32);
  687. MAP_INFOCLASS_TO_STRUCT(FileDispositionInfo, FILE_DISPOSITION_INFO, true, 0);
  688. MAP_INFOCLASS_TO_STRUCT(FileAllocationInfo, FILE_ALLOCATION_INFO, true, 0);
  689. MAP_INFOCLASS_TO_STRUCT(FileEndOfFileInfo, FILE_END_OF_FILE_INFO, true, 0);
  690. MAP_INFOCLASS_TO_STRUCT(FileStreamInfo, FILE_STREAM_INFO, false, 32);
  691. MAP_INFOCLASS_TO_STRUCT(FileCompressionInfo, FILE_COMPRESSION_INFO, true, 0);
  692. MAP_INFOCLASS_TO_STRUCT(FileAttributeTagInfo, FILE_ATTRIBUTE_TAG_INFO, true, 0);
  693. MAP_INFOCLASS_TO_STRUCT(FileIdBothDirectoryInfo, FILE_ID_BOTH_DIR_INFO, false, 4096);
  694. MAP_INFOCLASS_TO_STRUCT(FileIdBothDirectoryRestartInfo, FILE_ID_BOTH_DIR_INFO, true, 0);
  695. MAP_INFOCLASS_TO_STRUCT(FileIoPriorityHintInfo, FILE_IO_PRIORITY_HINT_INFO, true, 0);
  696. MAP_INFOCLASS_TO_STRUCT(FileRemoteProtocolInfo, FILE_REMOTE_PROTOCOL_INFO, true, 0);
  697. MAP_INFOCLASS_TO_STRUCT(FileFullDirectoryInfo, FILE_FULL_DIR_INFO, false, 4096);
  698. MAP_INFOCLASS_TO_STRUCT(FileFullDirectoryRestartInfo, FILE_FULL_DIR_INFO, true, 0);
  699. #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
  700. MAP_INFOCLASS_TO_STRUCT(FileStorageInfo, FILE_STORAGE_INFO, true, 0);
  701. MAP_INFOCLASS_TO_STRUCT(FileAlignmentInfo, FILE_ALIGNMENT_INFO, true, 0);
  702. MAP_INFOCLASS_TO_STRUCT(FileIdInfo, FILE_ID_INFO, true, 0);
  703. MAP_INFOCLASS_TO_STRUCT(FileIdExtdDirectoryInfo, FILE_ID_EXTD_DIR_INFO, false, 4096);
  704. MAP_INFOCLASS_TO_STRUCT(FileIdExtdDirectoryRestartInfo, FILE_ID_EXTD_DIR_INFO, true, 0);
  705. #endif
  706. // Type unsafe version used in the implementation to avoid template bloat.
  707. inline HRESULT GetFileInfo(HANDLE fileHandle, FILE_INFO_BY_HANDLE_CLASS infoClass, size_t allocationSize,
  708. _Outptr_result_nullonfailure_ void **result)
  709. {
  710. *result = nullptr;
  711. wistd::unique_ptr<char[]> resultHolder(new(std::nothrow) char[allocationSize]);
  712. RETURN_IF_NULL_ALLOC(resultHolder);
  713. for (;;)
  714. {
  715. if (GetFileInformationByHandleEx(fileHandle, infoClass, resultHolder.get(), static_cast<DWORD>(allocationSize)))
  716. {
  717. *result = resultHolder.release();
  718. break;
  719. }
  720. else
  721. {
  722. DWORD const lastError = ::GetLastError();
  723. if (lastError == ERROR_MORE_DATA)
  724. {
  725. allocationSize *= 2;
  726. resultHolder.reset(new(std::nothrow) char[allocationSize]);
  727. RETURN_IF_NULL_ALLOC(resultHolder);
  728. }
  729. else if (lastError == ERROR_NO_MORE_FILES) // for folder enumeration cases
  730. {
  731. break;
  732. }
  733. else if (lastError == ERROR_INVALID_PARAMETER) // operation not supported by file system
  734. {
  735. return HRESULT_FROM_WIN32(lastError);
  736. }
  737. else
  738. {
  739. RETURN_WIN32(lastError);
  740. }
  741. }
  742. }
  743. return S_OK;
  744. }
  745. }
  746. /// @endcond
  747. /** Get file information for a variable sized structure, returns an HRESULT.
  748. ~~~
  749. wistd::unique_ptr<FILE_NAME_INFO> fileNameInfo;
  750. RETURN_IF_FAILED(GetFileInfoNoThrow<FileNameInfo>(fileHandle, fileNameInfo));
  751. ~~~
  752. */
  753. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<!details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  754. HRESULT GetFileInfoNoThrow(HANDLE fileHandle, wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> &result) WI_NOEXCEPT
  755. {
  756. void *rawResult;
  757. HRESULT hr = details::GetFileInfo(fileHandle, infoClass,
  758. sizeof(typename details::MapInfoClassToInfoStruct<infoClass>::type) + details::MapInfoClassToInfoStruct<infoClass>::extraSize,
  759. &rawResult);
  760. result.reset(static_cast<typename details::MapInfoClassToInfoStruct<infoClass>::type*>(rawResult));
  761. RETURN_HR_IF_EXPECTED(hr, hr == E_INVALIDARG); // operation not supported by file system
  762. RETURN_IF_FAILED(hr);
  763. return S_OK;
  764. }
  765. /** Get file information for a fixed sized structure, returns an HRESULT.
  766. ~~~
  767. FILE_BASIC_INFO fileBasicInfo;
  768. RETURN_IF_FAILED(GetFileInfoNoThrow<FileBasicInfo>(fileHandle, &fileBasicInfo));
  769. ~~~
  770. */
  771. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  772. HRESULT GetFileInfoNoThrow(HANDLE fileHandle, _Out_ typename details::MapInfoClassToInfoStruct<infoClass>::type *result) WI_NOEXCEPT
  773. {
  774. const HRESULT hr = GetFileInformationByHandleEx(fileHandle, infoClass, result, sizeof(*result)) ?
  775. S_OK : HRESULT_FROM_WIN32(::GetLastError());
  776. RETURN_HR_IF_EXPECTED(hr, hr == E_INVALIDARG); // operation not supported by file system
  777. RETURN_IF_FAILED(hr);
  778. return S_OK;
  779. }
  780. #ifdef _CPPUNWIND
  781. /** Get file information for a fixed sized structure, throws on failure.
  782. ~~~
  783. auto fileBasicInfo = GetFileInfo<FileBasicInfo>(fileHandle);
  784. ~~~
  785. */
  786. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  787. typename details::MapInfoClassToInfoStruct<infoClass>::type GetFileInfo(HANDLE fileHandle)
  788. {
  789. typename details::MapInfoClassToInfoStruct<infoClass>::type result;
  790. THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, &result));
  791. return result;
  792. }
  793. /** Get file information for a variable sized structure, throws on failure.
  794. ~~~
  795. auto fileBasicInfo = GetFileInfo<FileNameInfo>(fileHandle);
  796. ~~~
  797. */
  798. template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<!details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
  799. wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> GetFileInfo(HANDLE fileHandle)
  800. {
  801. wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> result;
  802. THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, result));
  803. return result;
  804. }
  805. #endif // _CPPUNWIND
  806. #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
  807. }
  808. #endif // __WIL_FILESYSTEM_INCLUDED