text: //*********************************************************
  //
  //    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
  