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