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.
 
 
 
 
 
 

964 lines
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