|
- //*********************************************************
- //
- // Copyright (c) Microsoft. All rights reserved.
- // This code is licensed under the MIT License.
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
- // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- // PARTICULAR PURPOSE AND NONINFRINGEMENT.
- //
- //*********************************************************
- #ifndef __WIL_FILESYSTEM_INCLUDED
- #define __WIL_FILESYSTEM_INCLUDED
-
- #ifdef _KERNEL_MODE
- #error This header is not supported in kernel-mode.
- #endif
-
- #include <new>
- #include <combaseapi.h> // Needed for CoTaskMemFree() used in output of some helpers.
- #include <winbase.h> // LocalAlloc
- #include <PathCch.h>
- #include "result.h"
- #include "win32_helpers.h"
- #include "resource.h"
-
- namespace wil
- {
- //! Determines if a path is an extended length path that can be used to access paths longer than MAX_PATH.
- inline bool is_extended_length_path(_In_ PCWSTR path)
- {
- return wcsncmp(path, L"\\\\?\\", 4) == 0;
- }
-
- //! Find the last segment of a path. Matches the behavior of shlwapi!PathFindFileNameW()
- //! note, does not support streams being specified like PathFindFileNameW(), is that a bug or a feature?
- inline PCWSTR find_last_path_segment(_In_ PCWSTR path)
- {
- auto const pathLength = wcslen(path);
- // If there is a trailing slash ignore that in the search.
- auto const limitedLength = ((pathLength > 0) && (path[pathLength - 1] == L'\\')) ? (pathLength - 1) : pathLength;
-
- PCWSTR result;
- auto const offset = FindStringOrdinal(FIND_FROMEND, path, static_cast<int>(limitedLength), L"\\", 1, TRUE);
- if (offset == -1)
- {
- result = path + pathLength; // null terminator
- }
- else
- {
- result = path + offset + 1; // just past the slash
- }
- return result;
- }
-
- //! Determine if the file name is one of the special "." or ".." names.
- inline bool path_is_dot_or_dotdot(_In_ PCWSTR fileName)
- {
- return ((fileName[0] == L'.') &&
- ((fileName[1] == L'\0') || ((fileName[1] == L'.') && (fileName[2] == L'\0'))));
- }
-
- //! Returns the drive number, if it has one. Returns true if there is a drive number, false otherwise. Supports regular and extended length paths.
- inline bool try_get_drive_letter_number(_In_ PCWSTR path, _Out_ int* driveNumber)
- {
- if (path[0] == L'\\' && path[1] == L'\\' && path[2] == L'?' && path[3] == L'\\')
- {
- path += 4;
- }
- if (path[0] && (path[1] == L':'))
- {
- if ((path[0] >= L'a') && (path[0] <= L'z'))
- {
- *driveNumber = path[0] - L'a';
- return true;
- }
- else if ((path[0] >= L'A') && (path[0] <= L'Z'))
- {
- *driveNumber = path[0] - L'A';
- return true;
- }
- }
- *driveNumber = -1;
- return false;
- }
-
- #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
-
- // PathCch.h APIs are only in desktop API for now.
-
- // Compute the substring in the input value that is the parent folder path.
- // returns:
- // true + parentPathLength - path has a parent starting at the beginning path and of parentPathLength length.
- // false, no parent path, the input is a root path.
- inline bool try_get_parent_path_range(_In_ PCWSTR path, _Out_ size_t* parentPathLength)
- {
- *parentPathLength = 0;
- bool hasParent = false;
- PCWSTR rootEnd;
- if (SUCCEEDED(PathCchSkipRoot(path, &rootEnd)) && (*rootEnd != L'\0'))
- {
- auto const lastSegment = find_last_path_segment(path);
- *parentPathLength = lastSegment - path;
- hasParent = (*parentPathLength != 0);
- }
- return hasParent;
- }
-
- // Creates directories for the specified path, creating parent paths
- // as needed.
- inline HRESULT CreateDirectoryDeepNoThrow(PCWSTR path) WI_NOEXCEPT
- {
- if (::CreateDirectoryW(path, nullptr) == FALSE)
- {
- DWORD const lastError = ::GetLastError();
- if (lastError == ERROR_PATH_NOT_FOUND)
- {
- size_t parentLength;
- if (try_get_parent_path_range(path, &parentLength))
- {
- wistd::unique_ptr<wchar_t[]> parent(new (std::nothrow) wchar_t[parentLength + 1]);
- RETURN_IF_NULL_ALLOC(parent.get());
- RETURN_IF_FAILED(StringCchCopyNW(parent.get(), parentLength + 1, path, parentLength));
- CreateDirectoryDeepNoThrow(parent.get()); // recurs
- }
- RETURN_IF_WIN32_BOOL_FALSE(::CreateDirectoryW(path, nullptr));
- }
- else if (lastError != ERROR_ALREADY_EXISTS)
- {
- RETURN_WIN32(lastError);
- }
- }
- return S_OK;
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- inline void CreateDirectoryDeep(PCWSTR path)
- {
- THROW_IF_FAILED(CreateDirectoryDeepNoThrow(path));
- }
- #endif // WIL_ENABLE_EXCEPTIONS
-
- //! A strongly typed version of the Win32 API GetFullPathNameW.
- //! Return a path in an allocated buffer for handling long paths.
- //! Optionally return the pointer to the file name part.
- template <typename string_type, size_t stackBufferLength = 256>
- HRESULT GetFullPathNameW(PCWSTR file, string_type& path, _Outptr_opt_ PCWSTR* filePart = nullptr)
- {
- wil::assign_null_to_opt_param(filePart);
- const auto hr = AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
- [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
- {
- // Note that GetFullPathNameW() is not limited to MAX_PATH
- // but it does take a fixed size buffer.
- *valueLengthNeededWithNull = ::GetFullPathNameW(file, static_cast<DWORD>(valueLength), value, nullptr);
- RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
- WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
- if (*valueLengthNeededWithNull < valueLength)
- {
- (*valueLengthNeededWithNull)++; // it fit, account for the null
- }
- return S_OK;
- });
- if (SUCCEEDED(hr) && filePart)
- {
- *filePart = wil::find_last_path_segment(details::string_maker<string_type>::get(path));
- }
- return hr;
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- //! A strongly typed version of the Win32 API of GetFullPathNameW.
- //! Return a path in an allocated buffer for handling long paths.
- //! Optionally return the pointer to the file name part.
- template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
- string_type GetFullPathNameW(PCWSTR file, _Outptr_opt_ PCWSTR* filePart = nullptr)
- {
- string_type result;
- THROW_IF_FAILED((GetFullPathNameW<string_type, stackBufferLength>(file, result, filePart)));
- return result;
- }
- #endif
-
- enum class RemoveDirectoryOptions
- {
- None = 0,
- KeepRootDirectory = 0x1
- };
- DEFINE_ENUM_FLAG_OPERATORS(RemoveDirectoryOptions);
-
- // If inputPath is a non-normalized name be sure to pass an extended length form to ensure
- // it can be addressed and deleted.
- inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None) WI_NOEXCEPT
- {
- wil::unique_hlocal_string path;
- PATHCCH_OPTIONS combineOptions = PATHCCH_NONE;
-
- if (is_extended_length_path(inputPath))
- {
- path = wil::make_hlocal_string_nothrow(inputPath);
- RETURN_IF_NULL_ALLOC(path);
- // PathAllocCombine will convert extended length paths to regular paths if shorter than
- // MAX_PATH, avoid that behavior to provide access inputPath with non-normalized names.
- combineOptions = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH;
- }
- else
- {
- // For regular paths normalize here to get consistent results when searching and deleting.
- RETURN_IF_FAILED(wil::GetFullPathNameW(inputPath, path));
- combineOptions = PATHCCH_ALLOW_LONG_PATHS;
- }
-
- wil::unique_hlocal_string searchPath;
- RETURN_IF_FAILED(::PathAllocCombine(path.get(), L"*", combineOptions, &searchPath));
-
- WIN32_FIND_DATAW fd;
- wil::unique_hfind findHandle(::FindFirstFileW(searchPath.get(), &fd));
- RETURN_LAST_ERROR_IF(!findHandle);
-
- for (;;)
- {
- // skip "." and ".."
- if (!(WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY) && path_is_dot_or_dotdot(fd.cFileName)))
- {
- // Need to form an extended length path to provide the ability to delete paths > MAX_PATH
- // and files with non-normalized names (dots or spaces at the end).
- wil::unique_hlocal_string pathToDelete;
- RETURN_IF_FAILED(::PathAllocCombine(path.get(), fd.cFileName,
- PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &pathToDelete));
- if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
- {
- RemoveDirectoryOptions localOptions = options;
- RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory)));
- }
- else
- {
- // note: if pathToDelete is read-only this will fail, consider adding
- // RemoveDirectoryOptions::RemoveReadOnly to enable this behavior.
- RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
- }
- }
-
- if (!::FindNextFileW(findHandle.get(), &fd))
- {
- auto const err = ::GetLastError();
- if (err == ERROR_NO_MORE_FILES)
- {
- break;
- }
- RETURN_WIN32(err);
- }
- }
-
- if (WI_IsFlagClear(options, RemoveDirectoryOptions::KeepRootDirectory))
- {
- RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
- }
- return S_OK;
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- inline void RemoveDirectoryRecursive(PCWSTR path, RemoveDirectoryOptions options = RemoveDirectoryOptions::None)
- {
- THROW_IF_FAILED(RemoveDirectoryRecursiveNoThrow(path, options));
- }
- #endif // WIL_ENABLE_EXCEPTIONS
-
- // Range based for that supports Win32 structures that use NextEntryOffset as the basis of traversing
- // a result buffer that contains data. This is used in the following FileIO calls:
- // FileStreamInfo, FILE_STREAM_INFO
- // FileIdBothDirectoryInfo, FILE_ID_BOTH_DIR_INFO
- // FileFullDirectoryInfo, FILE_FULL_DIR_INFO
- // FileIdExtdDirectoryInfo, FILE_ID_EXTD_DIR_INFO
- // ReadDirectoryChangesW, FILE_NOTIFY_INFORMATION
-
- template <typename T>
- struct next_entry_offset_iterator
- {
- // Fulfill std::iterator_traits requirements
- using difference_type = ptrdiff_t;
- using value_type = T;
- using pointer = const T*;
- using reference = const T&;
- #ifdef _XUTILITY_
- using iterator_category = ::std::forward_iterator_tag;
- #endif
-
- next_entry_offset_iterator(T *iterable = __nullptr) : current_(iterable) {}
-
- // range based for requires operator!=, operator++ and operator* to do its work
- // on the type returned from begin() and end(), provide those here.
- bool operator!=(const next_entry_offset_iterator& other) const { return current_ != other.current_; }
-
- next_entry_offset_iterator& operator++()
- {
- current_ = (current_->NextEntryOffset != 0) ?
- reinterpret_cast<T *>(reinterpret_cast<unsigned char*>(current_) + current_->NextEntryOffset) :
- __nullptr;
- return *this;
- }
-
- next_entry_offset_iterator operator++(int)
- {
- auto copy = *this;
- ++(*this);
- return copy;
- }
-
- reference operator*() const WI_NOEXCEPT { return *current_; }
- pointer operator->() const WI_NOEXCEPT { return current_; }
-
- next_entry_offset_iterator<T> begin() { return *this; }
- next_entry_offset_iterator<T> end() { return next_entry_offset_iterator<T>(); }
-
- T* current_;
- };
-
- template <typename T>
- next_entry_offset_iterator<T> create_next_entry_offset_iterator(T* p)
- {
- return next_entry_offset_iterator<T>(p);
- }
-
- #pragma region Folder Watcher
- // Example use in exception based code:
- // auto watcher = wil::make_folder_watcher(folder.Path().c_str(), true, wil::allChangeEvents, []()
- // {
- // // respond
- // });
- //
- // Example use in result code based code:
- // wil::unique_folder_watcher watcher;
- // THROW_IF_FAILED(watcher.create(folder, true, wil::allChangeEvents, []()
- // {
- // // respond
- // }));
-
- enum class FolderChangeEvent : DWORD
- {
- ChangesLost = 0, // requies special handling, reset state as events were lost
- Added = FILE_ACTION_ADDED,
- Removed = FILE_ACTION_REMOVED,
- Modified = FILE_ACTION_MODIFIED,
- RenameOldName = FILE_ACTION_RENAMED_OLD_NAME,
- RenameNewName = FILE_ACTION_RENAMED_NEW_NAME,
- };
-
- enum class FolderChangeEvents : DWORD
- {
- None = 0,
- FileName = FILE_NOTIFY_CHANGE_FILE_NAME,
- DirectoryName = FILE_NOTIFY_CHANGE_DIR_NAME,
- Attributes = FILE_NOTIFY_CHANGE_ATTRIBUTES,
- FileSize = FILE_NOTIFY_CHANGE_SIZE,
- LastWriteTime = FILE_NOTIFY_CHANGE_LAST_WRITE,
- Security = FILE_NOTIFY_CHANGE_SECURITY,
- All = FILE_NOTIFY_CHANGE_FILE_NAME |
- FILE_NOTIFY_CHANGE_DIR_NAME |
- FILE_NOTIFY_CHANGE_ATTRIBUTES |
- FILE_NOTIFY_CHANGE_SIZE |
- FILE_NOTIFY_CHANGE_LAST_WRITE |
- FILE_NOTIFY_CHANGE_SECURITY
- };
- DEFINE_ENUM_FLAG_OPERATORS(FolderChangeEvents);
-
- /// @cond
- namespace details
- {
- struct folder_watcher_state
- {
- folder_watcher_state(wistd::function<void()> &&callback) : m_callback(wistd::move(callback))
- {
- }
- wistd::function<void()> m_callback;
- // Order is important, need to close the thread pool wait before the change handle.
- unique_hfind_change m_findChangeHandle;
- unique_threadpool_wait m_threadPoolWait;
- };
-
- inline void delete_folder_watcher_state(_In_opt_ folder_watcher_state *storage) { delete storage; }
-
- typedef resource_policy<folder_watcher_state *, decltype(&details::delete_folder_watcher_state),
- details::delete_folder_watcher_state, details::pointer_access_none> folder_watcher_state_resource_policy;
- }
- /// @endcond
-
- template <typename storage_t, typename err_policy = err_exception_policy>
- class folder_watcher_t : public storage_t
- {
- public:
- // forward all base class constructors...
- template <typename... args_t>
- explicit folder_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {}
-
- // HRESULT or void error handling...
- typedef typename err_policy::result result;
-
- // Exception-based constructors
- folder_watcher_t(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
- {
- static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
- create(folderToWatch, isRecursive, filter, wistd::move(callback));
- }
-
- result create(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
- {
- return err_policy::HResult(create_common(folderToWatch, isRecursive, filter, wistd::move(callback)));
- }
- private:
- // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
- // to __stdcall
- static void __stdcall callback(PTP_CALLBACK_INSTANCE /*Instance*/, void *context, TP_WAIT *pThreadPoolWait, TP_WAIT_RESULT /*result*/)
- {
- auto watcherState = static_cast<details::folder_watcher_state *>(context);
- watcherState->m_callback();
-
- // Rearm the wait. Should not fail with valid parameters.
- FindNextChangeNotification(watcherState->m_findChangeHandle.get());
- SetThreadpoolWait(pThreadPoolWait, watcherState->m_findChangeHandle.get(), __nullptr);
- }
-
- // This function exists to avoid template expansion of this code based on err_policy.
- HRESULT create_common(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
- {
- wistd::unique_ptr<details::folder_watcher_state> watcherState(new(std::nothrow) details::folder_watcher_state(wistd::move(callback)));
- RETURN_IF_NULL_ALLOC(watcherState);
-
- watcherState->m_findChangeHandle.reset(FindFirstChangeNotificationW(folderToWatch, isRecursive, static_cast<DWORD>(filter)));
- RETURN_LAST_ERROR_IF(!watcherState->m_findChangeHandle);
-
- watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(&folder_watcher_t::callback, watcherState.get(), __nullptr));
- RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait);
- this->reset(watcherState.release()); // no more failures after this, pass ownership
- SetThreadpoolWait(this->get()->m_threadPoolWait.get(), this->get()->m_findChangeHandle.get(), __nullptr);
- return S_OK;
- }
- };
-
- typedef unique_any_t<folder_watcher_t<details::unique_storage<details::folder_watcher_state_resource_policy>, err_returncode_policy>> unique_folder_watcher_nothrow;
-
- inline unique_folder_watcher_nothrow make_folder_watcher_nothrow(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback) WI_NOEXCEPT
- {
- unique_folder_watcher_nothrow watcher;
- watcher.create(folderToWatch, isRecursive, filter, wistd::move(callback));
- return watcher; // caller must test for success using if (watcher)
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- typedef unique_any_t<folder_watcher_t<details::unique_storage<details::folder_watcher_state_resource_policy>, err_exception_policy>> unique_folder_watcher;
-
- inline unique_folder_watcher make_folder_watcher(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void()> &&callback)
- {
- return unique_folder_watcher(folderToWatch, isRecursive, filter, wistd::move(callback));
- }
- #endif // WIL_ENABLE_EXCEPTIONS
-
- #pragma endregion
-
- #pragma region Folder Reader
-
- // Example use for throwing:
- // auto reader = wil::make_folder_change_reader(folder.Path().c_str(), true, wil::FolderChangeEvents::All,
- // [](wil::FolderChangeEvent event, PCWSTR fileName)
- // {
- // switch (event)
- // {
- // case wil::FolderChangeEvent::ChangesLost: break;
- // case wil::FolderChangeEvent::Added: break;
- // case wil::FolderChangeEvent::Removed: break;
- // case wil::FolderChangeEvent::Modified: break;
- // case wil::FolderChangeEvent::RenamedOldName: break;
- // case wil::FolderChangeEvent::RenamedNewName: break;
- // });
- //
- // Example use for non throwing:
- // wil::unique_folder_change_reader_nothrow reader;
- // THROW_IF_FAILED(reader.create(folder, true, wil::FolderChangeEvents::All,
- // [](wil::FolderChangeEvent event, PCWSTR fileName)
- // {
- // // handle changes
- // }));
- //
-
- // @cond
- namespace details
- {
- struct folder_change_reader_state
- {
- folder_change_reader_state(bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
- : m_callback(wistd::move(callback)), m_isRecursive(isRecursive), m_filter(filter)
- {
- }
-
- ~folder_change_reader_state()
- {
- if (m_tpIo != __nullptr)
- {
- TP_IO *tpIo = m_tpIo;
-
- // Indicate to the callback function that this object is being torn
- // down.
-
- {
- auto autoLock = m_cancelLock.lock_exclusive();
- m_tpIo = __nullptr;
- }
-
- // Cancel IO to terminate the file system monitoring operation.
-
- if (m_folderHandle)
- {
- CancelIoEx(m_folderHandle.get(), &m_overlapped);
- }
-
- // Wait for callbacks to complete.
- //
- // N.B. This is a blocking call and must not be made within a
- // callback or within a lock which is taken inside the
- // callback.
-
- WaitForThreadpoolIoCallbacks(tpIo, TRUE);
- CloseThreadpoolIo(tpIo);
- }
- }
-
- HRESULT StartIo()
- {
- // Unfortunately we have to handle ref-counting of IOs on behalf of the
- // thread pool.
- StartThreadpoolIo(m_tpIo);
- HRESULT hr = ReadDirectoryChangesW(m_folderHandle.get(), m_readBuffer, sizeof(m_readBuffer),
- m_isRecursive, static_cast<DWORD>(m_filter), __nullptr, &m_overlapped, __nullptr) ?
- S_OK : HRESULT_FROM_WIN32(::GetLastError());
- if (FAILED(hr))
- {
- // This operation does not have the usual semantic of returning
- // ERROR_IO_PENDING.
- // WI_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING));
-
- // If the operation failed for whatever reason, ensure the TP
- // ref counts are accurate.
-
- CancelThreadpoolIo(m_tpIo);
- }
- return hr;
- }
-
- // void (wil::FolderChangeEvent event, PCWSTR fileName)
- wistd::function<void(FolderChangeEvent, PCWSTR)> m_callback;
- unique_handle m_folderHandle;
- BOOL m_isRecursive = FALSE;
- FolderChangeEvents m_filter = FolderChangeEvents::None;
- OVERLAPPED m_overlapped{};
- TP_IO *m_tpIo = __nullptr;
- srwlock m_cancelLock;
- char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
- };
-
- inline void delete_folder_change_reader_state(_In_opt_ folder_change_reader_state *storage) { delete storage; }
-
- typedef resource_policy<folder_change_reader_state *, decltype(&details::delete_folder_change_reader_state),
- details::delete_folder_change_reader_state, details::pointer_access_none> folder_change_reader_state_resource_policy;
- }
- /// @endcond
-
- template <typename storage_t, typename err_policy = err_exception_policy>
- class folder_change_reader_t : public storage_t
- {
- public:
- // forward all base class constructors...
- template <typename... args_t>
- explicit folder_change_reader_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...) {}
-
- // HRESULT or void error handling...
- typedef typename err_policy::result result;
-
- // Exception-based constructors
- folder_change_reader_t(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
- {
- static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
- create(folderToWatch, isRecursive, filter, wistd::move(callback));
- }
-
- result create(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
- {
- return err_policy::HResult(create_common(folderToWatch, isRecursive, filter, wistd::move(callback)));
- }
-
- wil::unique_hfile& folder_handle() { return this->get()->m_folderHandle; }
-
- private:
- // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
- // to __stdcall
- static void __stdcall callback(PTP_CALLBACK_INSTANCE /* Instance */, void *context, void * /*overlapped*/,
- ULONG result, ULONG_PTR /* BytesTransferred */, TP_IO * /* Io */)
- {
- auto readerState = static_cast<details::folder_change_reader_state *>(context);
- // WI_ASSERT(overlapped == &readerState->m_overlapped);
-
- bool requeue = true;
- if (result == ERROR_SUCCESS)
- {
- for (auto const& info : create_next_entry_offset_iterator(reinterpret_cast<FILE_NOTIFY_INFORMATION *>(readerState->m_readBuffer)))
- {
- wchar_t realtiveFileName[MAX_PATH];
- StringCchCopyNW(realtiveFileName, ARRAYSIZE(realtiveFileName), info.FileName, info.FileNameLength / sizeof(info.FileName[0]));
-
- readerState->m_callback(static_cast<FolderChangeEvent>(info.Action), realtiveFileName);
- }
- }
- else if (result == ERROR_NOTIFY_ENUM_DIR)
- {
- readerState->m_callback(FolderChangeEvent::ChangesLost, __nullptr);
- }
- else
- {
- requeue = false;
- }
-
- if (requeue)
- {
- // If the lock is held non-shared or the TP IO is nullptr, this
- // structure is being torn down. Otherwise, monitor for further
- // changes.
- auto autoLock = readerState->m_cancelLock.try_lock_shared();
- if (autoLock && readerState->m_tpIo)
- {
- readerState->StartIo(); // ignoring failure here
- }
- }
- }
-
- // This function exists to avoid template expansion of this code based on err_policy.
- HRESULT create_common(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter, wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
- {
- wistd::unique_ptr<details::folder_change_reader_state> readerState(new(std::nothrow) details::folder_change_reader_state(
- isRecursive, filter, wistd::move(callback)));
- RETURN_IF_NULL_ALLOC(readerState);
-
- readerState->m_folderHandle.reset(CreateFileW(folderToWatch,
- FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
- __nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, __nullptr));
- RETURN_LAST_ERROR_IF(!readerState->m_folderHandle);
-
- readerState->m_tpIo = CreateThreadpoolIo(readerState->m_folderHandle.get(), &folder_change_reader_t::callback, readerState.get(), __nullptr);
- RETURN_LAST_ERROR_IF_NULL(readerState->m_tpIo);
- RETURN_IF_FAILED(readerState->StartIo());
- this->reset(readerState.release());
- return S_OK;
- }
- };
-
- 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;
-
- inline unique_folder_change_reader_nothrow make_folder_change_reader_nothrow(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter,
- wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback) WI_NOEXCEPT
- {
- unique_folder_change_reader_nothrow watcher;
- watcher.create(folderToWatch, isRecursive, filter, wistd::move(callback));
- return watcher; // caller must test for success using if (watcher)
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- 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;
-
- inline unique_folder_change_reader make_folder_change_reader(PCWSTR folderToWatch, bool isRecursive, FolderChangeEvents filter,
- wistd::function<void(FolderChangeEvent, PCWSTR)> &&callback)
- {
- return unique_folder_change_reader(folderToWatch, isRecursive, filter, wistd::move(callback));
- }
- #endif // WIL_ENABLE_EXCEPTIONS
- #pragma endregion
-
- //! Dos and VolumeGuid paths are always extended length paths with the \\?\ prefix.
- enum class VolumePrefix
- {
- Dos = VOLUME_NAME_DOS, // Extended Dos Device path form, e.g. \\?\C:\Users\Chris\AppData\Local\Temp\wil8C31.tmp
- VolumeGuid = VOLUME_NAME_GUID, // \\?\Volume{588fb606-b95b-4eae-b3cb-1e49861aaf18}\Users\Chris\AppData\Local\Temp\wil8C31.tmp
- // The following are special paths which can't be used with Win32 APIs, but are useful in other scenarios.
- None = VOLUME_NAME_NONE, // Path without the volume root, e.g. \Users\Chris\AppData\Local\Temp\wil8C31.tmp
- NtObjectName = VOLUME_NAME_NT, // Unique name used by Object Manager, e.g. \Device\HarddiskVolume4\Users\Chris\AppData\Local\Temp\wil8C31.tmp
- };
- enum class PathOptions
- {
- Normalized = FILE_NAME_NORMALIZED,
- Opened = FILE_NAME_OPENED,
- };
- DEFINE_ENUM_FLAG_OPERATORS(PathOptions);
-
- /** A strongly typed version of the Win32 API GetFinalPathNameByHandleW.
- Get the full path name in different forms
- Use this instead + VolumePrefix::None instead of GetFileInformationByHandleEx(FileNameInfo) to
- get that path form. */
- template <typename string_type, size_t stackBufferLength = 256>
- HRESULT GetFinalPathNameByHandleW(HANDLE fileHandle, string_type& path,
- wil::VolumePrefix volumePrefix = wil::VolumePrefix::Dos, wil::PathOptions options = wil::PathOptions::Normalized)
- {
- return AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
- [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
- {
- *valueLengthNeededWithNull = ::GetFinalPathNameByHandleW(fileHandle, value, static_cast<DWORD>(valueLength),
- static_cast<DWORD>(volumePrefix) | static_cast<DWORD>(options));
- RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
- WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
- if (*valueLengthNeededWithNull < valueLength)
- {
- (*valueLengthNeededWithNull)++; // it fit, account for the null
- }
- return S_OK;
- });
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- /** A strongly typed version of the Win32 API GetFinalPathNameByHandleW.
- Get the full path name in different forms. Use this + VolumePrefix::None
- instead of GetFileInformationByHandleEx(FileNameInfo) to get that path form. */
- template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
- string_type GetFinalPathNameByHandleW(HANDLE fileHandle,
- wil::VolumePrefix volumePrefix = wil::VolumePrefix::Dos, wil::PathOptions options = wil::PathOptions::Normalized)
- {
- string_type result;
- THROW_IF_FAILED((GetFinalPathNameByHandleW<string_type, stackBufferLength>(fileHandle, result, volumePrefix, options)));
- return result;
- }
- #endif
-
- //! A strongly typed version of the Win32 API of GetCurrentDirectoryW.
- //! Return a path in an allocated buffer for handling long paths.
- template <typename string_type, size_t stackBufferLength = 256>
- HRESULT GetCurrentDirectoryW(string_type& path)
- {
- return AdaptFixedSizeToAllocatedResult<string_type, stackBufferLength>(path,
- [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNull) -> HRESULT
- {
- *valueLengthNeededWithNull = ::GetCurrentDirectoryW(static_cast<DWORD>(valueLength), value);
- RETURN_LAST_ERROR_IF(*valueLengthNeededWithNull == 0);
- WI_ASSERT((*value != L'\0') == (*valueLengthNeededWithNull < valueLength));
- if (*valueLengthNeededWithNull < valueLength)
- {
- (*valueLengthNeededWithNull)++; // it fit, account for the null
- }
- return S_OK;
- });
- }
-
- #ifdef WIL_ENABLE_EXCEPTIONS
- //! A strongly typed version of the Win32 API of GetCurrentDirectoryW.
- //! Return a path in an allocated buffer for handling long paths.
- template <typename string_type = wil::unique_cotaskmem_string, size_t stackBufferLength = 256>
- string_type GetCurrentDirectoryW()
- {
- string_type result;
- THROW_IF_FAILED((GetCurrentDirectoryW<string_type, stackBufferLength>(result)));
- return result;
- }
- #endif
-
- // TODO: add support for these and other similar APIs.
- // GetShortPathNameW()
- // GetLongPathNameW()
- // GetWindowsDirectory()
- // GetTempDirectory()
-
- /// @cond
- namespace details
- {
- template <FILE_INFO_BY_HANDLE_CLASS infoClass> struct MapInfoClassToInfoStruct; // failure to map is a usage error caught by the compiler
- #define MAP_INFOCLASS_TO_STRUCT(InfoClass, InfoStruct, IsFixed, Extra) \
- template <> struct MapInfoClassToInfoStruct<InfoClass> \
- { \
- typedef InfoStruct type; \
- static bool const isFixed = IsFixed; \
- static size_t const extraSize = Extra; \
- };
-
- MAP_INFOCLASS_TO_STRUCT(FileBasicInfo, FILE_BASIC_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileStandardInfo, FILE_STANDARD_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileNameInfo, FILE_NAME_INFO, false, 32);
- MAP_INFOCLASS_TO_STRUCT(FileRenameInfo, FILE_RENAME_INFO, false, 32);
- MAP_INFOCLASS_TO_STRUCT(FileDispositionInfo, FILE_DISPOSITION_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileAllocationInfo, FILE_ALLOCATION_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileEndOfFileInfo, FILE_END_OF_FILE_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileStreamInfo, FILE_STREAM_INFO, false, 32);
- MAP_INFOCLASS_TO_STRUCT(FileCompressionInfo, FILE_COMPRESSION_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileAttributeTagInfo, FILE_ATTRIBUTE_TAG_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileIdBothDirectoryInfo, FILE_ID_BOTH_DIR_INFO, false, 4096);
- MAP_INFOCLASS_TO_STRUCT(FileIdBothDirectoryRestartInfo, FILE_ID_BOTH_DIR_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileIoPriorityHintInfo, FILE_IO_PRIORITY_HINT_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileRemoteProtocolInfo, FILE_REMOTE_PROTOCOL_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileFullDirectoryInfo, FILE_FULL_DIR_INFO, false, 4096);
- MAP_INFOCLASS_TO_STRUCT(FileFullDirectoryRestartInfo, FILE_FULL_DIR_INFO, true, 0);
- #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
- MAP_INFOCLASS_TO_STRUCT(FileStorageInfo, FILE_STORAGE_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileAlignmentInfo, FILE_ALIGNMENT_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileIdInfo, FILE_ID_INFO, true, 0);
- MAP_INFOCLASS_TO_STRUCT(FileIdExtdDirectoryInfo, FILE_ID_EXTD_DIR_INFO, false, 4096);
- MAP_INFOCLASS_TO_STRUCT(FileIdExtdDirectoryRestartInfo, FILE_ID_EXTD_DIR_INFO, true, 0);
- #endif
-
- // Type unsafe version used in the implementation to avoid template bloat.
- inline HRESULT GetFileInfo(HANDLE fileHandle, FILE_INFO_BY_HANDLE_CLASS infoClass, size_t allocationSize,
- _Outptr_result_nullonfailure_ void **result)
- {
- *result = nullptr;
-
- wistd::unique_ptr<char[]> resultHolder(new(std::nothrow) char[allocationSize]);
- RETURN_IF_NULL_ALLOC(resultHolder);
-
- for (;;)
- {
- if (GetFileInformationByHandleEx(fileHandle, infoClass, resultHolder.get(), static_cast<DWORD>(allocationSize)))
- {
- *result = resultHolder.release();
- break;
- }
- else
- {
- DWORD const lastError = ::GetLastError();
- if (lastError == ERROR_MORE_DATA)
- {
- allocationSize *= 2;
- resultHolder.reset(new(std::nothrow) char[allocationSize]);
- RETURN_IF_NULL_ALLOC(resultHolder);
- }
- else if (lastError == ERROR_NO_MORE_FILES) // for folder enumeration cases
- {
- break;
- }
- else if (lastError == ERROR_INVALID_PARAMETER) // operation not supported by file system
- {
- return HRESULT_FROM_WIN32(lastError);
- }
- else
- {
- RETURN_WIN32(lastError);
- }
- }
- }
- return S_OK;
- }
- }
- /// @endcond
-
- /** Get file information for a variable sized structure, returns an HRESULT.
- ~~~
- wistd::unique_ptr<FILE_NAME_INFO> fileNameInfo;
- RETURN_IF_FAILED(GetFileInfoNoThrow<FileNameInfo>(fileHandle, fileNameInfo));
- ~~~
- */
- template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<!details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
- HRESULT GetFileInfoNoThrow(HANDLE fileHandle, wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> &result) WI_NOEXCEPT
- {
- void *rawResult;
- HRESULT hr = details::GetFileInfo(fileHandle, infoClass,
- sizeof(typename details::MapInfoClassToInfoStruct<infoClass>::type) + details::MapInfoClassToInfoStruct<infoClass>::extraSize,
- &rawResult);
- result.reset(static_cast<typename details::MapInfoClassToInfoStruct<infoClass>::type*>(rawResult));
- RETURN_HR_IF_EXPECTED(hr, hr == E_INVALIDARG); // operation not supported by file system
- RETURN_IF_FAILED(hr);
- return S_OK;
- }
-
- /** Get file information for a fixed sized structure, returns an HRESULT.
- ~~~
- FILE_BASIC_INFO fileBasicInfo;
- RETURN_IF_FAILED(GetFileInfoNoThrow<FileBasicInfo>(fileHandle, &fileBasicInfo));
- ~~~
- */
- template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
- HRESULT GetFileInfoNoThrow(HANDLE fileHandle, _Out_ typename details::MapInfoClassToInfoStruct<infoClass>::type *result) WI_NOEXCEPT
- {
- const HRESULT hr = GetFileInformationByHandleEx(fileHandle, infoClass, result, sizeof(*result)) ?
- S_OK : HRESULT_FROM_WIN32(::GetLastError());
- RETURN_HR_IF_EXPECTED(hr, hr == E_INVALIDARG); // operation not supported by file system
- RETURN_IF_FAILED(hr);
- return S_OK;
- }
-
- #ifdef _CPPUNWIND
- /** Get file information for a fixed sized structure, throws on failure.
- ~~~
- auto fileBasicInfo = GetFileInfo<FileBasicInfo>(fileHandle);
- ~~~
- */
- template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
- typename details::MapInfoClassToInfoStruct<infoClass>::type GetFileInfo(HANDLE fileHandle)
- {
- typename details::MapInfoClassToInfoStruct<infoClass>::type result;
- THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, &result));
- return result;
- }
-
- /** Get file information for a variable sized structure, throws on failure.
- ~~~
- auto fileBasicInfo = GetFileInfo<FileNameInfo>(fileHandle);
- ~~~
- */
- template <FILE_INFO_BY_HANDLE_CLASS infoClass, typename wistd::enable_if<!details::MapInfoClassToInfoStruct<infoClass>::isFixed, int>::type = 0>
- wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> GetFileInfo(HANDLE fileHandle)
- {
- wistd::unique_ptr<typename details::MapInfoClassToInfoStruct<infoClass>::type> result;
- THROW_IF_FAILED(GetFileInfoNoThrow<infoClass>(fileHandle, result));
- return result;
- }
- #endif // _CPPUNWIND
- #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
- }
-
- #endif // __WIL_FILESYSTEM_INCLUDED
|