Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/llfio.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com>2018-07-03 11:43:55 +0300
committerNiall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com>2018-07-03 11:43:55 +0300
commit53d415a3b69d2680c15ae2c56a9baf9b149ad71d (patch)
treeb55e046b3b7a2e504c9abebf4a15a3dddff67eba /include/llfio
parent69e9c42365a92bc1af57a843f48513e19aaf0cdf (diff)
Rename afio to llfio part 1 of many
Diffstat (limited to 'include/llfio')
-rw-r--r--include/llfio/afio.hpp22
-rw-r--r--include/llfio/afio.ixx4
m---------include/llfio/ntkernel-error-category0
-rw-r--r--include/llfio/revision.hpp4
-rw-r--r--include/llfio/v2.0/afio.hpp86
-rw-r--r--include/llfio/v2.0/algorithm/cached_parent_handle_adapter.hpp215
-rw-r--r--include/llfio/v2.0/algorithm/mapped_span.hpp124
-rw-r--r--include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp557
-rw-r--r--include/llfio/v2.0/algorithm/shared_fs_mutex/base.hpp244
-rw-r--r--include/llfio/v2.0/algorithm/shared_fs_mutex/byte_ranges.hpp253
-rw-r--r--include/llfio/v2.0/algorithm/shared_fs_mutex/lock_files.hpp241
-rw-r--r--include/llfio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp428
-rw-r--r--include/llfio/v2.0/algorithm/shared_fs_mutex/safe_byte_ranges.hpp179
-rw-r--r--include/llfio/v2.0/algorithm/trivial_vector.hpp713
-rw-r--r--include/llfio/v2.0/async_file_handle.hpp818
-rw-r--r--include/llfio/v2.0/config.hpp494
-rw-r--r--include/llfio/v2.0/deadline.h112
-rw-r--r--include/llfio/v2.0/detail/impl/cached_parent_handle_adapter.ipp151
-rw-r--r--include/llfio/v2.0/detail/impl/path_discovery.ipp262
-rw-r--r--include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp381
-rw-r--r--include/llfio/v2.0/detail/impl/posix/directory_handle.ipp424
-rw-r--r--include/llfio/v2.0/detail/impl/posix/file_handle.ipp492
-rw-r--r--include/llfio/v2.0/detail/impl/posix/fs_handle.ipp225
-rw-r--r--include/llfio/v2.0/detail/impl/posix/handle.ipp197
-rw-r--r--include/llfio/v2.0/detail/impl/posix/import.hpp116
-rw-r--r--include/llfio/v2.0/detail/impl/posix/io_handle.ipp317
-rw-r--r--include/llfio/v2.0/detail/impl/posix/io_service.ipp410
-rw-r--r--include/llfio/v2.0/detail/impl/posix/map_handle.ipp596
-rw-r--r--include/llfio/v2.0/detail/impl/posix/mapped_file_handle.ipp162
-rw-r--r--include/llfio/v2.0/detail/impl/posix/path_discovery.ipp145
-rw-r--r--include/llfio/v2.0/detail/impl/posix/path_handle.ipp61
-rw-r--r--include/llfio/v2.0/detail/impl/posix/stat.ipp225
-rw-r--r--include/llfio/v2.0/detail/impl/posix/statfs.ipp300
-rw-r--r--include/llfio/v2.0/detail/impl/posix/storage_profile.ipp325
-rw-r--r--include/llfio/v2.0/detail/impl/posix/utils.ipp229
-rw-r--r--include/llfio/v2.0/detail/impl/safe_byte_ranges.ipp438
-rw-r--r--include/llfio/v2.0/detail/impl/storage_profile.ipp1382
-rw-r--r--include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp277
-rw-r--r--include/llfio/v2.0/detail/impl/windows/directory_handle.ipp367
-rw-r--r--include/llfio/v2.0/detail/impl/windows/file_handle.ipp499
-rw-r--r--include/llfio/v2.0/detail/impl/windows/fs_handle.ipp290
-rw-r--r--include/llfio/v2.0/detail/impl/windows/handle.ipp123
-rw-r--r--include/llfio/v2.0/detail/impl/windows/import.hpp1478
-rw-r--r--include/llfio/v2.0/detail/impl/windows/io_handle.ipp220
-rw-r--r--include/llfio/v2.0/detail/impl/windows/io_service.ipp102
-rw-r--r--include/llfio/v2.0/detail/impl/windows/map_handle.ipp864
-rw-r--r--include/llfio/v2.0/detail/impl/windows/mapped_file_handle.ipp181
-rw-r--r--include/llfio/v2.0/detail/impl/windows/path_discovery.ipp126
-rw-r--r--include/llfio/v2.0/detail/impl/windows/path_handle.ipp101
-rw-r--r--include/llfio/v2.0/detail/impl/windows/path_view.ipp55
-rw-r--r--include/llfio/v2.0/detail/impl/windows/stat.ipp224
-rw-r--r--include/llfio/v2.0/detail/impl/windows/statfs.ipp205
-rw-r--r--include/llfio/v2.0/detail/impl/windows/storage_profile.ipp440
-rw-r--r--include/llfio/v2.0/detail/impl/windows/utils.ipp238
-rw-r--r--include/llfio/v2.0/directory_handle.hpp388
-rw-r--r--include/llfio/v2.0/file_handle.hpp437
-rw-r--r--include/llfio/v2.0/fs_handle.hpp272
-rw-r--r--include/llfio/v2.0/handle.hpp576
-rw-r--r--include/llfio/v2.0/io_handle.hpp611
-rw-r--r--include/llfio/v2.0/io_service.hpp331
-rw-r--r--include/llfio/v2.0/logging.hpp336
-rw-r--r--include/llfio/v2.0/map_handle.hpp748
-rw-r--r--include/llfio/v2.0/mapped_file_handle.hpp519
-rw-r--r--include/llfio/v2.0/native_handle_type.hpp147
m---------include/llfio/v2.0/outcome0
-rw-r--r--include/llfio/v2.0/path_discovery.hpp130
-rw-r--r--include/llfio/v2.0/path_handle.hpp141
-rw-r--r--include/llfio/v2.0/path_view.hpp573
m---------include/llfio/v2.0/quickcpplib0
-rw-r--r--include/llfio/v2.0/stat.hpp170
-rw-r--r--include/llfio/v2.0/statfs.hpp115
-rw-r--r--include/llfio/v2.0/status_code.hpp560
-rw-r--r--include/llfio/v2.0/storage_profile.hpp410
-rw-r--r--include/llfio/v2.0/utils.hpp294
-rw-r--r--include/llfio/version.hpp34
75 files changed, 23914 insertions, 0 deletions
diff --git a/include/llfio/afio.hpp b/include/llfio/afio.hpp
new file mode 100644
index 00000000..6e6163d6
--- /dev/null
+++ b/include/llfio/afio.hpp
@@ -0,0 +1,22 @@
+//! \file afio/afio.hpp The master *latest version* AFIO include file. All AFIO consuming libraries should include this header only.
+#include "version.hpp"
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define AFIO_HEADERS_PATH2 AFIO_VERSION_GLUE(v, AFIO_HEADERS_VERSION, /afio.hpp)
+#elif !__PCPP_ALWAYS_FALSE__
+#define AFIO_HEADERS_PATH2 AFIO_VERSION_GLUE(v, AFIO_HEADERS_VERSION,)/afio.hpp
+#endif
+
+#if 0 // cmake's Makefiles and Ninja generators won't pick up dependent headers without this
+#include "v2.0/afio.hpp"
+#endif
+
+#define AFIO_HEADERS_PATH4(a) #a
+#define AFIO_HEADERS_PATH3(a) AFIO_HEADERS_PATH4(a)
+//! \brief The AFIO headers path generated by the preprocessor from the version
+#define AFIO_HEADERS_PATH AFIO_HEADERS_PATH3(AFIO_HEADERS_PATH2)
+#include AFIO_HEADERS_PATH
+#undef AFIO_HEADERS_PATH
+#undef AFIO_HEADERS_PATH2
+#undef AFIO_HEADERS_PATH3
+#undef AFIO_HEADERS_PATH4
diff --git a/include/llfio/afio.ixx b/include/llfio/afio.ixx
new file mode 100644
index 00000000..f6197437
--- /dev/null
+++ b/include/llfio/afio.ixx
@@ -0,0 +1,4 @@
+// Tell the headers we are generating the interface for the library
+#define GENERATING_AFIO_MODULE_INTERFACE
+module afio_v2_0; // AFIO_MODULE_NAME
+#include "afio.hpp"
diff --git a/include/llfio/ntkernel-error-category b/include/llfio/ntkernel-error-category
new file mode 160000
+Subproject 8b1bc49c852feaadf99b815e63dffb8b6a7cd0c
diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp
new file mode 100644
index 00000000..0814ade3
--- /dev/null
+++ b/include/llfio/revision.hpp
@@ -0,0 +1,4 @@
+// Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time
+#define AFIO_PREVIOUS_COMMIT_REF bd06afea35d760e6de395b047a9524c55bf44588
+#define AFIO_PREVIOUS_COMMIT_DATE "2018-06-29 22:05:49 +00:00"
+#define AFIO_PREVIOUS_COMMIT_UNIQUE bd06afea
diff --git a/include/llfio/v2.0/afio.hpp b/include/llfio/v2.0/afio.hpp
new file mode 100644
index 00000000..be31688c
--- /dev/null
+++ b/include/llfio/v2.0/afio.hpp
@@ -0,0 +1,86 @@
+//! \file v2.0/afio.hpp The master *versioned* AFIO include file. All version specific AFIO consuming libraries should include this header only.
+
+#undef AFIO_VERSION_MAJOR
+#undef AFIO_VERSION_MINOR
+#undef AFIO_VERSION_PATCH
+// Remove any previously defined versioning
+#undef AFIO_VERSION_REVISION
+#undef AFIO_VERSION_GLUE2
+#undef AFIO_VERSION_GLUE
+#undef AFIO_HEADERS_VERSION
+#undef AFIO_NAMESPACE_VERSION
+#undef AFIO_MODULE_NAME
+
+#define AFIO_VERSION_GLUE2(a, b, c) a##b##c
+#define AFIO_VERSION_GLUE(a, b, c) AFIO_VERSION_GLUE2(a, b, c)
+
+// Hard coded as this is a specific version
+#define AFIO_VERSION_MAJOR 2
+#define AFIO_VERSION_MINOR 0
+#define AFIO_VERSION_PATCH 0
+#define AFIO_VERSION_REVISION 0
+//! \brief The namespace AFIO_V2_NAMESPACE::v ## AFIO_NAMESPACE_VERSION
+#define AFIO_NAMESPACE_VERSION AFIO_VERSION_GLUE(AFIO_VERSION_MAJOR, _, AFIO_VERSION_MINOR)
+
+#if defined(__cpp_modules) || defined(DOXYGEN_SHOULD_SKIP_THIS)
+#if defined(_MSC_VER) && !defined(__clang__)
+//! \brief The AFIO C++ module name
+#define AFIO_MODULE_NAME AFIO_VERSION_GLUE(afio_v, AFIO_NAMESPACE_VERSION, )
+#else
+//! \brief The AFIO C++ module name
+#define AFIO_MODULE_NAME AFIO_VERSION_GLUE(afio_v, AFIO_NAMESPACE_VERSION, )
+#endif
+#endif
+
+// If C++ Modules are on and we are not compiling the library,
+// we are either generating the interface or importing
+#if defined(__cpp_modules)
+#if defined(GENERATING_AFIO_MODULE_INTERFACE)
+// We are generating this module's interface
+#define QUICKCPPLIB_HEADERS_ONLY 0
+#define AFIO_HEADERS_ONLY 0
+#define AFIO_INCLUDE_ALL
+#elif defined(AFIO_SOURCE)
+// We are implementing this module
+#define AFIO_INCLUDE_ALL
+#else
+// We are importing this module
+import AFIO_MODULE_NAME;
+#undef AFIO_INCLUDE_ALL
+#endif
+#else
+// C++ Modules not on, therefore include as usual
+#define AFIO_INCLUDE_ALL
+#endif
+
+#ifdef AFIO_INCLUDE_ALL
+
+#include "config.hpp"
+
+// Predeclare to keep the single header edition happy
+#include "handle.hpp"
+#include "stat.hpp"
+#include "utils.hpp"
+
+#ifndef AFIO_LEAN_AND_MEAN
+#include "async_file_handle.hpp"
+#else
+#include "file_handle.hpp"
+#endif
+#include "directory_handle.hpp"
+#include "map_handle.hpp"
+#include "statfs.hpp"
+#ifndef AFIO_LEAN_AND_MEAN
+#include "storage_profile.hpp"
+#endif
+
+#include "algorithm/cached_parent_handle_adapter.hpp"
+#include "algorithm/mapped_span.hpp"
+#include "algorithm/shared_fs_mutex/atomic_append.hpp"
+#include "algorithm/shared_fs_mutex/byte_ranges.hpp"
+#include "algorithm/shared_fs_mutex/lock_files.hpp"
+#include "algorithm/shared_fs_mutex/memory_map.hpp"
+#include "algorithm/shared_fs_mutex/safe_byte_ranges.hpp"
+#include "algorithm/trivial_vector.hpp"
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/cached_parent_handle_adapter.hpp b/include/llfio/v2.0/algorithm/cached_parent_handle_adapter.hpp
new file mode 100644
index 00000000..ad5a7eb7
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/cached_parent_handle_adapter.hpp
@@ -0,0 +1,215 @@
+/* A parent handle caching adapter
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_CACHED_PARENT_HANDLE_ADAPTER_HPP
+#define AFIO_CACHED_PARENT_HANDLE_ADAPTER_HPP
+
+#include "../directory_handle.hpp"
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+//! \file cached_parent_handle_adapter.hpp Adapts any `fs_handle` to cache its parent directory handle
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace detail
+ {
+ struct AFIO_DECL cached_path_handle : public std::enable_shared_from_this<cached_path_handle>
+ {
+ directory_handle h;
+ filesystem::path _lastpath;
+ explicit cached_path_handle(directory_handle &&_h)
+ : h(std::move(_h))
+ {
+ }
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<filesystem::path> current_path(const filesystem::path &append) noexcept;
+ };
+ using cached_path_handle_ptr = std::shared_ptr<cached_path_handle>;
+ // Passed the base and path of the adapted handle being created, returns a handle to the containing directory and the leafname
+ AFIO_HEADERS_ONLY_FUNC_SPEC std::pair<cached_path_handle_ptr, filesystem::path> get_cached_path_handle(const path_handle &base, path_view path);
+ } // namespace detail
+
+ /*! \brief Adapts any `construct()`-able implementation to cache its parent directory handle in a process wide cache.
+
+ For some use cases where one is calling `parent_path_handle()` or code which calls that function very frequently
+ e.g. calling `relink()` or `unlink()` a lot on many files with the same parent directory, having to constantly
+ fetch the current path, open the parent directory and verify inodes becomes unhelpfully inefficient. This
+ adapter keeps a process-wide hash table of directory handles shared between all instances of this adapter,
+ thus making calling `parent_path_handle()` almost zero cost.
+
+ This adapter is of especial use on platforms which do not reliably implement per-fd path tracking for regular
+ files (Apple MacOS, FreeBSD) as `current_path()` is reimplemented to use the current path of the shared parent
+ directory instead. One loses race freedom within the contained directory, but that is the case on POSIX anyway.
+
+ This adapter is also of use on platforms which do not implement path tracking for open handles at all (e.g.
+ Linux without `/proc` mounted) as the process-wide cache of directory handles retains the path of the directory
+ handle at the time of creation. Third party changes to the part of the filesystem you are working upon will
+ result in the inability to do race free unlinking etc, but if no third party changes are encountered it ought
+ to work well.
+ */
+ template <class T> AFIO_REQUIRES(sizeof(construct<T>) > 0) class AFIO_DECL cached_parent_handle_adapter : public T
+ {
+ static_assert(sizeof(construct<T>) > 0, "Type T must be registered with the construct<T> framework so cached_parent_handle_adapter<T> knows how to construct it"); // NOLINT
+
+ public:
+ //! The handle type being adapted
+ using adapted_handle_type = T;
+ using path_type = typename T::path_type;
+ using path_view_type = typename T::path_view_type;
+
+ protected:
+ detail::cached_path_handle_ptr _sph;
+ filesystem::path _leafname;
+
+ public:
+ cached_parent_handle_adapter() = default;
+ cached_parent_handle_adapter(const cached_parent_handle_adapter &) = default;
+ cached_parent_handle_adapter(cached_parent_handle_adapter &&) = default; // NOLINT
+ cached_parent_handle_adapter &operator=(const cached_parent_handle_adapter &) = default;
+ cached_parent_handle_adapter &operator=(cached_parent_handle_adapter &&) = default; // NOLINT
+ cached_parent_handle_adapter(adapted_handle_type &&o, const path_handle &base, path_view path)
+ : adapted_handle_type(std::move(o))
+ {
+ auto r = detail::get_cached_path_handle(base, path);
+ _sph = std::move(r.first);
+ _leafname = std::move(r.second);
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~cached_parent_handle_adapter() override
+ {
+ if(this->_v)
+ {
+ (void) cached_parent_handle_adapter::close();
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_type> current_path() const noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ return (_sph != nullptr) ? _sph->current_path(_leafname) : path_type();
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ OUTCOME_TRYV(adapted_handle_type::close());
+ _sph.reset();
+ _leafname.clear();
+ return success();
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ _sph.reset();
+ _leafname.clear();
+ return adapted_handle_type::release();
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_handle> parent_path_handle(deadline /* unused */ = std::chrono::seconds(30)) const noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ OUTCOME_TRY(ret, _sph->h.clone_to_path_handle());
+ return {std::move(ret)};
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> relink(const path_handle &base, path_view_type newpath, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ OUTCOME_TRYV(adapted_handle_type::relink(base, newpath, atomic_replace, d));
+ _sph.reset();
+ _leafname.clear();
+ try
+ {
+ auto r = detail::get_cached_path_handle(base, newpath);
+ _sph = std::move(r.first);
+ _leafname = std::move(r.second);
+ return success();
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ OUTCOME_TRYV(adapted_handle_type::unlink(d));
+ _sph.reset();
+ _leafname.clear();
+ return success();
+ }
+ };
+ /*! \brief Constructs a `T` adapted into a parent handle caching implementation.
+
+ This function works via the `construct<T>()` free function framework for which your `handle`
+ implementation must have registered its construction details.
+ */
+ template <class T, class... Args> inline result<cached_parent_handle_adapter<T>> cache_parent(Args &&... args) noexcept
+ {
+ construct<T> constructor{std::forward<Args>(args)...};
+ OUTCOME_TRY(h, constructor());
+ try
+ {
+ return cached_parent_handle_adapter<T>(std::move(h), constructor.base, constructor._path);
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+
+} // namespace algorithm
+
+//! \brief Constructor for `algorithm::::cached_parent_handle_adapter<T>`
+template <class T> struct construct<algorithm::cached_parent_handle_adapter<T>>
+{
+ construct<T> args;
+ result<algorithm::cached_parent_handle_adapter<T>> operator()() const noexcept
+ {
+ OUTCOME_TRY(h, args());
+ try
+ {
+ return algorithm::cached_parent_handle_adapter<T>(std::move(h), args.base, args._path);
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+};
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#include "../detail/impl/cached_parent_handle_adapter.ipp"
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/mapped_span.hpp b/include/llfio/v2.0/algorithm/mapped_span.hpp
new file mode 100644
index 00000000..1ab2c292
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/mapped_span.hpp
@@ -0,0 +1,124 @@
+/* A typed view of a mapped section
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_MAPPED_VIEW_HPP
+#define AFIO_MAPPED_VIEW_HPP
+
+#include "../mapped_file_handle.hpp"
+#include "../utils.hpp"
+
+//! \file mapped_span.hpp Provides typed view of mapped section.
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ /*! \brief Provides a typed mapped view of a `section_handle` suitable for feeding to STL algorithms
+ or the Ranges TS by wrapping a `map_handle` into a `span<T>`.
+
+ Optionally can issue a blocking write barrier on destruction of the mapped view by setting the flag
+ `section_handle::flag::barrier_on_close`, thus forcing any changes to data referred to by the view
+ to storage before the destructor returns.
+ */
+ template <class T> class mapped_span : public span<T>
+ {
+ public:
+ //! The extent type.
+ using extent_type = typename section_handle::extent_type;
+ //! The size type.
+ using size_type = typename section_handle::size_type;
+
+ private:
+ map_handle _mapping;
+ mapped_span(extent_type page_offset, extent_type offset, section_handle &sh, size_type bytes, section_handle::flag _flag) // NOLINT
+ : _mapping(map_handle::map(sh, (bytes == 0) ? 0 : bytes + (offset - page_offset), page_offset, _flag).value())
+ {
+ offset -= page_offset;
+ byte *addr = _mapping.address() + offset;
+ size_t len = sh.length().value() - offset; // use section length, not mapped length as mapped length is rounded up to page size
+ if(bytes != 0 && bytes < len)
+ {
+ len = bytes;
+ }
+ static_cast<span<T> &>(*this) = span<T>(reinterpret_cast<T *>(addr), len / sizeof(T)); // NOLINT
+ }
+
+ public:
+ //! Default constructor
+ constexpr mapped_span() {} // NOLINT
+
+ /*! Create a view of new memory.
+
+ \param length The number of items to map.
+ \param _flag The flags to pass to `map_handle::map()`.
+ */
+ explicit mapped_span(size_type length, section_handle::flag _flag = section_handle::flag::readwrite)
+ : _mapping(map_handle::map(length * sizeof(T), _flag).value())
+ {
+ byte *addr = _mapping.address();
+ static_cast<span<T> &>(*this) = span<T>(reinterpret_cast<T *>(addr), length); // NOLINT
+ }
+ /*! Construct a mapped view of the given section handle.
+
+ \param sh The section handle to use as the data source for creating the map.
+ \param length The number of items to map, use -1 to mean the length of the section handle divided by `sizeof(T)`.
+ \param byteoffset The byte offset into the section handle, this does not need to be a multiple of the page size.
+ \param _flag The flags to pass to `map_handle::map()`.
+ */
+ explicit mapped_span(section_handle &sh, size_type length = (size_type) -1, extent_type byteoffset = 0, section_handle::flag _flag = section_handle::flag::readwrite) // NOLINT
+ : mapped_span((length == 0) ? mapped_span() : mapped_span(
+#ifdef _WIN32
+ byteoffset & ~65535,
+#else
+ utils::round_down_to_page_size(byteoffset),
+#endif
+ byteoffset, sh, (length == (size_type) -1) ? 0 : length * sizeof(T), _flag)) // NOLINT
+ {
+ }
+ /*! Construct a mapped view of the given map handle.
+
+ \param mh The map handle to use.
+ \param length The number of items to map, use -1 to mean the length of the map handle divided by `sizeof(T)`.
+ \param byteoffset The byte offset into the map handle, this does not need to be a multiple of the page size.
+ */
+ explicit mapped_span(map_handle &mh, size_type length = (size_type) -1, extent_type byteoffset = 0) // NOLINT
+ : span<T>(reinterpret_cast<T *>(mh.address() + byteoffset), (length == (size_type) -1) ? ((mh.length() - byteoffset) / sizeof(T)) : length) // NOLINT
+ {
+ }
+ /*! Construct a mapped view of the given mapped file handle.
+
+ \param mfh The mapped file handle to use as the data source for creating the map.
+ \param length The number of items to map, use -1 to mean the length of the section handle divided by `sizeof(T)`.
+ \param byteoffset The byte offset into the mapped file handle, this does not need to be a multiple of the page size.
+ */
+ explicit mapped_span(mapped_file_handle &mfh, size_type length = (size_type) -1, extent_type byteoffset = 0) // NOLINT
+ : span<T>(reinterpret_cast<T *>(mfh.address() + byteoffset), (length == (size_type) -1) ? ((mfh.maximum_extent().value() - byteoffset) / sizeof(T)) : length) // NOLINT
+ {
+ }
+ };
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp
new file mode 100644
index 00000000..5bcec29b
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp
@@ -0,0 +1,557 @@
+/* Efficient many actor read-write lock
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (14 commits)
+File Created: March 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_SHARED_FS_MUTEX_ATOMIC_APPEND_HPP
+#define AFIO_SHARED_FS_MUTEX_ATOMIC_APPEND_HPP
+
+#include "../../file_handle.hpp"
+#include "base.hpp"
+
+#include <cassert>
+#include <thread> // for yield()
+
+//! \file atomic_append.hpp Provides algorithm::shared_fs_mutex::atomic_append
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+#if !DOXYGEN_SHOULD_SKIP_THIS
+ namespace atomic_append_detail
+ {
+#pragma pack(push)
+#pragma pack(1)
+ struct alignas(16) header
+ {
+ uint128 hash; // Hash of remaining 112 bytes
+ uint64 generation{}; // Iterated per write
+ uint64 time_offset{}; // time_t in seconds at time of creation. Used to
+ // offset us_count below.
+ uint64 first_known_good{}; // offset to first known good lock_request
+ uint64 first_after_hole_punch{}; // offset to first byte after last hole
+ // punch
+ // First 48 bytes are the header, remainder is zeros for future
+ // expansion
+ uint64 _padding[10]{};
+ // Last byte is used to detect first user of the file
+ };
+ static_assert(sizeof(header) == 128, "header structure is not 128 bytes long!");
+
+ struct alignas(16) lock_request
+ {
+ uint128 hash; // Hash of remaining 112 bytes
+ uint64 unique_id{}; // A unique id identifying this locking instance
+ uint64 us_count : 56; // Microseconds since the lock file created
+ uint64 items : 8; // The number of entities below which are valid
+ shared_fs_mutex::entity_type entities[12]; // Entities to exclusive or share lock
+ constexpr lock_request()
+ : us_count{}
+ , items{}
+ {
+ }
+ };
+ static_assert(sizeof(lock_request) == 128, "lock_request structure is not 128 bytes long!");
+#pragma pack(pop)
+ } // namespace atomic_append_detail
+#endif
+ /*! \class atomic_append
+ \brief Scalable many entity shared/exclusive file system based lock
+
+ Lock files and byte ranges scale poorly to the number of items being concurrently locked with typically an exponential
+ drop off in performance as the number of items being concurrently locked rises. This file system
+ algorithm solves this problem using IPC via a shared append-only lock file.
+
+ - Compatible with networked file systems (NFS too if the special nfs_compatibility flag is true.
+ Note turning this on is not free of cost if you don't need NFS compatibility).
+ - Nearly constant time to number of entities being locked.
+ - Nearly constant time to number of processes concurrently using the lock (i.e. number of waiters).
+ - Can sleep until a lock becomes free in a power-efficient manner.
+ - Sudden power loss during use is recovered from.
+
+ Caveats:
+ - Much slower than byte_ranges for few waiters or small number of entities.
+ - Sudden process exit with locks held will deadlock all other users.
+ - Maximum of twelve entities may be locked concurrently.
+ - Wasteful of disk space if used on a non-extents based filing system (e.g. FAT32, ext3).
+ It is best used in `/tmp` if possible (`file_handle::temp_file()`). If you really must use a non-extents based filing
+ system, destroy and recreate the object instance periodically to force resetting the lock
+ file's length to zero.
+ - Similarly older operating systems (e.g. Linux < 3.0) do not implement extent hole punching
+ and therefore will also see excessive disk space consumption. Note at the time of writing
+ OS X doesn't implement hole punching at all.
+ - If your OS doesn't have sane byte range locks (OS X, BSD, older Linuxes) and multiple
+ objects in your process use the same lock file, misoperation will occur. Use lock_files instead.
+
+ \todo Implement hole punching once I port that code from AFIO v1.
+ \todo Decide on some resolution mechanism for sudden process exit.
+ \todo There is a 1 out of 2^64-2 chance of unique id collision. It would be nice if we
+ actually formally checked that our chosen unique id is actually unique.
+ */
+ class atomic_append : public shared_fs_mutex
+ {
+ file_handle _h;
+ file_handle::extent_guard _guard; // tags file so other users know they are not alone
+ bool _nfs_compatibility; // Do additional locking to work around NFS's lack of atomic append
+ bool _skip_hashing; // Assume reads never can see torn writes
+ uint64 _unique_id; // My (very random) unique id
+ atomic_append_detail::header _header; // Header as of the last time I read it
+
+ atomic_append(file_handle &&h, file_handle::extent_guard &&guard, bool nfs_compatibility, bool skip_hashing)
+ : _h(std::move(h))
+ , _guard(std::move(guard))
+ , _nfs_compatibility(nfs_compatibility)
+ , _skip_hashing(skip_hashing)
+ , _unique_id(0)
+ {
+ // guard now points at a non-existing handle
+ _guard.set_handle(&_h);
+ utils::random_fill(reinterpret_cast<char *>(&_unique_id), sizeof(_unique_id)); // NOLINT crypto strong random
+ memset(&_header, 0, sizeof(_header));
+ (void) _read_header();
+ }
+
+ result<void> _read_header()
+ {
+ bool first = true;
+ do
+ {
+ OUTCOME_TRY(_, _h.read(0, {{reinterpret_cast<byte *>(&_header), 48}}));
+ if(_[0].data != reinterpret_cast<byte *>(&_header))
+ {
+ memcpy(&_header, _[0].data, _[0].len);
+ }
+ if(_skip_hashing)
+ {
+ return success();
+ }
+ if(first)
+ {
+ first = false;
+ }
+ else
+ {
+ std::this_thread::yield();
+ }
+ // No timeout as this should very rarely block for any significant length of time
+ } while(_header.hash != QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash((reinterpret_cast<char *>(&_header)) + 16, sizeof(_header) - 16));
+ return success();
+ }
+
+ public:
+ //! The type of an entity id
+ using entity_type = shared_fs_mutex::entity_type;
+ //! The type of a sequence of entities
+ using entities_type = shared_fs_mutex::entities_type;
+
+ //! No copy construction
+ atomic_append(const atomic_append &) = delete;
+ //! No copy assignment
+ atomic_append &operator=(const atomic_append &) = delete;
+ ~atomic_append() = default;
+ //! Move constructor
+ atomic_append(atomic_append &&o) noexcept : _h(std::move(o._h)), _guard(std::move(o._guard)), _nfs_compatibility(o._nfs_compatibility), _skip_hashing(o._skip_hashing), _unique_id(o._unique_id), _header(o._header) { _guard.set_handle(&_h); }
+ //! Move assign
+ atomic_append &operator=(atomic_append &&o) noexcept
+ {
+ this->~atomic_append();
+ new(this) atomic_append(std::move(o));
+ return *this;
+ }
+
+ /*! Initialises a shared filing system mutex using the file at \em lockfile
+
+ \return An implementation of shared_fs_mutex using the atomic_append algorithm.
+ \param base Optional base for the path to the file.
+ \param lockfile The path to the file to use for IPC.
+ \param nfs_compatibility Make this true if the lockfile could be accessed by NFS.
+ \param skip_hashing Some filing systems (typically the copy on write ones e.g.
+ ZFS, btrfs) guarantee atomicity of updates and therefore torn writes are never
+ observed by readers. For these, hashing can be safely disabled.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static result<atomic_append> fs_mutex_append(const path_handle &base, path_view lockfile, bool nfs_compatibility = false, bool skip_hashing = false) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(0);
+ OUTCOME_TRY(ret, file_handle::file(base, lockfile, file_handle::mode::write, file_handle::creation::if_needed, file_handle::caching::temporary));
+ atomic_append_detail::header header;
+ // Lock the entire header for exclusive access
+ auto lockresult = ret.try_lock(0, sizeof(header), true);
+ //! \todo fs_mutex_append needs to check if file still exists after lock is granted, awaiting path fetching.
+ if(lockresult.has_error())
+ {
+ if(lockresult.error() != errc::timed_out)
+ {
+ return lockresult.error();
+ }
+ // Somebody else is also using this file
+ }
+ else
+ {
+ // I am the first person to be using this (stale?) file, so write a new header and truncate
+ OUTCOME_TRYV(ret.truncate(sizeof(header)));
+ memset(&header, 0, sizeof(header));
+ header.time_offset = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ header.first_known_good = sizeof(header);
+ header.first_after_hole_punch = sizeof(header);
+ if(!skip_hashing)
+ {
+ header.hash = QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash((reinterpret_cast<char *>(&header)) + 16, sizeof(header) - 16);
+ }
+ OUTCOME_TRYV(ret.write(0, {{reinterpret_cast<byte *>(&header), sizeof(header)}}));
+ }
+ // Open a shared lock on last byte in header to prevent other users zomping the file
+ OUTCOME_TRY(guard, ret.lock(sizeof(header) - 1, 1, false));
+ // Unlock any exclusive lock I gained earlier now
+ if(lockresult)
+ {
+ lockresult.value().unlock();
+ }
+ // The constructor will read and cache the header
+ return atomic_append(std::move(ret), std::move(guard), nfs_compatibility, skip_hashing);
+ }
+
+ //! Return the handle to file being used for this lock
+ const file_handle &handle() const noexcept { return _h; }
+
+ protected:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ atomic_append_detail::lock_request lock_request;
+ if(out.entities.size() > sizeof(lock_request.entities) / sizeof(lock_request.entities[0]))
+ {
+ return errc::argument_list_too_long;
+ }
+
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if((d).steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = (d).to_time_point();
+ }
+ }
+ // Fire this if an error occurs
+ auto disableunlock = undoer([&] { out.release(); });
+
+ // Write my lock request immediately
+ memset(&lock_request, 0, sizeof(lock_request));
+ lock_request.unique_id = _unique_id;
+ auto count = std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(_header.time_offset);
+ lock_request.us_count = std::chrono::duration_cast<std::chrono::microseconds>(count).count();
+ lock_request.items = out.entities.size();
+ memcpy(lock_request.entities, out.entities.data(), sizeof(lock_request.entities[0]) * out.entities.size());
+ if(!_skip_hashing)
+ {
+ lock_request.hash = QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash((reinterpret_cast<char *>(&lock_request)) + 16, sizeof(lock_request) - 16);
+ }
+ // My lock request will be the file's current length or higher
+ OUTCOME_TRY(my_lock_request_offset, _h.maximum_extent());
+ {
+ OUTCOME_TRYV(_h.set_append_only(true));
+ auto undo = undoer([this] { (void) _h.set_append_only(false); });
+ file_handle::extent_guard append_guard;
+ if(_nfs_compatibility)
+ {
+ auto lastbyte = static_cast<file_handle::extent_type>(-1);
+ // Lock up to the beginning of the shadow lock space
+ lastbyte &= ~(1ULL << 63U);
+ OUTCOME_TRY(append_guard_, _h.lock(my_lock_request_offset, lastbyte, true));
+ append_guard = std::move(append_guard_);
+ }
+ OUTCOME_TRYV(_h.write(0, {{reinterpret_cast<byte *>(&lock_request), sizeof(lock_request)}}));
+ }
+
+ // Find the record I just wrote
+ alignas(64) byte _buffer[4096 + 2048]; // 6Kb cache line aligned buffer
+ // Read onwards from length as reported before I wrote my lock request
+ // until I find my lock request. This loop should never actually iterate
+ // except under extreme load conditions.
+ //! \todo Read from header.last_known_good immediately if possible in order
+ //! to avoid a duplicate read later
+ for(;;)
+ {
+ file_handle::io_result<file_handle::buffers_type> readoutcome = _h.read(my_lock_request_offset, {{_buffer, sizeof(_buffer)}});
+ // Should never happen :)
+ if(readoutcome.has_error())
+ {
+ AFIO_LOG_FATAL(this, "atomic_append::lock() saw an error when searching for just written data");
+ std::terminate();
+ }
+ const atomic_append_detail::lock_request *record, *lastrecord;
+ for(record = reinterpret_cast<const atomic_append_detail::lock_request *>(readoutcome.value()[0].data), lastrecord = reinterpret_cast<const atomic_append_detail::lock_request *>(readoutcome.value()[0].data + readoutcome.value()[0].len); record < lastrecord && record->hash != lock_request.hash; ++record)
+ {
+ my_lock_request_offset += sizeof(atomic_append_detail::lock_request);
+ }
+ if(record->hash == lock_request.hash)
+ {
+ break;
+ }
+ }
+
+ // extent_guard is now valid and will be unlocked on error
+ out.hint = my_lock_request_offset;
+ disableunlock.dismiss();
+
+ // Lock my request for writing so others can sleep on me
+ file_handle::extent_guard my_request_guard;
+ if(!spin_not_sleep)
+ {
+ auto lock_offset = my_lock_request_offset;
+ // Set the top bit to use the shadow lock space on Windows
+ lock_offset |= (1ULL << 63U);
+ OUTCOME_TRY(my_request_guard_, _h.lock(lock_offset, sizeof(lock_request), true));
+ my_request_guard = std::move(my_request_guard_);
+ }
+
+ // Read every record preceding mine until header.first_known_good inclusive
+ auto record_offset = my_lock_request_offset - sizeof(atomic_append_detail::lock_request);
+ do
+ {
+ reload:
+ // Refresh the header and load a snapshot of everything between record_offset
+ // and first_known_good or -6Kb, whichever the sooner
+ OUTCOME_TRYV(_read_header());
+ // If there are no preceding records, we're done
+ if(record_offset < _header.first_known_good)
+ {
+ break;
+ }
+ auto start_offset = record_offset;
+ if(start_offset > sizeof(_buffer) - sizeof(atomic_append_detail::lock_request))
+ {
+ start_offset -= sizeof(_buffer) - sizeof(atomic_append_detail::lock_request);
+ }
+ else
+ {
+ start_offset = sizeof(atomic_append_detail::lock_request);
+ }
+ if(start_offset < _header.first_known_good)
+ {
+ start_offset = _header.first_known_good;
+ }
+ assert(record_offset >= start_offset);
+ assert(record_offset - start_offset <= sizeof(_buffer));
+ OUTCOME_TRY(batchread, _h.read(start_offset, {{_buffer, (size_t)(record_offset - start_offset) + sizeof(atomic_append_detail::lock_request)}}));
+ assert(batchread[0].len == record_offset - start_offset + sizeof(atomic_append_detail::lock_request));
+ const atomic_append_detail::lock_request *record = reinterpret_cast<atomic_append_detail::lock_request *>(batchread[0].data + batchread[0].len - sizeof(atomic_append_detail::lock_request));
+ const atomic_append_detail::lock_request *firstrecord = reinterpret_cast<atomic_append_detail::lock_request *>(batchread[0].data);
+
+ // Skip all completed lock requests or not mentioning any of my entities
+ for(; record >= firstrecord; record_offset -= sizeof(atomic_append_detail::lock_request), --record)
+ {
+ // If a completed lock request, skip
+ if(!record->hash && (record->unique_id == 0u))
+ {
+ continue;
+ }
+ // If record hash doesn't match contents it's a torn read, reload
+ if(!_skip_hashing)
+ {
+ if(record->hash != QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash((reinterpret_cast<const char *>(record)) + 16, sizeof(atomic_append_detail::lock_request) - 16))
+ {
+ goto reload;
+ }
+ }
+
+ // Does this record lock anything I am locking?
+ for(const auto &entity : out.entities)
+ {
+ for(size_t n = 0; n < record->items; n++)
+ {
+ if(record->entities[n].value == entity.value)
+ {
+ // Is the lock I want exclusive or the lock he wants exclusive?
+ // If so, need to block
+ if((record->entities[n].exclusive != 0u) || (entity.exclusive != 0u))
+ {
+ goto beginwait;
+ }
+ }
+ }
+ }
+ }
+ // None of this batch of records has anything to do with my request, so keep going
+ continue;
+
+ beginwait:
+ // Sleep until this record is freed using a shared lock
+ // on the record in our way. Note there is a race here
+ // between when the lock requester writes the lock
+ // request and when he takes an exclusive lock on it,
+ // so if our shared lock succeeds we need to immediately
+ // unlock and retry based on the data.
+ std::this_thread::yield();
+ if(!spin_not_sleep)
+ {
+ deadline nd;
+ if(d)
+ {
+ if((d).steady)
+ {
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now());
+ if(ns.count() < 0)
+ {
+ (nd).nsecs = 0;
+ }
+ else
+ {
+ (nd).nsecs = ns.count();
+ }
+ }
+ else
+ {
+ (nd) = (d);
+ }
+ }
+ auto lock_offset = record_offset;
+ // Set the top bit to use the shadow lock space on Windows
+ lock_offset |= (1ULL << 63U);
+ OUTCOME_TRYV(_h.lock(lock_offset, sizeof(*record), false, nd));
+ }
+ // Make sure we haven't timed out during this wait
+ if(d)
+ {
+ if((d).steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ } while(record_offset >= _header.first_known_good);
+ return success();
+ }
+
+ public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(entities_type entities, unsigned long long hint) noexcept final
+ {
+ (void) entities;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(hint == 0u)
+ {
+ AFIO_LOG_WARN(this, "atomic_append::unlock() currently requires a hint to work, assuming this is a failed lock.");
+ return;
+ }
+ auto my_lock_request_offset = static_cast<file_handle::extent_type>(hint);
+ {
+ atomic_append_detail::lock_request record;
+#ifdef _DEBUG
+ (void) _h.read(my_lock_request_offset, {{(byte *) &record, sizeof(record)}});
+ if(!record.unique_id)
+ {
+ AFIO_LOG_FATAL(this, "atomic_append::unlock() I have been previously unlocked!");
+ std::terminate();
+ }
+ (void) _read_header();
+ if(_header.first_known_good > my_lock_request_offset)
+ {
+ AFIO_LOG_FATAL(this, "atomic_append::unlock() header exceeds the lock I am unlocking!");
+ std::terminate();
+ }
+#endif
+ memset(&record, 0, sizeof(record));
+ (void) _h.write(my_lock_request_offset, {{reinterpret_cast<byte *>(&record), sizeof(record)}});
+ }
+
+ // Every 32 records or so, bump _header.first_known_good
+ if((my_lock_request_offset & 4095U) == 0U)
+ {
+ //_read_header();
+
+ // Forward scan records until first non-zero record is found
+ // and update header with new info
+ alignas(64) byte _buffer[4096 + 2048];
+ bool done = false;
+ while(!done)
+ {
+ auto bytesread_ = _h.read(_header.first_known_good, {{_buffer, sizeof(_buffer)}});
+ if(bytesread_.has_error())
+ {
+ // If distance between original first known good and end of file is exactly
+ // 6Kb we can read an EOF
+ break;
+ }
+ const auto &bytesread = bytesread_.value();
+ // If read was partial, we are done after this round
+ if(bytesread[0].len < sizeof(_buffer))
+ {
+ done = true;
+ }
+ const auto *record = reinterpret_cast<const atomic_append_detail::lock_request *>(bytesread[0].data);
+ const auto *lastrecord = reinterpret_cast<const atomic_append_detail::lock_request *>(bytesread[0].data + bytesread[0].len);
+ for(; record < lastrecord; ++record)
+ {
+ if(!record->hash && (record->unique_id == 0u))
+ {
+ _header.first_known_good += sizeof(atomic_append_detail::lock_request);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ // Hole punch if >= 1Mb of zeros exists
+ if(_header.first_known_good - _header.first_after_hole_punch >= 1024U * 1024U)
+ {
+ handle::extent_type holepunchend = _header.first_known_good & ~(1024U * 1024U - 1);
+#ifdef _DEBUG
+ fprintf(stderr, "hole_punch(%llx, %llx)\n", _header.first_after_hole_punch, holepunchend - _header.first_after_hole_punch);
+#endif
+ _header.first_after_hole_punch = holepunchend;
+ }
+ ++_header.generation;
+ if(!_skip_hashing)
+ {
+ _header.hash = QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash((reinterpret_cast<char *>(&_header)) + 16, sizeof(_header) - 16);
+ }
+ // Rewrite the first part of the header only
+ (void) _h.write(0, {{reinterpret_cast<byte *>(&_header), 48}});
+ }
+ }
+ };
+
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/base.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/base.hpp
new file mode 100644
index 00000000..e0c111ef
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/base.hpp
@@ -0,0 +1,244 @@
+/* Protect a shared filing system resource from concurrent modification
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: March 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_SHARED_FS_MUTEX_BASE_HPP
+#define AFIO_SHARED_FS_MUTEX_BASE_HPP
+
+#include "../../handle.hpp"
+
+#ifdef __has_include
+#if __has_include("../../quickcpplib/include/algorithm/hash.hpp")
+#include "../../quickcpplib/include/algorithm/hash.hpp"
+#else
+#include "quickcpplib/include/algorithm/hash.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/algorithm/hash.hpp"
+#else
+#include "../../quickcpplib/include/algorithm/hash.hpp"
+#endif
+
+
+//! \file base.hpp Provides algorithm::shared_fs_mutex::shared_fs_mutex
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ //! Algorithms for protecting a shared filing system resource from racy modification
+ namespace shared_fs_mutex
+ {
+ //! Unsigned 64 bit integer
+ using uint64 = unsigned long long;
+ //! Unsigned 128 bit integer
+ using uint128 = QUICKCPPLIB_NAMESPACE::integers128::uint128;
+
+ /*! \class shared_fs_mutex
+ \brief Abstract base class for an object which protects shared filing system resources
+
+ The implementations of this abstract base class have various pros and cons with varying
+ time and space complexities. See their documentation for details. All share the concept
+ of "entity_type" as being a unique 63 bit identifier of a lockable entity. Various
+ conversion functions are provided below for converting strings, buffers etc. into an
+ entity_type.
+ */
+ class shared_fs_mutex
+ {
+ public:
+ //! The type of an entity id
+ struct entity_type
+ {
+ //! The type backing the value
+ using value_type = handle::extent_type;
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4201) // nameless union used
+#endif
+ union {
+ value_type _init;
+ struct
+ {
+ //! The value of the entity type which can range between 0 and (2^63)-1
+ value_type value : 63;
+ //! True if entity should be locked for exclusive access
+ value_type exclusive : 1;
+ };
+ };
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ //! Default constructor
+ constexpr entity_type() noexcept : _init(0) {} // NOLINT
+//! Constructor
+#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ >= 7
+ constexpr
+#endif
+ entity_type(value_type _value, bool _exclusive) noexcept : _init(0)
+ {
+ value = _value;
+ exclusive = _exclusive; // NOLINT
+ }
+ };
+ static_assert(std::is_literal_type<entity_type>::value, "entity_type is not a literal type");
+ static_assert(sizeof(entity_type) == sizeof(entity_type::value_type), "entity_type is bit equal to its underlying type");
+ //! The type of a sequence of entities
+ using entities_type = span<entity_type>;
+
+ protected:
+ constexpr shared_fs_mutex() {} // NOLINT
+ shared_fs_mutex(const shared_fs_mutex &) = default;
+ shared_fs_mutex(shared_fs_mutex &&) = default;
+ shared_fs_mutex &operator=(const shared_fs_mutex &) = default;
+ shared_fs_mutex &operator=(shared_fs_mutex &&) = default;
+
+ public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~shared_fs_mutex() = default;
+
+ //! Generates an entity id from a sequence of bytes
+ entity_type entity_from_buffer(const char *buffer, size_t bytes, bool exclusive = true) noexcept
+ {
+ uint128 hash = QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash(buffer, bytes);
+ return {hash.as_longlongs[0] ^ hash.as_longlongs[1], exclusive};
+ }
+ //! Generates an entity id from a string
+ template <typename T> entity_type entity_from_string(const std::basic_string<T> &str, bool exclusive = true) noexcept
+ {
+ uint128 hash = QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash(str);
+ return {hash.as_longlongs[0] ^ hash.as_longlongs[1], exclusive};
+ }
+ //! Generates a cryptographically random entity id.
+ entity_type random_entity(bool exclusive = true) noexcept
+ {
+ entity_type::value_type v;
+ utils::random_fill(reinterpret_cast<char *>(&v), sizeof(v));
+ return {v, exclusive};
+ }
+ //! Fills a sequence of entity ids with cryptographic randomness. Much faster than calling random_entity() individually.
+ void fill_random_entities(span<entity_type> seq, bool exclusive = true) noexcept
+ {
+ utils::random_fill(reinterpret_cast<char *>(seq.data()), seq.size() * sizeof(entity_type));
+ for(auto &i : seq)
+ {
+ i.exclusive = exclusive; // NOLINT
+ }
+ }
+
+ //! RAII holder for a lock on a sequence of entities
+ class entities_guard
+ {
+ entity_type _entity;
+
+ public:
+ shared_fs_mutex *parent{nullptr};
+ entities_type entities;
+ unsigned long long hint{0};
+ entities_guard() = default;
+ entities_guard(shared_fs_mutex *_parent, entities_type _entities)
+ : parent(_parent)
+ , entities(_entities)
+ , hint(0)
+ {
+ }
+ entities_guard(shared_fs_mutex *_parent, entity_type entity)
+ : _entity(entity)
+ , parent(_parent)
+ , entities(&_entity, 1)
+ , hint(0)
+ {
+ }
+ entities_guard(const entities_guard &) = delete;
+ entities_guard &operator=(const entities_guard &) = delete;
+ entities_guard(entities_guard &&o) noexcept : _entity(o._entity), parent(o.parent), entities(o.entities), hint(o.hint)
+ {
+ if(entities.data() == &o._entity)
+ {
+ entities = entities_type(&_entity, 1);
+ }
+ o.release();
+ }
+ entities_guard &operator=(entities_guard &&o) noexcept
+ {
+ this->~entities_guard();
+ new(this) entities_guard(std::move(o));
+ return *this;
+ }
+ ~entities_guard()
+ {
+ if(parent != nullptr)
+ {
+ unlock();
+ }
+ }
+ //! True if extent guard is valid
+ explicit operator bool() const noexcept { return parent != nullptr; }
+ //! True if extent guard is invalid
+ bool operator!() const noexcept { return parent == nullptr; }
+ //! Unlocks the locked entities immediately
+ void unlock() noexcept
+ {
+ if(parent != nullptr)
+ {
+ parent->unlock(entities, hint);
+ release();
+ }
+ }
+ //! Detach this RAII unlocker from the locked state
+ void release() noexcept
+ {
+ parent = nullptr;
+ entities = entities_type();
+ }
+ };
+
+ virtual result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept = 0;
+
+ //! Lock all of a sequence of entities for exclusive or shared access
+ result<entities_guard> lock(entities_type entities, deadline d = deadline(), bool spin_not_sleep = false) noexcept
+ {
+ entities_guard ret(this, entities);
+ OUTCOME_TRYV(_lock(ret, d, spin_not_sleep));
+ return std::move(ret);
+ }
+ //! Lock a single entity for exclusive or shared access
+ result<entities_guard> lock(entity_type entity, deadline d = deadline(), bool spin_not_sleep = false) noexcept
+ {
+ entities_guard ret(this, entity);
+ OUTCOME_TRYV(_lock(ret, d, spin_not_sleep));
+ return std::move(ret);
+ }
+ //! Try to lock all of a sequence of entities for exclusive or shared access
+ result<entities_guard> try_lock(entities_type entities) noexcept { return lock(entities, deadline(std::chrono::seconds(0))); }
+ //! Try to lock a single entity for exclusive or shared access
+ result<entities_guard> try_lock(entity_type entity) noexcept { return lock(entity, deadline(std::chrono::seconds(0))); }
+ //! Unlock a previously locked sequence of entities
+ virtual void unlock(entities_type entities, unsigned long long hint = 0) noexcept = 0;
+ };
+
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/byte_ranges.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/byte_ranges.hpp
new file mode 100644
index 00000000..1fe9fb13
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/byte_ranges.hpp
@@ -0,0 +1,253 @@
+/* Efficient small actor read-write lock
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: March 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_SHARED_FS_MUTEX_BYTE_RANGES_HPP
+#define AFIO_SHARED_FS_MUTEX_BYTE_RANGES_HPP
+
+#include "../../file_handle.hpp"
+#include "base.hpp"
+
+#ifdef __has_include
+#if __has_include("../../quickcpplib/include/algorithm/small_prng.hpp")
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#else
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#else
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#endif
+
+//! \file byte_ranges.hpp Provides algorithm::shared_fs_mutex::byte_ranges
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+ /*! \class byte_ranges
+ \brief Many entity shared/exclusive file system based lock
+
+ This is a simple many entity shared mutex. It works by locking in the same file the byte at the
+ offset of the entity id. If it fails to lock a byte, it backs out all preceding locks, randomises the order
+ and tries locking them again until success. Needless to say this algorithm puts a lot of strain on
+ your byte range locking implementation, some NFS implementations have been known to fail to cope.
+
+ \note Most users will want to use `safe_byte_ranges` instead of this class directly.
+
+ - Compatible with networked file systems, though be cautious with older NFS.
+ - Linear complexity to number of concurrent users.
+ - Exponential complexity to number of entities being concurrently locked, though some OSs
+ provide linear complexity so long as total concurrent waiting processes is CPU core count or less.
+ - Does a reasonable job of trying to sleep the thread if any of the entities are locked.
+ - Sudden process exit with lock held is recovered from.
+ - Sudden power loss during use is recovered from.
+ - Safe for multithreaded usage of the same instance.
+
+ Caveats:
+ - When entities being locked is more than one, the algorithm places the contending lock at the
+ front of the list during the randomisation after lock failure so we can sleep the thread until
+ it becomes free. However, under heavy churn the thread will generally spin, consuming 100% CPU.
+ - Byte range locks need to work properly on your system. Misconfiguring NFS or Samba
+ to cause byte range locks to not work right will produce bad outcomes.
+ - If your OS doesn't have sane byte range locks (OS X, BSD, older Linuxes) and multiple
+ objects in your process use the same lock file, misoperation will occur. Use lock_files
+ or share a single instance of this class per lock file in this case.
+ - If you are on POSIX and the same process relocks an entity a second time, it will release everything
+ on the first unlock. On Windows, the first unlock releases the exclusive lock and the second
+ unlock will release the shared lock.
+ */
+ class byte_ranges : public shared_fs_mutex
+ {
+ file_handle _h;
+
+ explicit byte_ranges(file_handle &&h)
+ : _h(std::move(h))
+ {
+ }
+
+ public:
+ //! The type of an entity id
+ using entity_type = shared_fs_mutex::entity_type;
+ //! The type of a sequence of entities
+ using entities_type = shared_fs_mutex::entities_type;
+
+ //! No copy construction
+ byte_ranges(const byte_ranges &) = delete;
+ //! No copy assignment
+ byte_ranges &operator=(const byte_ranges &) = delete;
+ ~byte_ranges() = default;
+ //! Move constructor
+ byte_ranges(byte_ranges &&o) noexcept : _h(std::move(o._h)) {}
+ //! Move assign
+ byte_ranges &operator=(byte_ranges &&o) noexcept
+ {
+ _h = std::move(o._h);
+ return *this;
+ }
+
+ //! Initialises a shared filing system mutex using the file at \em lockfile
+ AFIO_MAKE_FREE_FUNCTION
+ static result<byte_ranges> fs_mutex_byte_ranges(const path_handle &base, path_view lockfile) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(0);
+ OUTCOME_TRY(ret, file_handle::file(base, lockfile, file_handle::mode::write, file_handle::creation::if_needed, file_handle::caching::temporary));
+ return byte_ranges(std::move(ret));
+ }
+
+ //! Return the handle to file being used for this lock
+ const file_handle &handle() const noexcept { return _h; }
+
+ protected:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if((d).steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = (d).to_time_point();
+ }
+ }
+ // Fire this if an error occurs
+ auto disableunlock = undoer([&] { out.release(); });
+ size_t n;
+ for(;;)
+ {
+ auto was_contended = static_cast<size_t>(-1);
+ {
+ auto undo = undoer([&] {
+ // 0 to (n-1) need to be closed
+ if(n > 0)
+ {
+ --n;
+ // Now 0 to n needs to be closed
+ for(; n > 0; n--)
+ {
+ _h.unlock(out.entities[n].value, 1);
+ }
+ _h.unlock(out.entities[0].value, 1);
+ }
+ });
+ for(n = 0; n < out.entities.size(); n++)
+ {
+ deadline nd;
+ // Only for very first entity will we sleep until its lock becomes available
+ if(n != 0u)
+ {
+ nd = deadline(std::chrono::seconds(0));
+ }
+ else
+ {
+ nd = deadline();
+ if(d)
+ {
+ if((d).steady)
+ {
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now());
+ if(ns.count() < 0)
+ {
+ (nd).nsecs = 0;
+ }
+ else
+ {
+ (nd).nsecs = ns.count();
+ }
+ }
+ else
+ {
+ (nd) = (d);
+ }
+ }
+ }
+ auto outcome = _h.lock(out.entities[n].value, 1, out.entities[n].exclusive != 0u, nd);
+ if(!outcome)
+ {
+ was_contended = n;
+ goto failed;
+ }
+ outcome.value().release();
+ }
+ // Everything is locked, exit
+ undo.dismiss();
+ disableunlock.dismiss();
+ return success();
+ }
+ failed:
+ if(d)
+ {
+ if((d).steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ // Move was_contended to front and randomise rest of out.entities
+ std::swap(out.entities[was_contended], out.entities[0]);
+ auto front = out.entities.begin();
+ ++front;
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::random_shuffle(front, out.entities.end());
+ if(!spin_not_sleep)
+ {
+ std::this_thread::yield();
+ }
+ }
+ // return success();
+ }
+
+ public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(entities_type entities, unsigned long long /*hint*/) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ for(const auto &i : entities)
+ {
+ _h.unlock(i.value, 1);
+ }
+ }
+ };
+
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/lock_files.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/lock_files.hpp
new file mode 100644
index 00000000..33b3b31d
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/lock_files.hpp
@@ -0,0 +1,241 @@
+/* Compatibility read-write lock
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: April 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_SHARED_FS_MUTEX_LOCK_FILES_HPP
+#define AFIO_SHARED_FS_MUTEX_LOCK_FILES_HPP
+
+#include "../../file_handle.hpp"
+#include "base.hpp"
+
+#ifdef __has_include
+#if __has_include("../../quickcpplib/include/algorithm/small_prng.hpp")
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#else
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#else
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#endif
+
+
+//! \file lock_files.hpp Provides algorithm::shared_fs_mutex::lock_files
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+ /*! \class lock_files
+ \brief Many entity exclusive compatibility file system based lock
+
+ This is a very simple many entity shared mutex likely to work almost anywhere without surprises.
+ It works by trying to exclusively create a file called the hex of the entity id. If it fails to
+ exclusively create any file, it deletes all previously created files, randomises the order
+ and tries locking them again until success. The only real reason to use this implementation
+ is its excellent compatibility with almost everything, most users will want byte_ranges instead.
+
+ - Compatible with all networked file systems.
+ - Linear complexity to number of concurrent users.
+ - Exponential complexity to number of contended entities being concurrently locked.
+ - Requests for shared locks are treated as if for exclusive locks.
+
+ Caveats:
+ - No ability to sleep until a lock becomes free, so CPUs are spun at 100%.
+ - On POSIX only sudden process exit with locks held will deadlock all other users by leaving stale
+ files around.
+ - Costs a file descriptor per entity locked.
+ - Sudden power loss during use will deadlock first user after reboot, again due to stale files.
+ - Currently this implementation does not permit more than one lock() per instance as the lock
+ information is stored as member data. Creating multiple instances referring to the same path
+ works fine. This could be fixed easily, but it would require a memory allocation per lock and
+ user demand that this is actually a problem in practice.
+ - Leaves many 16 character long hexadecimal named files in the supplied directory which may
+ confuse users. Tip: create a hidden lockfile directory.
+
+ Fixing the stale lock file problem could be quite trivial - simply byte range lock the first byte
+ in the lock file to detect when a lock file is stale. However in this situation using the
+ byte_ranges algorithm would be far superior, so implementing stale lock file clean up is left up
+ to the user.
+ */
+ class lock_files : public shared_fs_mutex
+ {
+ const path_handle &_path;
+ std::vector<file_handle> _hs;
+
+ explicit lock_files(const path_handle &o)
+ : _path(o)
+ {
+ }
+
+ public:
+ //! The type of an entity id
+ using entity_type = shared_fs_mutex::entity_type;
+ //! The type of a sequence of entities
+ using entities_type = shared_fs_mutex::entities_type;
+
+ //! No copy construction
+ lock_files(const lock_files &) = delete;
+ //! No copy assignment
+ lock_files &operator=(const lock_files &) = delete;
+ ~lock_files() = default;
+ //! Move constructor
+ lock_files(lock_files &&o) noexcept : _path(o._path), _hs(std::move(o._hs)) {}
+ //! Move assign
+ lock_files &operator=(lock_files &&o) noexcept
+ {
+ this->~lock_files();
+ new(this) lock_files(std::move(o));
+ return *this;
+ }
+
+ //! Initialises a shared filing system mutex using the directory at \em lockdir which MUST stay valid for the duration of this lock.
+ AFIO_MAKE_FREE_FUNCTION
+ static result<lock_files> fs_mutex_lock_files(const path_handle &lockdir) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(0);
+ return lock_files(lockdir);
+ }
+
+ //! Return the path to the directory being used for this lock
+ const path_handle &path() const noexcept { return _path; }
+
+ protected:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if((d).steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = (d).to_time_point();
+ }
+ }
+ size_t n;
+ // Create a set of paths to files to exclusively create
+ std::vector<std::string> entity_paths(out.entities.size());
+ for(n = 0; n < out.entities.size(); n++)
+ {
+ auto v = out.entities[n].value;
+ entity_paths[n] = QUICKCPPLIB_NAMESPACE::algorithm::string::to_hex_string(span<char>(reinterpret_cast<char *>(&v), 8));
+ }
+ _hs.resize(out.entities.size());
+ do
+ {
+ auto was_contended = static_cast<size_t>(-1);
+ {
+ auto undo = undoer([&] {
+ // 0 to (n-1) need to be closed
+ if(n > 0)
+ {
+ --n;
+ // Now 0 to n needs to be closed
+ for(; n > 0; n--)
+ {
+ (void) _hs[n].close(); // delete on close semantics deletes the file
+ }
+ (void) _hs[0].close();
+ }
+ });
+ for(n = 0; n < out.entities.size(); n++)
+ {
+ auto ret = file_handle::file(_path, entity_paths[n], file_handle::mode::write, file_handle::creation::only_if_not_exist, file_handle::caching::temporary, file_handle::flag::unlink_on_first_close);
+ if(ret.has_error())
+ {
+ const auto &ec = ret.error();
+ if(ec != errc::resource_unavailable_try_again && ec != errc::file_exists)
+ {
+ return ret.error();
+ }
+ // Collided with another locker
+ was_contended = n;
+ break;
+ }
+ _hs[n] = std::move(ret.value());
+ }
+ if(n == out.entities.size())
+ {
+ undo.dismiss();
+ }
+ }
+ if(n != out.entities.size())
+ {
+ if(d)
+ {
+ if((d).steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ // Move was_contended to front and randomise rest of out.entities
+ std::swap(out.entities[was_contended], out.entities[0]);
+ auto front = out.entities.begin();
+ ++front;
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::random_shuffle(front, out.entities.end());
+ // Sleep for a very short time
+ if(!spin_not_sleep)
+ {
+ std::this_thread::yield();
+ }
+ }
+ } while(n < out.entities.size());
+ return success();
+ }
+
+ public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(entities_type /*entities*/, unsigned long long /*hint*/) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ for(auto &i : _hs)
+ {
+ (void) i.close(); // delete on close semantics deletes the file
+ }
+ }
+ };
+
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp
new file mode 100644
index 00000000..b71de0b2
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/memory_map.hpp
@@ -0,0 +1,428 @@
+/* Efficient large actor read-write lock
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (23 commits)
+File Created: Aug 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_SHARED_FS_MUTEX_MEMORY_MAP_HPP
+#define AFIO_SHARED_FS_MUTEX_MEMORY_MAP_HPP
+
+#include "../../map_handle.hpp"
+#include "base.hpp"
+
+#ifdef __has_include
+#if __has_include("../../quickcpplib/include/algorithm/hash.hpp")
+#include "../../quickcpplib/include/algorithm/hash.hpp"
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#include "../../quickcpplib/include/spinlock.hpp"
+#else
+#include "quickcpplib/include/algorithm/hash.hpp"
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#include "quickcpplib/include/spinlock.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/algorithm/hash.hpp"
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#include "quickcpplib/include/spinlock.hpp"
+#else
+#include "../../quickcpplib/include/algorithm/hash.hpp"
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#include "../../quickcpplib/include/spinlock.hpp"
+#endif
+
+
+//! \file memory_map.hpp Provides algorithm::shared_fs_mutex::memory_map
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+ /*! \class memory_map
+ \brief Many entity memory mapped shared/exclusive file system based lock
+ \tparam Hasher A STL compatible hash algorithm to use (defaults to `fnv1a_hash`)
+ \tparam HashIndexSize The size in bytes of the hash index to use (defaults to 4Kb)
+ \tparam SpinlockType The type of spinlock to use (defaults to a `SharedMutex` concept spinlock)
+
+ This is the highest performing filing system mutex in AFIO, but it comes with a long list of potential
+ gotchas. It works by creating a random temporary file somewhere on the system and placing its path
+ in a file at the lock file location. The random temporary file is mapped into memory by all processes
+ using the lock where an open addressed hash table is kept. Each entity is hashed into somewhere in the
+ hash table and its individual spin lock is used to implement the exclusion. As with `byte_ranges`, each
+ entity is locked individually in sequence but if a particular lock fails, all are unlocked and the
+ list is randomised before trying again. Because this locking
+ implementation is entirely implemented in userspace using shared memory without any kernel syscalls,
+ performance is probably as fast as any many-arbitrary-entity shared locking system could be.
+
+ As it uses shared memory, this implementation of `shared_fs_mutex` cannot work over a networked
+ drive. If you attempt to open this lock on a network drive and the first user of the lock is not
+ on this local machine, `errc::no_lock_available` will be returned from the constructor.
+
+ - Linear complexity to number of concurrent users up until hash table starts to get full or hashed
+ entries collide.
+ - Sudden power loss during use is recovered from.
+ - Safe for multithreaded usage of the same instance.
+ - In the lightly contended case, an order of magnitude faster than any other `shared_fs_mutex` algorithm.
+
+ Caveats:
+ - No ability to sleep until a lock becomes free, so CPUs are spun at 100%.
+ - Sudden process exit with locks held will deadlock all other users.
+ - Exponential complexity to number of entities being concurrently locked.
+ - Exponential complexity to concurrency if entities hash to the same cache line. Most SMP and especially
+ NUMA systems have a finite bandwidth for atomic compare and swap operations, and every attempt to
+ lock or unlock an entity under this implementation is several of those operations. Under heavy contention,
+ whole system performance very noticeably nose dives from excessive atomic operations, things like audio and the
+ mouse pointer will stutter.
+ - Sometimes different entities hash to the same offset and collide with one another, causing very poor performance.
+ - Memory mapped files need to be cache unified with normal i/o in your OS kernel. Known OSs which
+ don't use a unified cache for memory mapped and normal i/o are QNX, OpenBSD. Furthermore, doing
+ normal i/o and memory mapped i/o to the same file needs to not corrupt the file. In the past,
+ there have been editions of the Linux kernel and the OS X kernel which did this.
+ - If your OS doesn't have sane byte range locks (OS X, BSD, older Linuxes) and multiple
+ objects in your process use the same lock file, misoperation will occur.
+ - Requires `handle::current_path()` to be working.
+
+ \todo memory_map::_hash_entities needs to hash x16, x8 and x4 at a time to encourage auto vectorisation
+ */
+ template <template <class> class Hasher = QUICKCPPLIB_NAMESPACE::algorithm::hash::fnv1a_hash, size_t HashIndexSize = 4096, class SpinlockType = QUICKCPPLIB_NAMESPACE::configurable_spinlock::shared_spinlock<>> class memory_map : public shared_fs_mutex
+ {
+ public:
+ //! The type of an entity id
+ using entity_type = shared_fs_mutex::entity_type;
+ //! The type of a sequence of entities
+ using entities_type = shared_fs_mutex::entities_type;
+ //! The type of the hasher being used
+ using hasher_type = Hasher<entity_type::value_type>;
+ //! The type of the spinlock being used
+ using spinlock_type = SpinlockType;
+
+ private:
+ static constexpr size_t _container_entries = HashIndexSize / sizeof(spinlock_type);
+ using _hash_index_type = std::array<spinlock_type, _container_entries>;
+ static constexpr file_handle::extent_type _initialisingoffset = static_cast<file_handle::extent_type>(1024) * 1024;
+ static constexpr file_handle::extent_type _lockinuseoffset = static_cast<file_handle::extent_type>(1024) * 1024 + 1;
+
+ file_handle _h, _temph;
+ file_handle::extent_guard _hlockinuse; // shared lock of last byte of _h marking if lock is in use
+ map_handle _hmap, _temphmap;
+
+ _hash_index_type &_index() const
+ {
+ auto *ret = reinterpret_cast<_hash_index_type *>(_temphmap.address());
+ return *ret;
+ }
+
+ memory_map(file_handle &&h, file_handle &&temph, file_handle::extent_guard &&hlockinuse, map_handle &&hmap, map_handle &&temphmap)
+ : _h(std::move(h))
+ , _temph(std::move(temph))
+ , _hlockinuse(std::move(hlockinuse))
+ , _hmap(std::move(hmap))
+ , _temphmap(std::move(temphmap))
+ {
+ _hlockinuse.set_handle(&_h);
+ }
+
+ public:
+ //! No copy construction
+ memory_map(const memory_map &) = delete;
+ //! No copy assignment
+ memory_map &operator=(const memory_map &) = delete;
+ //! Move constructor
+ memory_map(memory_map &&o) noexcept : _h(std::move(o._h)), _temph(std::move(o._temph)), _hlockinuse(std::move(o._hlockinuse)), _hmap(std::move(o._hmap)), _temphmap(std::move(o._temphmap)) { _hlockinuse.set_handle(&_h); }
+ //! Move assign
+ memory_map &operator=(memory_map &&o) noexcept
+ {
+ this->~memory_map();
+ new(this) memory_map(std::move(o));
+ return *this;
+ }
+ ~memory_map() override
+ {
+ if(_h.is_valid())
+ {
+ // Release the maps
+ _hmap = {};
+ _temphmap = {};
+ // Release my shared locks and try locking inuse exclusively
+ _hlockinuse.unlock();
+ auto lockresult = _h.try_lock(_initialisingoffset, 2, true);
+#ifndef NDEBUG
+ if(!lockresult && lockresult.error() != errc::timed_out)
+ {
+ AFIO_LOG_FATAL(0, "memory_map::~memory_map() try_lock failed");
+ abort();
+ }
+#endif
+ if(lockresult)
+ {
+ // This means I am the last user, so zop the file contents as temp file is about to go away
+ auto o2 = _h.truncate(0);
+ if(!o2)
+ {
+ AFIO_LOG_FATAL(0, "memory_map::~memory_map() truncate failed");
+#ifndef NDEBUG
+ std::cerr << "~memory_map() truncate failed due to " << o2.error().message().c_str() << std::endl;
+#endif
+ abort();
+ }
+ // Unlink the temp file. We don't trap any failure to unlink on FreeBSD it can forget current path.
+ auto o3 = _temph.unlink();
+ if(!o3)
+ {
+#ifdef __FreeBSD__
+#ifndef NDEBUG
+ std::cerr << "~memory_map() unlink failed due to " << o3.error().message().c_str() << std::endl;
+#endif
+#else
+ AFIO_LOG_FATAL(0, "memory_map::~memory_map() unlink failed");
+#ifndef NDEBUG
+ std::cerr << "~memory_map() unlink failed due to " << o3.error().message().c_str() << std::endl;
+#endif
+ abort();
+#endif
+ }
+ }
+ }
+ }
+
+ /*! Initialises a shared filing system mutex using the file at \em lockfile.
+ \errors Awaiting the clang result<> AST parser which auto generates all the error codes which could occur,
+ but a particularly important one is `errc::no_lock_available` which will be returned if the lock
+ is in use by another computer on a network.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static result<memory_map> fs_mutex_map(const path_handle &base, path_view lockfile) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(0);
+ try
+ {
+ OUTCOME_TRY(ret, file_handle::file(base, lockfile, file_handle::mode::write, file_handle::creation::if_needed, file_handle::caching::reads));
+ file_handle temph;
+ // Am I the first person to this file? Lock everything exclusively
+ auto lockinuse = ret.try_lock(_initialisingoffset, 2, true);
+ if(lockinuse.has_error())
+ {
+ if(lockinuse.error() != errc::timed_out)
+ {
+ return lockinuse.error();
+ }
+ // Somebody else is also using this file, so try to read the hash index file I ought to use
+ lockinuse = ret.lock(_lockinuseoffset, 1, false); // inuse shared access, blocking
+ if(!lockinuse)
+ {
+ return lockinuse.error();
+ }
+ byte buffer[65536];
+ memset(buffer, 0, sizeof(buffer));
+ OUTCOME_TRYV(ret.read(0, {{buffer, 65535}}));
+ path_view temphpath(reinterpret_cast<filesystem::path::value_type *>(buffer));
+ result<file_handle> _temph(in_place_type<file_handle>);
+ _temph = file_handle::file({}, temphpath, file_handle::mode::write, file_handle::creation::open_existing, file_handle::caching::temporary);
+ // If temp file doesn't exist, I am on a different machine
+ if(!_temph)
+ {
+ // Release the exclusive lock and tell caller that this lock is not available
+ return errc::no_lock_available;
+ }
+ temph = std::move(_temph.value());
+ // Map the hash index file into memory for read/write access
+ OUTCOME_TRY(temphsection, section_handle::section(temph, HashIndexSize));
+ OUTCOME_TRY(temphmap, map_handle::map(temphsection, HashIndexSize));
+ // Map the path file into memory with its maximum possible size, read only
+ OUTCOME_TRY(hsection, section_handle::section(ret, 65536, section_handle::flag::read));
+ OUTCOME_TRY(hmap, map_handle::map(hsection, 0, 0, section_handle::flag::read));
+ return memory_map(std::move(ret), std::move(temph), std::move(lockinuse.value()), std::move(hmap), std::move(temphmap));
+ }
+
+ // I am the first person to be using this (stale?) file, so create a new hash index file in /tmp
+ auto &tempdirh = path_discovery::memory_backed_temporary_files_directory().is_valid() ? path_discovery::memory_backed_temporary_files_directory() : path_discovery::storage_backed_temporary_files_directory();
+ OUTCOME_TRY(_temph, file_handle::random_file(tempdirh));
+ temph = std::move(_temph);
+ // Truncate it out to the hash index size, and map it into memory for read/write access
+ OUTCOME_TRYV(temph.truncate(HashIndexSize));
+ OUTCOME_TRY(temphsection, section_handle::section(temph, HashIndexSize));
+ OUTCOME_TRY(temphmap, map_handle::map(temphsection, HashIndexSize));
+ // Write the path of my new hash index file, padding zeros to the nearest page size
+ // multiple to work around a race condition in the Linux kernel
+ OUTCOME_TRY(temppath, temph.current_path());
+ char buffer[4096];
+ memset(buffer, 0, sizeof(buffer));
+ size_t bytes = temppath.native().size() * sizeof(*temppath.c_str());
+ file_handle::const_buffer_type buffers[] = {{reinterpret_cast<const byte *>(temppath.c_str()), bytes}, {reinterpret_cast<const byte *>(buffer), 4096 - (bytes % 4096)}};
+ OUTCOME_TRYV(ret.truncate(65536));
+ OUTCOME_TRYV(ret.write({buffers, 0}));
+ // Map for read the maximum possible path file size, again to avoid race problems
+ OUTCOME_TRY(hsection, section_handle::section(ret, 65536, section_handle::flag::read));
+ OUTCOME_TRY(hmap, map_handle::map(hsection, 0, 0, section_handle::flag::read));
+ /* Take shared locks on inuse. Even if this implementation doesn't implement
+ atomic downgrade of exclusive range to shared range, we're fully prepared for other users
+ now. The _initialisingoffset remains exclusive to prevent double entry into this init routine.
+ */
+ OUTCOME_TRY(lockinuse2, ret.lock(_lockinuseoffset, 1, false));
+ lockinuse = std::move(lockinuse2); // releases exclusive lock on all three offsets
+ return memory_map(std::move(ret), std::move(temph), std::move(lockinuse.value()), std::move(hmap), std::move(temphmap));
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+
+ //! Return the handle to file being used for this lock
+ const file_handle &handle() const noexcept { return _h; }
+
+ protected:
+ struct _entity_idx
+ {
+ unsigned value : 31;
+ unsigned exclusive : 1;
+ };
+ // Create a cache of entities to their indices, eliding collisions where necessary
+ static span<_entity_idx> _hash_entities(_entity_idx *entity_to_idx, entities_type &entities)
+ {
+ _entity_idx *ep = entity_to_idx;
+ for(size_t n = 0; n < entities.size(); n++)
+ {
+ ep->value = hasher_type()(entities[n].value) % _container_entries;
+ ep->exclusive = entities[n].exclusive;
+ bool skip = false;
+ for(size_t m = 0; m < n; m++)
+ {
+ if(entity_to_idx[m].value == ep->value)
+ {
+ if(ep->exclusive && !entity_to_idx[m].exclusive)
+ {
+ entity_to_idx[m].exclusive = true;
+ }
+ skip = true;
+ }
+ }
+ if(!skip)
+ {
+ ++ep;
+ }
+ }
+ return span<_entity_idx>(entity_to_idx, ep - entity_to_idx);
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if((d).steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = (d).to_time_point();
+ }
+ }
+ // alloca() always returns 16 byte aligned addresses
+ span<_entity_idx> entity_to_idx(_hash_entities(reinterpret_cast<_entity_idx *>(alloca(sizeof(_entity_idx) * out.entities.size())), out.entities));
+ _hash_index_type &index = _index();
+ // Fire this if an error occurs
+ auto disableunlock = undoer([&] { out.release(); });
+ size_t n;
+ for(;;)
+ {
+ auto was_contended = static_cast<size_t>(-1);
+ {
+ auto undo = undoer([&] {
+ // 0 to (n-1) need to be closed
+ if(n > 0)
+ {
+ --n;
+ // Now 0 to n needs to be closed
+ for(; n > 0; n--)
+ {
+ entity_to_idx[n].exclusive ? index[entity_to_idx[n].value].unlock() : index[entity_to_idx[n].value].unlock_shared();
+ }
+ entity_to_idx[0].exclusive ? index[entity_to_idx[0].value].unlock() : index[entity_to_idx[0].value].unlock_shared();
+ }
+ });
+ for(n = 0; n < entity_to_idx.size(); n++)
+ {
+ if(!(entity_to_idx[n].exclusive ? index[entity_to_idx[n].value].try_lock() : index[entity_to_idx[n].value].try_lock_shared()))
+ {
+ was_contended = n;
+ goto failed;
+ }
+ }
+ // Everything is locked, exit
+ undo.dismiss();
+ disableunlock.dismiss();
+ return success();
+ }
+ failed:
+ if(d)
+ {
+ if((d).steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ // Move was_contended to front and randomise rest of out.entities
+ std::swap(entity_to_idx[was_contended], entity_to_idx[0]);
+ auto front = entity_to_idx.begin();
+ ++front;
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::random_shuffle(front, entity_to_idx.end());
+ if(!spin_not_sleep)
+ {
+ std::this_thread::yield();
+ }
+ }
+ // return success();
+ }
+
+ public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(entities_type entities, unsigned long long /*unused*/) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ span<_entity_idx> entity_to_idx(_hash_entities(reinterpret_cast<_entity_idx *>(alloca(sizeof(_entity_idx) * entities.size())), entities));
+ _hash_index_type &index = _index();
+ for(const auto &i : entity_to_idx)
+ {
+ i.exclusive ? index[i.value].unlock() : index[i.value].unlock_shared();
+ }
+ }
+ };
+
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/shared_fs_mutex/safe_byte_ranges.hpp b/include/llfio/v2.0/algorithm/shared_fs_mutex/safe_byte_ranges.hpp
new file mode 100644
index 00000000..b388dd70
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/shared_fs_mutex/safe_byte_ranges.hpp
@@ -0,0 +1,179 @@
+/* Safe small actor read-write lock
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_SHARED_FS_MUTEX_SAFE_BYTE_RANGES_HPP
+#define AFIO_SHARED_FS_MUTEX_SAFE_BYTE_RANGES_HPP
+
+#if defined(_WIN32)
+#include "byte_ranges.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+ class safe_byte_ranges : public byte_ranges
+ {
+ public:
+ using byte_ranges::byte_ranges;
+ explicit safe_byte_ranges(byte_ranges &&o)
+ : byte_ranges(std::move(o))
+ {
+ }
+ //! Initialises a shared filing system mutex using the file at \em lockfile
+ AFIO_MAKE_FREE_FUNCTION
+ static result<safe_byte_ranges> fs_mutex_safe_byte_ranges(const path_handle &base, path_view lockfile) noexcept
+ {
+ OUTCOME_TRY(v, byte_ranges::fs_mutex_byte_ranges(base, lockfile));
+ return safe_byte_ranges(std::move(v));
+ }
+ };
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+#else
+
+#include "base.hpp"
+
+#include <memory>
+
+//! \file safe_byte_ranges.hpp Provides algorithm::shared_fs_mutex::safe_byte_ranges
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+ namespace detail
+ {
+ AFIO_HEADERS_ONLY_FUNC_SPEC result<std::shared_ptr<shared_fs_mutex>> inode_to_fs_mutex(const path_handle &base, path_view lockfile) noexcept;
+ }
+
+ /*! \class safe_byte_ranges
+ \brief Safe many entity shared/exclusive file system based lock
+
+ \sa byte_ranges
+
+ POSIX requires that byte range locks have insane semantics, namely that:
+
+ - Any fd closed on an inode must release all byte range locks on that inode for all
+ other fds.
+ - Threads replace each other's locks.
+
+ This makes the use of byte range locks in modern code highly problematic.
+
+ This implementation of `shared_fs_mutex` exclusively uses byte range locks on an inode
+ for exclusion with other processes, but on POSIX only also layers on top:
+
+ - Keeps a process-wide store of reference counted fd's per lock in the process, thus
+ preventing byte range locks ever being dropped unexpectedly.
+ - A process-local thread aware layer, allowing byte range locks to be held by a thread
+ and excluding other threads as well as processes.
+ - A thread may add an additional exclusive or shared lock on top of its existing
+ exclusive or shared lock, but unlocks always unlock the exclusive lock first. This
+ choice of behaviour is to match Microsoft Windows' behaviour to aid writing portable
+ code.
+
+ On Microsoft Windows `safe_byte_ranges` is typedefed to `byte_ranges`, as the Windows
+ byte range locking API already works as described above.
+
+ Benefits:
+
+ - Compatible with networked file systems, though be cautious with older NFS.
+ - Linear complexity to number of concurrent users.
+ - Exponential complexity to number of entities being concurrently locked.
+ - Does a reasonable job of trying to sleep the thread if any of the entities are locked.
+ - Sudden process exit with lock held is recovered from.
+ - Sudden power loss during use is recovered from.
+ - Safe for multithreaded usage.
+
+ Caveats:
+ - When entities being locked is more than one, the algorithm places the contending lock at the
+ front of the list during the randomisation after lock failure so we can sleep the thread until
+ it becomes free. However, under heavy churn the thread will generally spin, consuming 100% CPU.
+ - Byte range locks need to work properly on your system. Misconfiguring NFS or Samba
+ to cause byte range locks to not work right will produce bad outcomes.
+ - Unavoidably these locks will be a good bit slower than `byte_ranges`.
+ */
+ class safe_byte_ranges : public shared_fs_mutex
+ {
+ std::shared_ptr<shared_fs_mutex> _p;
+
+ explicit safe_byte_ranges(std::shared_ptr<shared_fs_mutex> p)
+ : _p(std::move(p))
+ {
+ }
+
+ public:
+ //! The type of an entity id
+ using entity_type = shared_fs_mutex::entity_type;
+ //! The type of a sequence of entities
+ using entities_type = shared_fs_mutex::entities_type;
+
+ //! No copy construction
+ safe_byte_ranges(const safe_byte_ranges &) = delete;
+ //! No copy assignment
+ safe_byte_ranges &operator=(const safe_byte_ranges &) = delete;
+ ~safe_byte_ranges() = default;
+ //! Move constructor
+ safe_byte_ranges(safe_byte_ranges &&o) noexcept : _p(std::move(o._p)) {}
+ //! Move assign
+ safe_byte_ranges &operator=(safe_byte_ranges &&o) noexcept
+ {
+ _p = std::move(o._p);
+ return *this;
+ }
+
+ //! Initialises a shared filing system mutex using the file at \em lockfile
+ AFIO_MAKE_FREE_FUNCTION
+ static result<safe_byte_ranges> fs_mutex_safe_byte_ranges(const path_handle &base, path_view lockfile) noexcept
+ {
+ OUTCOME_TRY(ret, detail::inode_to_fs_mutex(base, lockfile));
+ return safe_byte_ranges(std::move(ret));
+ }
+
+ protected:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept final { return _p->_lock(out, d, spin_not_sleep); }
+
+ public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(entities_type entities, unsigned long long hint) noexcept final { return _p->unlock(entities, hint); }
+ };
+
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#include "../../detail/impl/safe_byte_ranges.ipp"
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/algorithm/trivial_vector.hpp b/include/llfio/v2.0/algorithm/trivial_vector.hpp
new file mode 100644
index 00000000..0089092b
--- /dev/null
+++ b/include/llfio/v2.0/algorithm/trivial_vector.hpp
@@ -0,0 +1,713 @@
+/* A STL vector using a section_handle for storage
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: Nov 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_ALGORITHM_VECTOR_HPP
+#define AFIO_ALGORITHM_VECTOR_HPP
+
+#include "../map_handle.hpp"
+#include "../utils.hpp"
+
+
+//! \file trivial_vector.hpp Provides constant time reallocating STL vector.
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+#ifndef DOXYGEN_IS_IN_THE_HOUSE
+ namespace detail
+#else
+ //! Does not exist in the actual source code, purely here to workaround doxygen limitations
+ namespace impl
+#endif
+ {
+ template <bool has_default_construction, class T> struct trivial_vector_impl;
+ template <class T> class trivial_vector_iterator
+ {
+ template <bool has_default_construction, class _T> friend struct trivial_vector_impl;
+ T *_v;
+ explicit trivial_vector_iterator(T *v)
+ : _v(v)
+ {
+ }
+
+ public:
+ //! Value type
+ using value_type = T;
+ //! Pointer type
+ using pointer = value_type *;
+ //! Const pointer type
+ using const_pointer = typename std::pointer_traits<pointer>::template rebind<const value_type>;
+ //! Difference type
+ using difference_type = typename std::pointer_traits<pointer>::difference_type;
+ //! Size type
+ using size_type = typename std::make_unsigned<difference_type>::type;
+ //! Reference type
+ using reference = value_type &;
+ //! Const reference type
+ using const_reference = const value_type &;
+
+ //! Increment the iterator
+ trivial_vector_iterator &operator+=(size_type n)
+ {
+ _v += n;
+ return *this;
+ }
+ //! Decrement the iterator
+ trivial_vector_iterator &operator-=(size_type n)
+ {
+ _v -= n;
+ return *this;
+ }
+ //! Difference between iterators
+ difference_type operator-(const trivial_vector_iterator &o) const { return _v - o._v; }
+ //! Index iterator
+ reference operator[](size_type n) { return _v[n]; }
+ //! Index iterator
+ const_reference operator[](size_type n) const { return _v[n]; }
+ //! Compare
+ bool operator<(const trivial_vector_iterator &o) const { return _v < o._v; }
+ //! Compare
+ bool operator<=(const trivial_vector_iterator &o) const { return _v <= o._v; }
+ //! Compare
+ bool operator>(const trivial_vector_iterator &o) const { return _v > o._v; }
+ //! Compare
+ bool operator>=(const trivial_vector_iterator &o) const { return _v >= o._v; }
+
+ //! Decrement
+ trivial_vector_iterator &operator--()
+ {
+ _v--;
+ return *this;
+ }
+ //! Decrement
+ const trivial_vector_iterator operator--(int /*unused*/)
+ {
+ trivial_vector_iterator ret(*this);
+ _v--;
+ return ret;
+ }
+
+ //! Increment
+ trivial_vector_iterator &operator++()
+ {
+ _v++;
+ return *this;
+ }
+ //! Increment
+ const trivial_vector_iterator operator++(int /*unused*/)
+ {
+ trivial_vector_iterator ret(*this);
+ _v++;
+ return ret;
+ }
+ //! Compare
+ bool operator==(const trivial_vector_iterator &o) const { return _v == o._v; }
+ //! Compare
+ bool operator!=(const trivial_vector_iterator &o) const { return _v != o._v; }
+
+ //! Dereference
+ reference operator*() { return *_v; }
+ //! Dereference
+ const_reference operator*() const { return *_v; }
+ //! Dereference
+ pointer operator->() { return _v; }
+ //! Dereference
+ const_pointer operator->() const { return _v; }
+ };
+ //! Adds to the iterator
+ template <class T> inline trivial_vector_iterator<T> operator+(trivial_vector_iterator<T> a, size_t n)
+ {
+ a += n;
+ return a;
+ }
+ //! Adds to the iterator
+ template <class T> inline trivial_vector_iterator<T> operator+(size_t n, trivial_vector_iterator<T> a)
+ {
+ a += n;
+ return a;
+ }
+ //! Subtracts from the iterator
+ template <class T> inline trivial_vector_iterator<T> operator-(trivial_vector_iterator<T> a, size_t n)
+ {
+ a -= n;
+ return a;
+ }
+ template <bool has_default_construction, class T> struct trivial_vector_impl
+ {
+ static_assert(std::is_trivially_copyable<T>::value, "trivial_vector: Type T is not trivially copyable!");
+
+ //! Value type
+ using value_type = T;
+ //! Pointer type
+ using pointer = value_type *;
+ //! Const pointer type
+ using const_pointer = typename std::pointer_traits<pointer>::template rebind<const value_type>;
+ //! Difference type
+ using difference_type = typename std::pointer_traits<pointer>::difference_type;
+ //! Size type
+ using size_type = typename std::make_unsigned<difference_type>::type;
+ //! Reference type
+ using reference = value_type &;
+ //! Const reference type
+ using const_reference = const value_type &;
+ //! Iterator type
+ using iterator = pointer;
+ //! Const iterator type
+ using const_iterator = const_pointer;
+ //! Reverse iterator type
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ //! Const reverse iterator type
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ private:
+ section_handle _sh;
+ map_handle _mh;
+ pointer _begin{nullptr}, _end{nullptr}, _capacity{nullptr};
+
+ static size_type _scale_capacity(size_type cap)
+ {
+ if(cap == 0)
+ {
+ cap = utils::page_size() / sizeof(value_type);
+ return cap;
+ }
+ return cap * 2;
+ }
+
+ public:
+ //! Default constructor
+ constexpr trivial_vector_impl() {} // NOLINT
+ //! Filling constructor of `value_type`
+ trivial_vector_impl(size_type count, const value_type &v) { insert(begin(), count, v); }
+ //! Range constructor
+ template <class InputIt> trivial_vector_impl(InputIt first, InputIt last) { insert(begin(), first, last); }
+ //! Copy constructor disabled, use range constructor if you really want this
+ trivial_vector_impl(const trivial_vector_impl &) = delete;
+ //! Copy assigned disabled, use range constructor if you really want this
+ trivial_vector_impl &operator=(const trivial_vector_impl &) = delete;
+ //! Move constructor
+ trivial_vector_impl(trivial_vector_impl &&o) noexcept : _sh(std::move(o._sh)), _mh(std::move(o._mh)), _begin(o._begin), _end(o._end), _capacity(o._capacity)
+ {
+ _mh.set_section(&_sh);
+ o._begin = o._end = o._capacity = nullptr;
+ }
+ //! Move assignment
+ trivial_vector_impl &operator=(trivial_vector_impl &&o) noexcept
+ {
+ this->~trivial_vector_impl();
+ new(this) trivial_vector_impl(std::move(o));
+ return *this;
+ }
+ //! Initialiser list constructor
+ trivial_vector_impl(std::initializer_list<value_type> il);
+ ~trivial_vector_impl() { clear(); }
+
+ //! Assigns
+ void assign(size_type count, const value_type &v)
+ {
+ clear();
+ insert(begin(), count, v);
+ }
+ //! Assigns
+ template <class InputIt> void assign(InputIt first, InputIt last)
+ {
+ clear();
+ insert(begin(), first, last);
+ }
+ //! Assigns
+ void assign(std::initializer_list<value_type> il) { assign(il.begin(), il.end()); }
+
+ //! Item index, bounds checked
+ reference at(size_type i)
+ {
+ if(i >= size())
+ {
+ throw std::out_of_range("bounds exceeded"); // NOLINT
+ }
+ return _begin[i];
+ }
+ //! Item index, bounds checked
+ const_reference at(size_type i) const
+ {
+ if(i >= size())
+ {
+ throw std::out_of_range("bounds exceeded"); // NOLINT
+ }
+ return _begin[i];
+ }
+ //! Item index, unchecked
+ reference operator[](size_type i) { return _begin[i]; }
+ //! Item index, unchecked
+ const_reference operator[](size_type i) const { return _begin[i]; }
+ //! First element
+ reference front() { return _begin[0]; }
+ //! First element
+ const_reference front() const { return _begin[0]; }
+ //! Last element
+ reference back() { return _end[-1]; }
+ //! Last element
+ const_reference back() const { return _end[-1]; }
+ //! Underlying array
+ pointer data() noexcept { return _begin; }
+ //! Underlying array
+ const_pointer data() const noexcept { return _begin; }
+
+ //! Iterator to first item
+ iterator begin() noexcept { return iterator(_begin); }
+ //! Iterator to first item
+ const_iterator begin() const noexcept { return const_iterator(_begin); }
+ //! Iterator to first item
+ const_iterator cbegin() const noexcept { return const_iterator(_begin); }
+ //! Iterator to after last item
+ iterator end() noexcept { return iterator(_end); }
+ //! Iterator to after last item
+ const_iterator end() const noexcept { return const_iterator(_end); }
+ //! Iterator to after last item
+ const_iterator cend() const noexcept { return const_iterator(_end); }
+ //! Iterator to last item
+ reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
+ //! Iterator to last item
+ const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
+ //! Iterator to last item
+ const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); }
+ //! Iterator to before first item
+ reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
+ //! Iterator to before first item
+ const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
+ //! Iterator to before first item
+ const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); }
+
+ //! If empty
+ bool empty() const noexcept { return _end == _begin; }
+ //! Items in container
+ size_type size() const noexcept { return _end - _begin; }
+ //! Maximum items in container
+ size_type max_size() const noexcept { return static_cast<map_handle::size_type>(-1) / sizeof(T); }
+ //! Increase capacity
+ void reserve(size_type n)
+ {
+ if(n > max_size())
+ {
+ throw std::length_error("Max size exceeded"); // NOLINT
+ }
+ size_type current_size = size();
+ size_type bytes = n * sizeof(value_type);
+ bytes = utils::round_up_to_page_size(bytes);
+ if(!_sh.is_valid())
+ {
+ _sh = section_handle::section(bytes).value();
+ _mh = map_handle::map(_sh, bytes).value();
+ }
+ else if(n > capacity())
+ {
+ // We can always grow a section even with maps open on it
+ _sh.truncate(bytes).value();
+ // Attempt to resize the map in place
+ if(!_mh.truncate(bytes, true))
+ {
+ // std::cerr << "truncate fail" << std::endl;
+ // If can't resize, close the map and reopen it into a new address
+ _mh.close().value();
+ _mh = map_handle::map(_sh, bytes).value();
+ }
+ }
+ else
+ {
+ return;
+ }
+ _begin = reinterpret_cast<pointer>(_mh.address());
+ _capacity = reinterpret_cast<pointer>(_mh.address() + bytes);
+ _end = _begin + current_size;
+ }
+ //! Items can be stored until storage expanded
+ size_type capacity() const noexcept { return _capacity - _begin; }
+ //! Removes unused capacity
+ void shrink_to_fit()
+ {
+ size_type current_size = size();
+ size_type bytes = current_size * sizeof(value_type);
+ bytes = utils::round_up_to_page_size(bytes);
+ if(bytes / sizeof(value_type) == capacity())
+ {
+ return;
+ }
+ if(bytes == 0)
+ {
+ _mh.close().value();
+ _sh.close().value();
+ _begin = _end = _capacity = nullptr;
+ return;
+ }
+ _mh.close().value();
+ _sh.truncate(bytes).value();
+ _mh = map_handle::map(_sh, bytes).value();
+ _begin = reinterpret_cast<pointer>(_mh.address());
+ _capacity = reinterpret_cast<pointer>(_mh.address() + bytes);
+ _end = _begin + current_size;
+ }
+ //! Clears container
+ void clear() noexcept
+ {
+ // Trivially copyable means trivial destructor
+ _end = _begin;
+ }
+
+ //! Inserts item
+ iterator insert(const_iterator pos, const value_type &v) { return emplace(pos, v); }
+ //! Inserts item
+ iterator insert(const_iterator pos, value_type &&v) { return emplace(pos, std::move(v)); }
+ //! Inserts items
+ iterator insert(const_iterator pos, size_type count, const value_type &v)
+ {
+ {
+ size_type cap = capacity();
+ while(size() + count < cap)
+ {
+ cap = _scale_capacity(cap);
+ }
+ reserve(cap);
+ }
+ // Trivially copyable, so memmove and we know copy construction can't fail
+ memmove(pos._v + count, pos._v, (_end - pos._v) * sizeof(value_type));
+ for(size_type n = 0; n < count; n++)
+ {
+ new(pos._v + n) value_type(v);
+ }
+ _end += count;
+ return iterator(pos._v);
+ }
+ //! Inserts items
+ template <class InputIt> iterator insert(const_iterator pos, InputIt first, InputIt last)
+ {
+ size_type count = std::distance(first, last);
+ {
+ size_type cap = capacity();
+ while(size() + count < cap)
+ {
+ cap = _scale_capacity(cap);
+ }
+ reserve(cap);
+ }
+ // Trivially copyable, so memmove and we know copy construction can't fail
+ memmove(pos._v + count, pos._v, (_end - pos._v) * sizeof(value_type));
+ for(size_type n = 0; n < count; n++)
+ {
+ new(pos._v + n) value_type(*first++);
+ }
+ _end += count;
+ return iterator(pos._v);
+ }
+ //! Inserts items
+ iterator insert(const_iterator pos, std::initializer_list<value_type> il) { return insert(pos, il.begin(), il.end()); }
+ //! Emplace item
+ template <class... Args> iterator emplace(const_iterator pos, Args &&... args)
+ {
+ if(capacity() == size())
+ {
+ reserve(_scale_capacity(capacity()));
+ }
+ // Trivially copyable, so memmove
+ memmove(pos._v + 1, pos._v, (_end - pos._v) * sizeof(value_type));
+ // BUT complex constructors may throw!
+ try
+ {
+ new(pos._v) value_type(std::forward<Args>(args)...);
+ }
+ catch(...)
+ {
+ memmove(pos._v, pos._v + 1, (_end - pos._v) * sizeof(value_type));
+ throw;
+ }
+ ++_end;
+ return iterator(pos._v);
+ }
+
+ //! Erases item
+ iterator erase(const_iterator pos)
+ {
+ // Trivially copyable
+ memmove(pos._v, pos._v + 1, ((_end - 1) - pos._v) * sizeof(value_type));
+ --_end;
+ return iterator(pos._v);
+ }
+ //! Erases items
+ iterator erase(const_iterator first, const_iterator last)
+ {
+ // Trivially copyable
+ memmove(first._v, last._v, (_end - last._v) * sizeof(value_type));
+ _end -= last._v - first._v;
+ return iterator(first._v);
+ }
+ //! Appends item
+ void push_back(const value_type &v)
+ {
+ if(_end == _capacity)
+ {
+ reserve(_scale_capacity(capacity()));
+ }
+ new(_end++) value_type(v);
+ }
+ //! Appends item
+ void push_back(value_type &&v) { push_back(v); }
+ //! Appends item
+ template <class... Args> reference emplace_back(Args &&... args)
+ {
+ if(_end == _capacity)
+ {
+ reserve(_scale_capacity(capacity()));
+ }
+ new(_end) value_type(std::forward<Args>(args)...);
+ ++_end;
+ return *(_end - 1);
+ }
+ //! Removes last element
+ void pop_back()
+ {
+ if(!empty())
+ {
+ --_end;
+ }
+ }
+
+ //! Resizes container
+ void resize(size_type count, const value_type &v)
+ {
+ if(count < size())
+ {
+ // Trivially copyable means trivial destructor
+ _end = _begin + count;
+ return;
+ }
+ if(count > capacity())
+ {
+ reserve(count);
+ }
+ // TODO(ned): Kinda assuming the compiler will do the right thing below, should really check that
+ while(count - size() >= 16)
+ {
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ new(_end++) value_type(v);
+ }
+ while(count > size())
+ {
+ new(_end++) value_type(v);
+ }
+ }
+ //! Swaps
+ void swap(trivial_vector_impl &o) noexcept
+ {
+ using namespace std;
+ swap(_sh, o._sh);
+ swap(_mh, o._mh);
+ swap(_begin, o._begin);
+ swap(_end, o._end);
+ swap(_capacity, o._capacity);
+ }
+ };
+
+ template <class T> struct trivial_vector_impl<true, T> : trivial_vector_impl<false, T>
+ {
+ //! Value type
+ using value_type = T;
+ //! Pointer type
+ using pointer = value_type *;
+ //! Const pointer type
+ using const_pointer = typename std::pointer_traits<pointer>::template rebind<const value_type>;
+ //! Difference type
+ using difference_type = typename std::pointer_traits<pointer>::difference_type;
+ //! Size type
+ using size_type = typename std::make_unsigned<difference_type>::type;
+ //! Reference type
+ using reference = value_type &;
+ //! Const reference type
+ using const_reference = const value_type &;
+ //! Iterator type
+ using iterator = pointer;
+ //! Const iterator type
+ using const_iterator = const_pointer;
+ //! Reverse iterator type
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ //! Const reverse iterator type
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ public:
+ constexpr trivial_vector_impl() {} // NOLINT
+ using trivial_vector_impl<false, T>::trivial_vector_impl;
+ //! Filling constructor of default constructed `value_type`
+ explicit trivial_vector_impl(size_type count)
+ : trivial_vector_impl(count, value_type{})
+ {
+ }
+ using trivial_vector_impl<false, T>::resize;
+ //! Resizes container, filling any new items with default constructed `value_type`
+ void resize(size_type count) { return resize(count, value_type{}); }
+ };
+ } // namespace detail
+
+/*! \class trivial_vector
+\brief Provides a constant time capacity expanding move-only STL vector. Requires `T` to be
+trivially copyable.
+
+As a hand waving estimate for whether this vector implementation may be useful to you,
+it usually roughly breaks even with `std::vector` on recent Intel CPUs at around the L2 cache
+boundary. So if your vector fits into the L2 cache, this implementation will be no
+better, but no worse. If your vector fits into the L1 cache, this implementation will be
+worse, often considerably so.
+
+Note that no STL allocator support is provided as `T` must be trivially copyable
+(for which most STL's simply use `memcpy()` anyway instead of the allocator's
+`construct`), and an internal `section_handle` is used for the storage in order
+to implement the constant time capacity resizing.
+
+We also disable the copy constructor, as copying an entire backing file is expensive.
+Use the iterator based copy constructor if you really want to copy one of these.
+
+The very first item stored reserves a capacity of `utils::page_size()/sizeof(T)`
+on POSIX and `65536/sizeof(T)` on Windows.
+Capacity is doubled in byte terms thereafter (i.e. 8Kb, 16Kb and so on).
+As mentioned earlier, the capacity of the vector needs to become reasonably large
+before going to the kernel to resize the `section_handle` and remapping memory
+becomes faster than `memcpy`. For these reasons, this vector implementation is
+best suited to arrays of unknown in advance, but likely large, sizes.
+
+Benchmarking notes for Skylake 3.1Ghz Intel Core i5 with 2133Mhz DDR3 RAM, L2 256Kb,
+L3 4Mb:
+- OS X with clang 5.0 and libc++
+ - push_back():
+ 32768,40,59
+ 65536,36,76
+ 131072,78,159
+ 262144,166,198
+ 524288,284,299
+ 1048576,558,572
+ 2097152,1074,1175
+ 4194304,2201,2394
+ 8388608,4520,5503
+ 16777216,9339,9327
+ 33554432,24853,17754
+ 67108864,55876,40973
+ 134217728,122577,86331
+ 268435456,269004,178751
+ 536870912,586466,330716
+ - resize():
+ 8192,9,18
+ 16384,14,20
+ 32768,27,32
+ 65536,66,43
+ 131072,107,59
+ 262144,222,131
+ 524288,445,322
+ 1048576,885,500
+ 2097152,1785,1007
+ 4194304,3623,2133
+ 8388608,7334,4082
+ 16777216,17096,8713
+ 33554432,36890,18421
+ 67108864,73593,40702
+- Windows 10 with VS2017.5:
+ - push_back():
+ 8192,17,70
+ 16384,22,108
+ 32768,36,117
+ 65536,51,152
+ 131072,174,209
+ 262144,336,309
+ 524288,661,471
+ 1048576,1027,787
+ 2097152,2513,1361
+ 4194304,5948,2595
+ 8388608,9919,4820
+ 16777216,23789,9716
+ 33554432,52997,19558
+ 67108864,86468,39240
+ 134217728,193013,76298
+ 268435456,450059,149644
+ 536870912,983442,318078
+ - resize():
+ 8192,9,48
+ 16384,17,44
+ 32768,35,48
+ 65536,62,72
+ 131072,134,114
+ 262144,132,168
+ 524288,505,330
+ 1048576,988,582
+ 2097152,1501,1152
+ 4194304,2972,2231
+ 8388608,6122,4436
+ 16777216,12483,8906
+ 33554432,25203,17847
+ 67108864,52005,53646
+ 134217728,102942,86502
+ 268435456,246367,177999
+ 536870912,405524,294685
+ */
+#ifndef DOXYGEN_IS_IN_THE_HOUSE
+ template <class T> AFIO_REQUIRES(std::is_trivially_copyable<T>::value) class trivial_vector : public detail::trivial_vector_impl<std::is_default_constructible<T>::value, T>
+#else
+ template <class T> class trivial_vector : public impl::trivial_vector_impl<true, T>
+#endif
+ {
+ static_assert(std::is_trivially_copyable<T>::value, "trivial_vector: Type T is not trivially copyable!");
+
+ public:
+ constexpr trivial_vector() {} // NOLINT
+ using detail::trivial_vector_impl<std::is_default_constructible<T>::value, T>::trivial_vector_impl;
+ };
+
+ //! Compare
+ template <class T> inline bool operator==(const trivial_vector<T> &a, const trivial_vector<T> &b);
+ //! Compare
+ template <class T> inline bool operator!=(const trivial_vector<T> &a, const trivial_vector<T> &b);
+ //! Compare
+ template <class T> inline bool operator<(const trivial_vector<T> &a, const trivial_vector<T> &b);
+ //! Compare
+ template <class T> inline bool operator<=(const trivial_vector<T> &a, const trivial_vector<T> &b);
+ //! Compare
+ template <class T> inline bool operator>(const trivial_vector<T> &a, const trivial_vector<T> &b);
+ //! Compare
+ template <class T> inline bool operator>=(const trivial_vector<T> &a, const trivial_vector<T> &b);
+ //! Swap
+ template <class T> inline void swap(trivial_vector<T> &a, trivial_vector<T> &b) noexcept { a.swap(b); }
+
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
+
+#endif
diff --git a/include/llfio/v2.0/async_file_handle.hpp b/include/llfio/v2.0/async_file_handle.hpp
new file mode 100644
index 00000000..8eed1c2b
--- /dev/null
+++ b/include/llfio/v2.0/async_file_handle.hpp
@@ -0,0 +1,818 @@
+/* An async handle to a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "file_handle.hpp"
+#include "io_service.hpp"
+
+//! \file async_file_handle.hpp Provides async_file_handle
+
+#ifndef AFIO_ASYNC_FILE_HANDLE_H
+#define AFIO_ASYNC_FILE_HANDLE_H
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace detail
+{
+#if __cplusplus > 201700 && (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION > 7000 /* approx end of 2017 */)
+ template <class R, class Fn, class... Args> using is_invocable_r = std::is_invocable_r<R, Fn, Args...>;
+#else
+ template <class R, class Fn, class... Args> using is_invocable_r = std::true_type;
+#endif
+}
+
+/*! \class async_file_handle
+\brief An asynchronous handle to an open something.
+
+\note Unlike the others, `async_file_handle` defaults to `only_metadata` caching as that is the
+only use case where using async i/o makes sense given the other options below.
+
+<table>
+<tr><th></th><th>Cost of opening</th><th>Cost of i/o</th><th>Concurrency and Atomicity</th><th>Other remarks</th></tr>
+<tr><td>`file_handle`</td><td>Least</td><td>Syscall</td><td>POSIX guarantees (usually)</td><td>Least gotcha</td></tr>
+<tr><td>`async_file_handle`</td><td>More</td><td>Most (syscall + malloc/free + reactor)</td><td>POSIX guarantees (usually)</td><td>Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`</td></tr>
+<tr><td>`mapped_file_handle`</td><td>Most</td><td>Least</td><td>None</td><td>Cannot be used with uncached i/o</td></tr>
+</table>
+
+\warning i/o initiated by this class MUST be on the same kernel thread as which
+created the owning `io_service` which MUST also be the same kernel thread as which
+runs the i/o service's `run()` function.
+
+\snippet coroutines.cpp coroutines_example
+*/
+class AFIO_DECL async_file_handle : public file_handle
+{
+ friend class io_service;
+
+public:
+ using dev_t = file_handle::dev_t;
+ using ino_t = file_handle::ino_t;
+ using path_view_type = file_handle::path_view_type;
+ using path_type = io_handle::path_type;
+ using extent_type = io_handle::extent_type;
+ using size_type = io_handle::size_type;
+ using mode = io_handle::mode;
+ using creation = io_handle::creation;
+ using caching = io_handle::caching;
+ using flag = io_handle::flag;
+ using buffer_type = io_handle::buffer_type;
+ using const_buffer_type = io_handle::const_buffer_type;
+ using buffers_type = io_handle::buffers_type;
+ using const_buffers_type = io_handle::const_buffers_type;
+ template <class T> using io_request = io_handle::io_request<T>;
+ template <class T> using io_result = io_handle::io_result<T>;
+
+protected:
+ // Do NOT declare variables here, put them into file_handle to preserve up-conversion
+
+public:
+ //! Default constructor
+ constexpr async_file_handle() {} // NOLINT
+ ~async_file_handle() = default;
+
+ //! Construct a handle from a supplied native handle
+ constexpr async_file_handle(io_service *service, native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none)
+ : file_handle(std::move(h), devid, inode, caching, flags)
+ {
+ this->_service = service;
+ }
+ //! Implicit move construction of async_file_handle permitted
+ async_file_handle(async_file_handle &&o) noexcept = default;
+ //! No copy construction (use `clone()`)
+ async_file_handle(const async_file_handle &) = delete;
+ //! Explicit conversion from file_handle permitted
+ explicit constexpr async_file_handle(file_handle &&o) noexcept : file_handle(std::move(o)) {}
+ //! Explicit conversion from handle and io_handle permitted
+ explicit constexpr async_file_handle(handle &&o, io_service *service, dev_t devid, ino_t inode) noexcept : file_handle(std::move(o), devid, inode) { this->_service = service; }
+ //! Move assignment of async_file_handle permitted
+ async_file_handle &operator=(async_file_handle &&o) noexcept
+ {
+ this->~async_file_handle();
+ new(this) async_file_handle(std::move(o));
+ return *this;
+ }
+ //! No copy assignment
+ async_file_handle &operator=(const async_file_handle &) = delete;
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(async_file_handle &o) noexcept
+ {
+ async_file_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! Create an async file handle opening access to a file on path
+ using the given io_service.
+ \param service The `io_service` to use.
+ \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+ \param _path The path relative to base to open.
+ \param _mode How to open the file.
+ \param _creation How to create the file.
+ \param _caching How to ask the kernel to cache the file.
+ \param flags Any additional custom behaviours.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<async_file_handle> async_file(io_service &service, const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::only_metadata, flag flags = flag::none) noexcept
+ {
+ // Open it overlapped, otherwise no difference.
+ OUTCOME_TRY(v, file_handle::file(std::move(base), _path, _mode, _creation, _caching, flags | flag::overlapped));
+ async_file_handle ret(std::move(v));
+ ret._service = &service;
+ return std::move(ret);
+ }
+
+ /*! Create an async file handle creating a randomly named file on a path.
+ The file is opened exclusively with `creation::only_if_not_exist` so it
+ will never collide with nor overwrite any existing file.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<async_file_handle> async_random_file(io_service &service, const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::only_metadata, flag flags = flag::none) noexcept
+ {
+ try
+ {
+ for(;;)
+ {
+ auto randomname = utils::random_string(32);
+ randomname.append(".random");
+ result<async_file_handle> ret = async_file(service, dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags);
+ if(ret || (!ret && ret.error() != errc::file_exists))
+ {
+ return ret;
+ }
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ /*! Create an async file handle creating the named file on some path which
+ the OS declares to be suitable for temporary files. Most OSs are
+ very lazy about flushing changes made to these temporary files.
+ Note the default flags are to have the newly created file deleted
+ on first handle close.
+ Note also that an empty name is equivalent to calling
+ `async_random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+ parameter is ignored.
+
+ \note If the temporary file you are creating is not going to have its
+ path sent to another process for usage, this is the WRONG function
+ to use. Use `temp_inode()` instead, it is far more secure.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<async_file_handle> async_temp_file(io_service &service, path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::only_metadata, flag flags = flag::unlink_on_first_close) noexcept
+ {
+ auto &tempdirh = path_discovery::storage_backed_temporary_files_directory();
+ return name.empty() ? async_random_file(service, tempdirh, _mode, _caching, flags) : async_file(service, tempdirh, name, _mode, _creation, _caching, flags);
+ }
+ /*! \em Securely create an async file handle creating a temporary anonymous inode in
+ the filesystem referred to by \em dirpath. The inode created has
+ no name nor accessible path on the filing system and ceases to
+ exist as soon as the last handle is closed, making it ideal for use as
+ a temporary file where other processes do not need to have access
+ to its contents via some path on the filing system (a classic use case
+ is for backing shared memory maps).
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<async_file_handle> async_temp_inode(io_service &service, const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept
+ {
+ // Open it overlapped, otherwise no difference.
+ OUTCOME_TRY(v, file_handle::temp_inode(dir, _mode, flags | flag::overlapped));
+ async_file_handle ret(std::move(v));
+ ret._service = &service;
+ return std::move(ret);
+ }
+
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override;
+ /*! Clone this handle to a different io_service (copy constructor is disabled to avoid accidental copying)
+
+ \errors Any of the values POSIX dup() or DuplicateHandle() can return.
+ */
+ result<async_file_handle> clone(io_service &service, mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept
+ {
+ OUTCOME_TRY(v, file_handle::clone(mode_, caching_, d));
+ async_file_handle ret(std::move(v));
+ ret._service = &service;
+ return std::move(ret);
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept override
+ {
+ OUTCOME_TRY(v, file_handle::clone(mode_, caching_, d));
+ async_file_handle ret(std::move(v));
+ ret._service = _service;
+ return static_cast<file_handle &&>(ret);
+ }
+
+#if DOXYGEN_SHOULD_SKIP_THIS
+private:
+#else
+protected:
+#endif
+ using shared_size_type = size_type;
+ enum class operation_t
+ {
+ read,
+ write,
+ fsync_sync,
+ dsync_sync,
+ fsync_async,
+ dsync_async
+ };
+ struct _erased_completion_handler;
+ // Holds state for an i/o in progress. Will be subclassed with platform specific state and how to implement completion.
+ struct _erased_io_state_type
+ {
+ friend class io_service;
+ async_file_handle *parent;
+ operation_t operation;
+ bool must_deallocate_self;
+ size_t items;
+ shared_size_type items_to_go;
+ union result_storage {
+ io_result<buffers_type> read;
+ io_result<const_buffers_type> write;
+ constexpr result_storage()
+ : read(buffers_type())
+ {
+ }
+ } result;
+ constexpr _erased_io_state_type(async_file_handle *_parent, operation_t _operation, bool _must_deallocate_self, size_t _items)
+ : parent(_parent)
+ , operation(_operation)
+ , must_deallocate_self(_must_deallocate_self)
+ , items(_items)
+ , items_to_go(0)
+ {
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~_erased_io_state_type()
+ {
+ // i/o still pending is very bad, this should never happen
+ assert(!items_to_go);
+ if(items_to_go != 0u)
+ {
+ AFIO_LOG_FATAL(parent->native_handle().h, "FATAL: io_state destructed while i/o still in flight, the derived class should never allow this.");
+ abort();
+ }
+ }
+
+ //! Retrieves a pointer to the copy of the completion handler held inside the i/o state.
+ virtual _erased_completion_handler *erased_completion_handler() noexcept = 0;
+ /* Called when an i/o is completed by the system, figures out whether to call invoke_completion.
+
+ For Windows:
+ - errcode: GetLastError() code
+ - bytes_transferred: obvious
+ - internal_state: LPOVERLAPPED for this op
+
+ For POSIX AIO:
+ - errcode: errno code
+ - bytes_transferred: return from aio_return(), usually bytes transferred
+ - internal_state: address of pointer to struct aiocb in io_service's _aiocbsv
+ */
+ virtual void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept = 0;
+
+ protected:
+ _erased_io_state_type(_erased_io_state_type &&) = default;
+ _erased_io_state_type(const _erased_io_state_type &) = default;
+ _erased_io_state_type &operator=(_erased_io_state_type &&) = default;
+ _erased_io_state_type &operator=(const _erased_io_state_type &) = default;
+ };
+ struct _io_state_deleter
+ {
+ template <class U> void operator()(U *_ptr) const
+ {
+ bool must_deallocate_self = _ptr->must_deallocate_self;
+ _ptr->~U();
+ if(must_deallocate_self)
+ {
+ auto *ptr = reinterpret_cast<char *>(_ptr);
+ ::free(ptr); // NOLINT
+ }
+ }
+ };
+
+public:
+ /*! Smart pointer to state of an i/o in progress. Destroying this before an i/o has completed
+ is <b>blocking</b> because the i/o must be cancelled before the destructor can safely exit.
+ */
+ using io_state_ptr = std::unique_ptr<_erased_io_state_type, _io_state_deleter>;
+
+#if DOXYGEN_SHOULD_SKIP_THIS
+private:
+#else
+protected:
+#endif
+ // Used to indirect copy and call of unknown completion handler
+ struct _erased_completion_handler
+ {
+ virtual ~_erased_completion_handler() = default;
+ // Returns my size including completion handler
+ virtual size_t bytes() const noexcept = 0;
+ // Moves me and handler to some new location
+ virtual void move(_erased_completion_handler *dest) = 0;
+ // Invokes my completion handler
+ virtual void operator()(_erased_io_state_type *state) = 0;
+ // Returns a pointer to the completion handler
+ virtual void *address() noexcept = 0;
+
+ protected:
+ _erased_completion_handler() = default;
+ _erased_completion_handler(_erased_completion_handler &&) = default;
+ _erased_completion_handler(const _erased_completion_handler &) = default;
+ _erased_completion_handler &operator=(_erased_completion_handler &&) = default;
+ _erased_completion_handler &operator=(const _erased_completion_handler &) = default;
+ };
+ template <class BuffersType, class IORoutine> result<io_state_ptr> AFIO_HEADERS_ONLY_MEMFUNC_SPEC _begin_io(span<char> mem, operation_t operation, io_request<BuffersType> reqs, _erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept;
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<io_state_ptr> _begin_io(span<char> mem, operation_t operation, io_request<const_buffers_type> reqs, _erased_completion_handler &&completion) noexcept;
+
+public:
+ /*! \brief Schedule a barrier to occur asynchronously.
+
+ \note All the caveats and exclusions which apply to `barrier()` also apply here. Note that Microsoft Windows
+ does not support asynchronously executed barriers, and this call will fail on that operating system.
+
+ \return Either an io_state_ptr to the i/o in progress, or an error code.
+ \param reqs A scatter-gather and offset request for what range to barrier. May be ignored on some platforms
+ which always write barrier the entire file. Supplying a default initialised reqs write barriers the entire file.
+ \param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &)`.
+ Note that buffers returned may not be buffers input, see documentation for `barrier()`.
+ \param wait_for_device True if you want the call to wait until data reaches storage and that storage
+ has acknowledged the data is physically written. Slow.
+ \param and_metadata True if you want the call to sync the metadata for retrieving the writes before the
+ barrier after a sudden power loss event. Slow.
+ \param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
+ \errors As for `barrier()`, plus `ENOMEM`.
+ \mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
+ erased completion handler of unknown type and state per buffers input.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ template <class CompletionRoutine> //
+ AFIO_REQUIRES(detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<const_buffers_type> &>::value) //
+ result<io_state_ptr> async_barrier(io_request<const_buffers_type> reqs, CompletionRoutine &&completion, bool wait_for_device = false, bool and_metadata = false, span<char> mem = {}) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ struct completion_handler : _erased_completion_handler
+ {
+ CompletionRoutine completion;
+ explicit completion_handler(CompletionRoutine c)
+ : completion(std::move(c))
+ {
+ }
+ size_t bytes() const noexcept final { return sizeof(*this); }
+ void move(_erased_completion_handler *_dest) final
+ {
+ auto *dest = reinterpret_cast<void *>(_dest);
+ new(dest) completion_handler(std::move(*this));
+ }
+ void operator()(_erased_io_state_type *state) final { completion(state->parent, state->result.write); }
+ void *address() noexcept final { return &completion; }
+ } ch{std::forward<CompletionRoutine>(completion)};
+ operation_t operation = operation_t::fsync_sync;
+ if(!wait_for_device && and_metadata)
+ {
+ operation = operation_t::fsync_async;
+ }
+ else if(wait_for_device && !and_metadata)
+ {
+ operation = operation_t::dsync_sync;
+ }
+ else if(!wait_for_device && !and_metadata)
+ {
+ operation = operation_t::dsync_async;
+ }
+ return _begin_io(mem, operation, reinterpret_cast<io_request<const_buffers_type> &>(reqs), std::move(ch));
+ }
+
+ /*! \brief Schedule a read to occur asynchronously.
+
+ Note that some OS kernels can only process a limited number async i/o
+ operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
+ and gracefully reschedule the i/o for a later time. This temporary
+ failure may be returned immediately, or to the completion handler
+ and hence you ought to handle both situations.
+
+ \return Either an io_state_ptr to the i/o in progress, or an error code.
+ \param reqs A scatter-gather and offset request.
+ \param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<buffers_type> &)`.
+ Note that buffers returned may not be buffers input, see documentation for `read()`.
+ \param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
+ \errors As for `read()`, plus `ENOMEM`.
+ \mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
+ erased completion handler of unknown type and state per buffers input.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ template <class CompletionRoutine> //
+ AFIO_REQUIRES(detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<buffers_type> &>::value) //
+ result<io_state_ptr> async_read(io_request<buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ struct completion_handler : _erased_completion_handler
+ {
+ CompletionRoutine completion;
+ explicit completion_handler(CompletionRoutine c)
+ : completion(std::move(c))
+ {
+ }
+ size_t bytes() const noexcept final { return sizeof(*this); }
+ void move(_erased_completion_handler *_dest) final
+ {
+ auto *dest = reinterpret_cast<void *>(_dest);
+ new(dest) completion_handler(std::move(*this));
+ }
+ void operator()(_erased_io_state_type *state) final { completion(state->parent, state->result.read); }
+ void *address() noexcept final { return &completion; }
+ } ch{std::forward<CompletionRoutine>(completion)};
+ return _begin_io(mem, operation_t::read, io_request<const_buffers_type>({reinterpret_cast<const_buffer_type *>(reqs.buffers.data()), reqs.buffers.size()}, reqs.offset), std::move(ch));
+ }
+
+ /*! \brief Schedule a write to occur asynchronously.
+
+ Note that some OS kernels can only process a limited number async i/o
+ operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
+ and gracefully reschedule the i/o for a later time. This temporary
+ failure may be returned immediately, or to the completion handler
+ and hence you ought to handle both situations.
+
+
+ \return Either an io_state_ptr to the i/o in progress, or an error code.
+ \param reqs A scatter-gather and offset request.
+ \param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &)`.
+ Note that buffers returned may not be buffers input, see documentation for `write()`.
+ \param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
+ \errors As for `write()`, plus `ENOMEM`.
+ \mallocs If mem in not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
+ erased completion handler of unknown type and state per buffers input.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ template <class CompletionRoutine> //
+ AFIO_REQUIRES(detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<const_buffers_type> &>::value) //
+ result<io_state_ptr> async_write(io_request<const_buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ struct completion_handler : _erased_completion_handler
+ {
+ CompletionRoutine completion;
+ explicit completion_handler(CompletionRoutine c)
+ : completion(std::move(c))
+ {
+ }
+ size_t bytes() const noexcept final { return sizeof(*this); }
+ void move(_erased_completion_handler *_dest) final
+ {
+ auto *dest = reinterpret_cast<void *>(_dest);
+ new(dest) completion_handler(std::move(*this));
+ }
+ void operator()(_erased_io_state_type *state) final { completion(state->parent, state->result.write); }
+ void *address() noexcept final { return &completion; }
+ } ch{std::forward<CompletionRoutine>(completion)};
+ return _begin_io(mem, operation_t::write, reqs, std::move(ch));
+ }
+
+ using file_handle::read;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
+ using file_handle::write;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override;
+
+#if defined(__cpp_coroutines) || defined(DOXYGEN_IS_IN_THE_HOUSE)
+private:
+ template <class BuffersType> class awaitable_state
+ {
+ friend class async_file_handle;
+ optional<coroutine_handle<>> _suspended;
+ optional<io_result<BuffersType>> _result;
+
+ // Called on completion of the i/o
+ void operator()(async_file_handle * /*unused*/, io_result<BuffersType> &result)
+ {
+ // store the result and resume the coroutine
+ _result = std::move(result);
+ if(_suspended)
+ {
+ _suspended->resume();
+ }
+ }
+ };
+
+public:
+ //! Type sugar to tell `co_await` what to do
+ template <class BuffersType> class awaitable
+ {
+ friend class async_file_handle;
+ io_state_ptr _state;
+ awaitable_state<BuffersType> *_astate;
+
+ explicit awaitable(io_state_ptr state)
+ : _state(std::move(state))
+ , _astate(reinterpret_cast<awaitable_state<BuffersType> *>(_state->erased_completion_handler()->address()))
+ {
+ }
+
+ public:
+ //! Called by `co_await` to determine whether to suspend the coroutine.
+ bool await_ready() { return _astate->_result.has_value(); }
+ //! Called by `co_await` to suspend the coroutine.
+ void await_suspend(coroutine_handle<> co) { _astate->_suspended = co; }
+ //! Called by `co_await` after resuming the coroutine to return a value.
+ io_result<BuffersType> await_resume() { return std::move(*_astate->_result); }
+ };
+
+public:
+ /*! \brief Schedule a read to occur asynchronously.
+
+ \return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
+ until the operation has completed, resuming with the buffers read, which may
+ not be the buffers input. The size of each scatter-gather buffer is updated with the number
+ of bytes of that buffer transferred, and the pointer to the data may be \em completely
+ different to what was submitted (e.g. it may point into a memory map).
+ \param reqs A scatter-gather and offset request.
+ \errors As for read(), plus ENOMEM.
+ \mallocs One calloc, one free.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ result<awaitable<buffers_type>> co_read(io_request<buffers_type> reqs) noexcept
+ {
+ OUTCOME_TRY(r, async_read(reqs, awaitable_state<buffers_type>()));
+ return awaitable<buffers_type>(std::move(r));
+ }
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ result<awaitable<buffers_type>> co_read(extent_type offset, std::initializer_list<buffer_type> lst) noexcept
+ {
+ buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
+ memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
+ io_request<buffers_type> reqs(buffers_type(_reqs, lst.size()), offset);
+ return co_read(reqs);
+ }
+
+ /*! \brief Schedule a write to occur asynchronously
+
+ \return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
+ until the operation has completed, resuming with the buffers written, which
+ may not be the buffers input. The size of each scatter-gather buffer is updated with
+ the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \errors As for write(), plus ENOMEM.
+ \mallocs One calloc, one free.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ result<awaitable<const_buffers_type>> co_write(io_request<const_buffers_type> reqs) noexcept
+ {
+ OUTCOME_TRY(r, async_write(reqs, awaitable_state<const_buffers_type>()));
+ return awaitable<const_buffers_type>(std::move(r));
+ }
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ result<awaitable<const_buffers_type>> co_write(extent_type offset, std::initializer_list<const_buffer_type> lst) noexcept
+ {
+ const_buffer_type *_reqs = reinterpret_cast<const_buffer_type *>(alloca(sizeof(const_buffer_type) * lst.size()));
+ memcpy(_reqs, lst.begin(), sizeof(const_buffer_type) * lst.size());
+ io_request<const_buffers_type> reqs(const_buffers_type(_reqs, lst.size()), offset);
+ return co_write(reqs);
+ }
+#endif
+};
+
+//! \brief Constructor for `async_file_handle`
+template <> struct construct<async_file_handle>
+{
+ io_service &service;
+ const path_handle &base;
+ async_file_handle::path_view_type _path;
+ async_file_handle::mode _mode = async_file_handle::mode::read;
+ async_file_handle::creation _creation = async_file_handle::creation::open_existing;
+ async_file_handle::caching _caching = async_file_handle::caching::only_metadata;
+ async_file_handle::flag flags = async_file_handle::flag::none;
+ result<async_file_handle> operator()() const noexcept { return async_file_handle::async_file(service, base, _path, _mode, _creation, _caching, flags); }
+};
+
+
+// BEGIN make_free_functions.py
+//! Swap with another instance
+inline void swap(async_file_handle &self, async_file_handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+/*! Create an async file handle opening access to a file on path
+using the given io_service.
+\param service The `io_service` to use.
+\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+\param _path The path relative to base to open.
+\param _mode How to open the file.
+\param _creation How to create the file.
+\param _caching How to ask the kernel to cache the file.
+\param flags Any additional custom behaviours.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<async_file_handle> async_file(io_service &service, const path_handle &base, async_file_handle::path_view_type _path, async_file_handle::mode _mode = async_file_handle::mode::read, async_file_handle::creation _creation = async_file_handle::creation::open_existing,
+ async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::none) noexcept
+{
+ return async_file_handle::async_file(std::forward<decltype(service)>(service), std::forward<decltype(base)>(base), std::forward<decltype(_path)>(_path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching),
+ std::forward<decltype(flags)>(flags));
+}
+/*! Create an async file handle creating a randomly named file on a path.
+The file is opened exclusively with `creation::only_if_not_exist` so it
+will never collide with nor overwrite any existing file.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<async_file_handle> async_random_file(io_service &service, const path_handle &dirpath, async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::none) noexcept
+{
+ return async_file_handle::async_random_file(std::forward<decltype(service)>(service), std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create an async file handle creating the named file on some path which
+the OS declares to be suitable for temporary files. Most OSs are
+very lazy about flushing changes made to these temporary files.
+Note the default flags are to have the newly created file deleted
+on first handle close.
+Note also that an empty name is equivalent to calling
+`async_random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+parameter is ignored.
+
+\note If the temporary file you are creating is not going to have its
+path sent to another process for usage, this is the WRONG function
+to use. Use `temp_inode()` instead, it is far more secure.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<async_file_handle> async_temp_file(io_service &service, async_file_handle::path_view_type name = async_file_handle::path_view_type(), async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::creation _creation = async_file_handle::creation::if_needed,
+ async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::unlink_on_first_close) noexcept
+{
+ return async_file_handle::async_temp_file(std::forward<decltype(service)>(service), std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! \em Securely create an async file handle creating a temporary anonymous inode in
+the filesystem referred to by \em dirpath. The inode created has
+no name nor accessible path on the filing system and ceases to
+exist as soon as the last handle is closed, making it ideal for use as
+a temporary file where other processes do not need to have access
+to its contents via some path on the filing system (a classic use case
+is for backing shared memory maps).
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<async_file_handle> async_temp_inode(io_service &service, const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::flag flags = async_file_handle::flag::none) noexcept
+{
+ return async_file_handle::async_temp_inode(std::forward<decltype(service)>(service), std::forward<decltype(dir)>(dir), std::forward<decltype(_mode)>(_mode), std::forward<decltype(flags)>(flags));
+}
+inline async_file_handle::io_result<async_file_handle::const_buffers_type> barrier(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs = async_file_handle::io_request<async_file_handle::const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false,
+ deadline d = deadline()) noexcept
+{
+ return self.barrier(std::forward<decltype(reqs)>(reqs), std::forward<decltype(wait_for_device)>(wait_for_device), std::forward<decltype(and_metadata)>(and_metadata), std::forward<decltype(d)>(d));
+}
+/*! \brief Schedule a barrier to occur asynchronously.
+
+\note All the caveats and exclusions which apply to `barrier()` also apply here. Note that Microsoft Windows
+does not support asynchronously executed barriers, and this call will fail on that operating system.
+
+\return Either an io_state_ptr to the i/o in progress, or an error code.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request for what range to barrier. May be ignored on some platforms
+which always write barrier the entire file. Supplying a default initialised reqs write barriers the entire file.
+\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &)`.
+Note that buffers returned may not be buffers input, see documentation for `barrier()`.
+\param wait_for_device True if you want the call to wait until data reaches storage and that storage
+has acknowledged the data is physically written. Slow.
+\param and_metadata True if you want the call to sync the metadata for retrieving the writes before the
+barrier after a sudden power loss event. Slow.
+\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
+\errors As for `barrier()`, plus `ENOMEM`.
+\mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
+erased completion handler of unknown type and state per buffers input.
+*/
+template <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_barrier(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, CompletionRoutine &&completion, bool wait_for_device = false, bool and_metadata = false, span<char> mem = {}) noexcept
+{
+ return self.async_barrier(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(wait_for_device)>(wait_for_device), std::forward<decltype(and_metadata)>(and_metadata), std::forward<decltype(mem)>(mem));
+}
+/*! \brief Schedule a read to occur asynchronously.
+
+Note that some OS kernels can only process a limited number async i/o
+operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
+and gracefully reschedule the i/o for a later time. This temporary
+failure may be returned immediately, or to the completion handler
+and hence you ought to handle both situations.
+
+\return Either an io_state_ptr to the i/o in progress, or an error code.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<buffers_type> &)`.
+Note that buffers returned may not be buffers input, see documentation for `read()`.
+\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
+\errors As for `read()`, plus `ENOMEM`.
+\mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
+erased completion handler of unknown type and state per buffers input.
+*/
+template <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_read(async_file_handle &self, async_file_handle::io_request<async_file_handle::buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
+{
+ return self.async_read(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(mem));
+}
+/*! \brief Schedule a write to occur asynchronously.
+
+Note that some OS kernels can only process a limited number async i/o
+operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
+and gracefully reschedule the i/o for a later time. This temporary
+failure may be returned immediately, or to the completion handler
+and hence you ought to handle both situations.
+
+
+\return Either an io_state_ptr to the i/o in progress, or an error code.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &)`.
+Note that buffers returned may not be buffers input, see documentation for `write()`.
+\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
+\errors As for `write()`, plus `ENOMEM`.
+\mallocs If mem in not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
+erased completion handler of unknown type and state per buffers input.
+*/
+template <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_write(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
+{
+ return self.async_write(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(mem));
+}
+#if defined(__cpp_coroutines) || defined(DOXYGEN_IS_IN_THE_HOUSE)
+/*! \brief Schedule a read to occur asynchronously.
+
+\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
+until the operation has completed, resuming with the buffers read, which may
+not be the buffers input. The size of each scatter-gather buffer is updated with the number
+of bytes of that buffer transferred, and the pointer to the data may be \em completely
+different to what was submitted (e.g. it may point into a memory map).
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\errors As for read(), plus ENOMEM.
+\mallocs One calloc, one free.
+*/
+inline result<async_file_handle::awaitable<async_file_handle::buffers_type>> co_read(async_file_handle &self, async_file_handle::io_request<async_file_handle::buffers_type> reqs) noexcept
+{
+ return self.co_read(std::forward<decltype(reqs)>(reqs));
+}
+//! \overload
+inline result<async_file_handle::awaitable<async_file_handle::buffers_type>> co_read(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list<async_file_handle::buffer_type> lst) noexcept
+{
+ return self.co_read(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(lst));
+}
+/*! \brief Schedule a write to occur asynchronously
+
+\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
+until the operation has completed, resuming with the buffers written, which
+may not be the buffers input. The size of each scatter-gather buffer is updated with
+the number of bytes of that buffer transferred.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\errors As for write(), plus ENOMEM.
+\mallocs One calloc, one free.
+*/
+inline result<async_file_handle::awaitable<async_file_handle::const_buffers_type>> co_write(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs) noexcept
+{
+ return self.co_write(std::forward<decltype(reqs)>(reqs));
+}
+//! \overload
+inline result<async_file_handle::awaitable<async_file_handle::const_buffers_type>> co_write(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list<async_file_handle::const_buffer_type> lst) noexcept
+{
+ return self.co_write(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(lst));
+}
+#endif
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/async_file_handle.ipp"
+#else
+#include "detail/impl/posix/async_file_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/config.hpp b/include/llfio/v2.0/config.hpp
new file mode 100644
index 00000000..c6f08c9d
--- /dev/null
+++ b/include/llfio/v2.0/config.hpp
@@ -0,0 +1,494 @@
+/* Configures AFIO
+(C) 2015-2018 Niall Douglas <http://www.nedproductions.biz/> (24 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_CONFIG_HPP
+#define AFIO_CONFIG_HPP
+
+//#include <iostream>
+//#define AFIO_LOG_TO_OSTREAM std::cerr
+//#define AFIO_LOGGING_LEVEL 6
+//#define AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+
+//! \file config.hpp Configures a compiler environment for AFIO header and source code
+
+//! \defgroup config Configuration macros
+
+#if !defined(AFIO_HEADERS_ONLY) && !defined(BOOST_ALL_DYN_LINK)
+//! \brief Whether AFIO is a headers only library. Defaults to 1 unless BOOST_ALL_DYN_LINK is defined. \ingroup config
+#define AFIO_HEADERS_ONLY 1
+#endif
+
+//! \def AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+//! \brief Define to not record the current handle's path in any failure info.
+#if DOXYGEN_IS_IN_THE_HOUSE
+#define AFIO_DISABLE_PATHS_IN_FAILURE_INFO not defined
+#endif
+
+#if !defined(AFIO_LOGGING_LEVEL)
+//! \brief How much detail to log. 0=disabled, 1=fatal, 2=error, 3=warn, 4=info, 5=debug, 6=all.
+//! Defaults to error level. \ingroup config
+#ifdef NDEBUG
+#define AFIO_LOGGING_LEVEL 1 // fatal
+#else
+#define AFIO_LOGGING_LEVEL 3 // warn
+#endif
+#endif
+
+#ifndef AFIO_LOG_TO_OSTREAM
+#if !defined(NDEBUG) && !defined(AFIO_DISABLE_LOG_TO_OSTREAM)
+#include <iostream> // for std::cerr
+//! \brief Any `ostream` to also log to. If `NDEBUG` is not defined, `std::cerr` is the default.
+#define AFIO_LOG_TO_OSTREAM std::cerr
+#endif
+#endif
+
+#if !defined(AFIO_LOG_BACKTRACE_LEVELS)
+//! \brief Bit mask of which log levels should be stack backtraced
+//! which will slow those logs thirty fold or so. Defaults to (1U<<1U)|(1U<<2U)|(1U<<3U) i.e. stack backtrace
+//! on fatal, error and warn logs. \ingroup config
+#define AFIO_LOG_BACKTRACE_LEVELS ((1U << 1U) | (1U << 2U) | (1U << 3U))
+#endif
+
+#if !defined(AFIO_LOGGING_MEMORY)
+#ifdef NDEBUG
+#define AFIO_LOGGING_MEMORY 4096
+#else
+//! \brief How much memory to use for the log.
+//! Defaults to 4Kb if NDEBUG defined, else 1Mb. \ingroup config
+#define AFIO_LOGGING_MEMORY (1024 * 1024)
+#endif
+#endif
+
+#if !defined(AFIO_EXPERIMENTAL_STATUS_CODE)
+//! \brief Whether to use SG14 experimental `status_code` instead of `std::error_code`
+#define AFIO_EXPERIMENTAL_STATUS_CODE 0
+#endif
+
+
+#if defined(_WIN32)
+#if !defined(_UNICODE)
+#error AFIO cannot target the ANSI Windows API. Please define _UNICODE to target the Unicode Windows API.
+#endif
+#if !defined(_WIN32_WINNT)
+#define _WIN32_WINNT 0x0600
+#elif _WIN32_WINNT < 0x0600
+#error _WIN32_WINNT must at least be set to Windows Vista for AFIO to work
+#endif
+#if defined(NTDDI_VERSION) && NTDDI_VERSION < 0x06000000
+#error NTDDI_VERSION must at least be set to Windows Vista for AFIO to work
+#endif
+#endif
+
+#ifdef __APPLE__
+#define AFIO_MISSING_PIOV 1
+#endif
+
+// Pull in detection of __MINGW64_VERSION_MAJOR
+#ifdef __MINGW32__
+#include <_mingw.h>
+#endif
+
+#include "quickcpplib/include/cpp_feature.h"
+
+#ifndef __cpp_exceptions
+#error AFIO needs C++ exceptions to be turned on
+#endif
+#ifndef __cpp_alias_templates
+#error AFIO needs template alias support in the compiler
+#endif
+#ifndef __cpp_variadic_templates
+#error AFIO needs variadic template support in the compiler
+#endif
+#if __cpp_constexpr < 201304L && !defined(_MSC_VER)
+#error AFIO needs relaxed constexpr (C++ 14) support in the compiler
+#endif
+#ifndef __cpp_init_captures
+#error AFIO needs lambda init captures support in the compiler (C++ 14)
+#endif
+#ifndef __cpp_attributes
+#error AFIO needs attributes support in the compiler
+#endif
+#ifndef __cpp_variable_templates
+#error AFIO needs variable template support in the compiler
+#endif
+#ifndef __cpp_generic_lambdas
+#error AFIO needs generic lambda support in the compiler
+#endif
+#ifdef __has_include
+// clang-format off
+#if !__has_include(<filesystem>) && !__has_include(<experimental/filesystem>)
+// clang-format on
+#error AFIO needs an implementation of the Filesystem TS in the standard library
+#endif
+#endif
+
+
+#include "quickcpplib/include/import.h"
+
+#if defined(AFIO_UNSTABLE_VERSION) && !defined(AFIO_DISABLE_ABI_PERMUTATION)
+#include "../revision.hpp"
+#define AFIO_V2 (QUICKCPPLIB_BIND_NAMESPACE_VERSION(afio_v2, AFIO_PREVIOUS_COMMIT_UNIQUE))
+#else
+#define AFIO_V2 (QUICKCPPLIB_BIND_NAMESPACE_VERSION(afio_v2))
+#endif
+/*! \def AFIO_V2
+\ingroup config
+\brief The namespace configuration of this AFIO v2. Consists of a sequence
+of bracketed tokens later fused by the preprocessor into namespace and C++ module names.
+*/
+#if DOXYGEN_IS_IN_THE_HOUSE
+//! The AFIO namespace
+namespace afio_v2_xxx
+{
+ //! Collection of file system based algorithms
+ namespace algorithm
+ {
+ }
+ //! YAML databaseable empirical testing of a storage's behaviour
+ namespace storage_profile
+ {
+ }
+ //! Utility routines often useful when using AFIO
+ namespace utils
+ {
+ }
+}
+/*! \brief The namespace of this AFIO v2 which will be some unknown inline
+namespace starting with `v2_` inside the `boost::afio` namespace.
+\ingroup config
+*/
+#define AFIO_V2_NAMESPACE afio_v2_xxx
+/*! \brief Expands into the appropriate namespace markup to enter the AFIO v2 namespace.
+\ingroup config
+*/
+#define AFIO_V2_NAMESPACE_BEGIN \
+ namespace afio_v2_xxx \
+ {
+/*! \brief Expands into the appropriate namespace markup to enter the C++ module
+exported AFIO v2 namespace.
+\ingroup config
+*/
+#define AFIO_V2_NAMESPACE_EXPORT_BEGIN \
+ export namespace afio_v2_xxx \
+ {
+/*! \brief Expands into the appropriate namespace markup to exit the AFIO v2 namespace.
+\ingroup config
+*/
+#define AFIO_V2_NAMESPACE_END }
+#elif defined(GENERATING_AFIO_MODULE_INTERFACE)
+#define AFIO_V2_NAMESPACE QUICKCPPLIB_BIND_NAMESPACE(AFIO_V2)
+#define AFIO_V2_NAMESPACE_BEGIN QUICKCPPLIB_BIND_NAMESPACE_BEGIN(AFIO_V2)
+#define AFIO_V2_NAMESPACE_EXPORT_BEGIN QUICKCPPLIB_BIND_NAMESPACE_EXPORT_BEGIN(AFIO_V2)
+#define AFIO_V2_NAMESPACE_END QUICKCPPLIB_BIND_NAMESPACE_END(AFIO_V2)
+#else
+#define AFIO_V2_NAMESPACE QUICKCPPLIB_BIND_NAMESPACE(AFIO_V2)
+#define AFIO_V2_NAMESPACE_BEGIN QUICKCPPLIB_BIND_NAMESPACE_BEGIN(AFIO_V2)
+#define AFIO_V2_NAMESPACE_EXPORT_BEGIN QUICKCPPLIB_BIND_NAMESPACE_BEGIN(AFIO_V2)
+#define AFIO_V2_NAMESPACE_END QUICKCPPLIB_BIND_NAMESPACE_END(AFIO_V2)
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+class handle;
+class file_handle;
+AFIO_V2_NAMESPACE_END
+
+// Bring in the Boost-lite macros
+#include "quickcpplib/include/config.hpp"
+#if AFIO_LOGGING_LEVEL
+#include "quickcpplib/include/ringbuffer_log.hpp"
+#include "quickcpplib/include/utils/thread.hpp"
+#endif
+// Bring in filesystem
+#if defined(__has_include)
+// clang-format off
+#if __has_include(<filesystem>) && __cplusplus >= 202000
+#include <filesystem>
+AFIO_V2_NAMESPACE_BEGIN
+namespace filesystem = std::filesystem;
+AFIO_V2_NAMESPACE_END
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+AFIO_V2_NAMESPACE_BEGIN
+namespace filesystem = std::experimental::filesystem;
+AFIO_V2_NAMESPACE_END
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include <experimental/filesystem>
+AFIO_V2_NAMESPACE_BEGIN
+namespace filesystem = std::experimental::filesystem;
+AFIO_V2_NAMESPACE_END
+// clang-format on
+#elif defined(_MSC_VER)
+#include <filesystem>
+AFIO_V2_NAMESPACE_BEGIN
+namespace filesystem = std::experimental::filesystem;
+AFIO_V2_NAMESPACE_END
+#else
+#error No <filesystem> implementation found
+#endif
+AFIO_V2_NAMESPACE_BEGIN
+struct path_hasher
+{
+ size_t operator()(const filesystem::path &p) const { return std::hash<filesystem::path::string_type>()(p.native()); }
+};
+AFIO_V2_NAMESPACE_END
+#include <ctime> // for struct timespec
+
+
+// Configure AFIO_DECL
+#if(defined(AFIO_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && !defined(AFIO_STATIC_LINK)
+
+#if defined(AFIO_SOURCE)
+#define AFIO_DECL QUICKCPPLIB_SYMBOL_EXPORT
+#define AFIO_BUILD_DLL
+#else
+#define AFIO_DECL QUICKCPPLIB_SYMBOL_IMPORT
+#endif
+#else
+#define AFIO_DECL
+#endif // building a shared library
+
+// Configure AFIO_THREAD_LOCAL
+#ifndef AFIO_THREAD_LOCAL_IS_CXX11
+#define AFIO_THREAD_LOCAL_IS_CXX11 QUICKCPPLIB_THREAD_LOCAL_IS_CXX11
+#endif
+#ifndef AFIO_THREAD_LOCAL
+#define AFIO_THREAD_LOCAL QUICKCPPLIB_THREAD_LOCAL
+#endif
+#ifndef AFIO_TEMPLATE
+#define AFIO_TEMPLATE(...) QUICKCPPLIB_TEMPLATE(__VA_ARGS__)
+#endif
+#ifndef AFIO_TREQUIRES
+#define AFIO_TREQUIRES(...) QUICKCPPLIB_TREQUIRES(__VA_ARGS__)
+#endif
+#ifndef AFIO_TEXPR
+#define AFIO_TEXPR(...) QUICKCPPLIB_TEXPR(__VA_ARGS__)
+#endif
+#ifndef AFIO_TPRED
+#define AFIO_TPRED(...) QUICKCPPLIB_TPRED(__VA_ARGS__)
+#endif
+#ifndef AFIO_REQUIRES
+#define AFIO_REQUIRES(...) QUICKCPPLIB_REQUIRES(__VA_ARGS__)
+#endif
+
+// A unique identifier generating macro
+#define AFIO_GLUE2(x, y) x##y
+#define AFIO_GLUE(x, y) AFIO_GLUE2(x, y)
+#define AFIO_UNIQUE_NAME AFIO_GLUE(__t, __COUNTER__)
+
+// Used to tag functions which need to be made free by the AST tool
+#ifndef AFIO_MAKE_FREE_FUNCTION
+#if 0 //__cplusplus >= 201700 // makes annoying warnings
+#define AFIO_MAKE_FREE_FUNCTION [[afio::make_free_function]]
+#else
+#define AFIO_MAKE_FREE_FUNCTION
+#endif
+#endif
+
+// Bring in bitfields
+#include "quickcpplib/include/bitfield.hpp"
+// Bring in scoped undo
+#include "quickcpplib/include/scoped_undo.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+using QUICKCPPLIB_NAMESPACE::scoped_undo::undoer;
+AFIO_V2_NAMESPACE_END
+// Bring in a span implementation
+#include "quickcpplib/include/span.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+using namespace QUICKCPPLIB_NAMESPACE::span;
+AFIO_V2_NAMESPACE_END
+// Bring in an optional implementation
+#include "quickcpplib/include/optional.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+using namespace QUICKCPPLIB_NAMESPACE::optional;
+AFIO_V2_NAMESPACE_END
+// Bring in a byte implementation
+#include "quickcpplib/include/byte.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+using QUICKCPPLIB_NAMESPACE::byte::byte;
+using QUICKCPPLIB_NAMESPACE::byte::to_byte;
+AFIO_V2_NAMESPACE_END
+// Bring in a string_view implementation
+#include "quickcpplib/include/string_view.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+using namespace QUICKCPPLIB_NAMESPACE::string_view;
+AFIO_V2_NAMESPACE_END
+// Bring in a persistent implementation
+#include "quickcpplib/include/persistent.hpp"
+AFIO_V2_NAMESPACE_BEGIN
+using namespace QUICKCPPLIB_NAMESPACE::persistence;
+AFIO_V2_NAMESPACE_END
+
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace detail
+{
+ // Used to cast an unknown input to some unsigned integer
+ AFIO_TEMPLATE(class T, class U)
+ AFIO_TREQUIRES(AFIO_TPRED(std::is_unsigned<T>::value && !std::is_same<std::decay_t<U>, std::nullptr_t>::value))
+ inline T unsigned_integer_cast(U &&v) { return static_cast<T>(v); }
+ AFIO_TEMPLATE(class T)
+ AFIO_TREQUIRES(AFIO_TPRED(std::is_unsigned<T>::value))
+ inline T unsigned_integer_cast(std::nullptr_t /* unused */) { return static_cast<T>(0); }
+ AFIO_TEMPLATE(class T, class U)
+ AFIO_TREQUIRES(AFIO_TPRED(std::is_unsigned<T>::value))
+ inline T unsigned_integer_cast(U *v) { return static_cast<T>(reinterpret_cast<uintptr_t>(v)); }
+} // namespace detail
+
+AFIO_V2_NAMESPACE_END
+
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace detail
+{
+ // A move only capable lightweight std::function, as std::function can't handle move only callables
+ template <class F> class function_ptr;
+ template <class R, class... Args> class function_ptr<R(Args...)>
+ {
+ struct function_ptr_storage
+ {
+ function_ptr_storage() = default;
+ function_ptr_storage(const function_ptr_storage &) = delete;
+ function_ptr_storage(function_ptr_storage &&) = delete;
+ function_ptr_storage &operator=(const function_ptr_storage &) = delete;
+ function_ptr_storage &operator=(function_ptr_storage &&) = delete;
+ virtual ~function_ptr_storage() = default;
+ virtual R operator()(Args &&... args) = 0;
+ };
+ template <class U> struct function_ptr_storage_impl : public function_ptr_storage
+ {
+ U c;
+ template <class... Args2>
+ constexpr explicit function_ptr_storage_impl(Args2 &&... args)
+ : c(std::forward<Args2>(args)...)
+ {
+ }
+ R operator()(Args &&... args) final { return c(std::move(args)...); }
+ };
+ function_ptr_storage *ptr;
+ template <class U> struct emplace_t
+ {
+ };
+ template <class U, class V> friend inline function_ptr<U> make_function_ptr(V &&f); // NOLINT
+ template <class U>
+ explicit function_ptr(std::nullptr_t, U &&f)
+ : ptr(new function_ptr_storage_impl<typename std::decay<U>::type>(std::forward<U>(f)))
+ {
+ }
+ template <class R_, class U, class... Args2> friend inline function_ptr<R_> emplace_function_ptr(Args2 &&... args); // NOLINT
+ template <class U, class... Args2>
+ explicit function_ptr(emplace_t<U> /*unused*/, Args2 &&... args)
+ : ptr(new function_ptr_storage_impl<U>(std::forward<Args2>(args)...))
+ {
+ }
+
+ public:
+ constexpr function_ptr() noexcept : ptr(nullptr) {}
+ constexpr explicit function_ptr(function_ptr_storage *p) noexcept : ptr(p) {}
+ constexpr function_ptr(function_ptr &&o) noexcept : ptr(o.ptr) { o.ptr = nullptr; }
+ function_ptr &operator=(function_ptr &&o) noexcept
+ {
+ delete ptr;
+ ptr = o.ptr;
+ o.ptr = nullptr;
+ return *this;
+ }
+ function_ptr(const function_ptr &) = delete;
+ function_ptr &operator=(const function_ptr &) = delete;
+ ~function_ptr() { delete ptr; }
+ explicit constexpr operator bool() const noexcept { return !!ptr; }
+ constexpr R operator()(Args... args) const { return (*ptr)(std::move(args)...); }
+ constexpr function_ptr_storage *get() noexcept { return ptr; }
+ constexpr void reset(function_ptr_storage *p = nullptr) noexcept
+ {
+ delete ptr;
+ ptr = p;
+ }
+ constexpr function_ptr_storage *release() noexcept
+ {
+ auto p = ptr;
+ ptr = nullptr;
+ return p;
+ }
+ };
+ template <class R, class U> inline function_ptr<R> make_function_ptr(U &&f) { return function_ptr<R>(nullptr, std::forward<U>(f)); }
+ template <class R, class U, class... Args> inline function_ptr<R> emplace_function_ptr(Args &&... args) { return function_ptr<R>(typename function_ptr<R>::template emplace_t<U>(), std::forward<Args>(args)...); }
+} // namespace detail
+
+// Native handle support
+namespace win
+{
+ using handle = void *;
+ using dword = unsigned long;
+}
+
+AFIO_V2_NAMESPACE_END
+
+
+#if 0
+///////////////////////////////////////////////////////////////////////////////
+// Auto library naming
+#if !defined(AFIO_SOURCE) && !defined(BOOST_ALL_NO_LIB) && !defined(AFIO_NO_LIB) && !AFIO_STANDALONE && !AFIO_HEADERS_ONLY
+
+#define BOOST_LIB_NAME boost_afio
+
+// tell the auto-link code to select a dll when required:
+#if defined(BOOST_ALL_DYN_LINK) || defined(AFIO_DYN_LINK)
+#define BOOST_DYN_LINK
+#endif
+
+#include <boost/config/auto_link.hpp>
+
+#endif // auto-linking disabled
+#endif
+
+//#define BOOST_THREAD_VERSION 4
+//#define BOOST_THREAD_PROVIDES_VARIADIC_THREAD
+//#define BOOST_THREAD_DONT_PROVIDE_FUTURE
+//#define BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK
+#if AFIO_HEADERS_ONLY == 1 && !defined(AFIO_SOURCE)
+/*! \brief Expands into the appropriate markup to declare an `extern`
+function exported from the AFIO DLL if not building headers only.
+\ingroup config
+*/
+#define AFIO_HEADERS_ONLY_FUNC_SPEC inline
+/*! \brief Expands into the appropriate markup to declare a class member
+function exported from the AFIO DLL if not building headers only.
+\ingroup config
+*/
+#define AFIO_HEADERS_ONLY_MEMFUNC_SPEC inline
+/*! \brief Expands into the appropriate markup to declare a virtual class member
+function exported from the AFIO DLL if not building headers only.
+\ingroup config
+*/
+#define AFIO_HEADERS_ONLY_VIRTUAL_SPEC inline virtual
+#else
+#define AFIO_HEADERS_ONLY_FUNC_SPEC extern AFIO_DECL
+#define AFIO_HEADERS_ONLY_MEMFUNC_SPEC
+#define AFIO_HEADERS_ONLY_VIRTUAL_SPEC virtual
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/deadline.h b/include/llfio/v2.0/deadline.h
new file mode 100644
index 00000000..e4629843
--- /dev/null
+++ b/include/llfio/v2.0/deadline.h
@@ -0,0 +1,112 @@
+/* Specifies a time deadline
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (4 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_DEADLINE_H
+#define AFIO_DEADLINE_H
+
+#include <stdbool.h> // NOLINT
+#include <time.h> // NOLINT
+
+//! \file deadline.h Provides struct deadline
+
+#if defined(__cplusplus) || DOXYGEN_IS_IN_THE_HOUSE
+#ifndef AFIO_CONFIG_HPP
+#error You must include the master afio.hpp, not individual header files directly
+#endif
+#include "config.hpp"
+#include <stdexcept>
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+#define AFIO_DEADLINE_NAME deadline
+#else
+#define AFIO_DEADLINE_NAME boost_afio_deadline
+#endif
+
+/*! \struct deadline
+\brief A time deadline in either relative-to-now or absolute (system clock) terms
+*/
+struct AFIO_DEADLINE_NAME
+{
+ //! True if deadline does not change with system clock changes
+ bool steady; // NOLINT
+ union {
+ //! System time from timespec_get(&ts, TIME_UTC)
+ struct timespec utc; // NOLINT
+ //! Nanosecond ticks from start of operation
+ unsigned long long nsecs; // NOLINT
+ };
+#ifdef __cplusplus
+ constexpr deadline() // NOLINT
+ : steady(false),
+ utc{0, 0}
+ {
+ }
+ //! True if deadline is valid
+ constexpr explicit operator bool() const noexcept { return steady || utc.tv_sec != 0; }
+ //! Implicitly construct a deadline from a system clock time point
+ deadline(std::chrono::system_clock::time_point tp) // NOLINT
+ : steady(false),
+ utc{0, 0}
+ {
+ std::chrono::seconds secs(std::chrono::system_clock::to_time_t(tp));
+ utc.tv_sec = secs.count();
+ std::chrono::system_clock::time_point _tp(std::chrono::system_clock::from_time_t(utc.tv_sec));
+ utc.tv_nsec = static_cast<long>(std::chrono::duration_cast<std::chrono::nanoseconds>(tp - _tp).count());
+ }
+ //! Implicitly construct a deadline from a duration from now
+ template <class Rep, class Period>
+ constexpr deadline(std::chrono::duration<Rep, Period> d) // NOLINT
+ : steady(true),
+ nsecs(0)
+ {
+ std::chrono::nanoseconds _nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(d);
+ // Negative durations are zero duration
+ if(_nsecs.count() > 0)
+ {
+ nsecs = _nsecs.count();
+ }
+ else
+ {
+ nsecs = 0;
+ }
+ }
+ //! Returns a system_clock::time_point for this deadline
+ std::chrono::system_clock::time_point to_time_point() const
+ {
+ if(steady)
+ {
+ throw std::invalid_argument("Not a UTC deadline!"); // NOLINT
+ }
+ std::chrono::system_clock::time_point tp(std::chrono::system_clock::from_time_t(utc.tv_sec));
+ tp += std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::nanoseconds(utc.tv_nsec));
+ return tp;
+ }
+#endif
+};
+
+#undef AFIO_DEADLINE_NAME
+#if defined(__cplusplus) || DOXYGEN_IS_IN_THE_HOUSE
+AFIO_V2_NAMESPACE_END
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/detail/impl/cached_parent_handle_adapter.ipp b/include/llfio/v2.0/detail/impl/cached_parent_handle_adapter.ipp
new file mode 100644
index 00000000..bb2d0fbe
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/cached_parent_handle_adapter.ipp
@@ -0,0 +1,151 @@
+/* A parent handle caching adapter
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../algorithm/cached_parent_handle_adapter.hpp"
+
+#include <mutex>
+#include <unordered_map>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace detail
+ {
+ struct cached_path_handle_map_
+ {
+ std::mutex lock;
+ size_t gc_count{0};
+ std::unordered_map<filesystem::path, std::weak_ptr<cached_path_handle>, path_hasher> by_path;
+ };
+ inline cached_path_handle_map_ &cached_path_handle_map()
+ {
+ static cached_path_handle_map_ map;
+ return map;
+ }
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<filesystem::path> cached_path_handle::current_path(const filesystem::path &append) noexcept
+ {
+ try
+ {
+ auto ret = h.current_path();
+ auto &map = cached_path_handle_map();
+ std::lock_guard<std::mutex> g(map.lock);
+ if(!ret)
+ {
+ std::string msg("cached_path_handle::current_path() failed to retrieve current path of cached handle due to ");
+ msg.append(ret.error().message().c_str());
+ AFIO_LOG_WARN(nullptr, msg.c_str());
+ }
+ else if(!ret.value().empty() && ret.value() != _lastpath)
+ {
+ map.by_path.erase(_lastpath);
+ _lastpath = std::move(ret).value();
+ map.by_path.emplace(_lastpath, shared_from_this());
+ }
+ return _lastpath / append;
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ AFIO_HEADERS_ONLY_FUNC_SPEC std::pair<cached_path_handle_ptr, filesystem::path> get_cached_path_handle(const path_handle &base, path_view path)
+ {
+ path_view leaf(path.filename());
+ path.remove_filename();
+ filesystem::path dirpath;
+ if(base.is_valid())
+ {
+ dirpath = base.current_path().value() / path.path();
+ }
+ else
+ {
+ dirpath = path.path();
+ if(path.empty())
+ {
+ dirpath = filesystem::current_path();
+ }
+#ifdef _WIN32
+ // On Windows, only use the kernel path form
+ dirpath = path_handle::path(dirpath).value().current_path().value();
+#endif
+ if(path.empty())
+ {
+ path = dirpath;
+ }
+ }
+ auto &map = cached_path_handle_map();
+ std::lock_guard<std::mutex> g(map.lock);
+ auto rungc = undoer([&map] {
+ if(map.gc_count++ >= 1024)
+ {
+ for(auto it = map.by_path.begin(); it != map.by_path.end();)
+ {
+ if(it->second.expired())
+ {
+ it = map.by_path.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+ map.gc_count = 0;
+ }
+ });
+ (void) rungc;
+ auto it = map.by_path.find(dirpath);
+ if(it != map.by_path.end())
+ {
+ cached_path_handle_ptr ret = it->second.lock();
+ if(ret)
+ {
+ return {ret, leaf.path()};
+ }
+ }
+ cached_path_handle_ptr ret = std::make_shared<cached_path_handle>(directory_handle::directory(base, path).value());
+ auto _currentpath = ret->h.current_path();
+ if(_currentpath)
+ {
+ ret->_lastpath = std::move(_currentpath).value();
+ }
+ else
+ {
+ ret->_lastpath = std::move(dirpath);
+ }
+ it = map.by_path.find(ret->_lastpath);
+ if(it != map.by_path.end())
+ {
+ it->second = ret;
+ }
+ else
+ {
+ map.by_path.emplace(ret->_lastpath, ret);
+ }
+ return {ret, leaf.path()};
+ }
+ } // namespace detail
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/path_discovery.ipp b/include/llfio/v2.0/detail/impl/path_discovery.ipp
new file mode 100644
index 00000000..1fd3588f
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/path_discovery.ipp
@@ -0,0 +1,262 @@
+/* Discovery of various useful filesystem paths
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../path_discovery.hpp"
+
+#include "../../directory_handle.hpp"
+#include "../../statfs.hpp"
+
+#include <mutex>
+#include <regex>
+#include <vector>
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace path_discovery
+{
+ struct _store
+ {
+ std::mutex lock;
+ std::vector<discovered_path> all;
+ span<discovered_path> verified;
+ struct _discovered_path
+ {
+ filesystem::path path;
+ size_t priority{0};
+ std::string fstypename;
+ directory_handle h; // not retained after verification
+ explicit _discovered_path(filesystem::path _path)
+ : path(std::move(_path))
+ {
+ }
+ };
+ std::vector<_discovered_path> _all;
+ directory_handle storage_backed, memory_backed;
+ };
+ inline _store &path_store()
+ {
+ static _store s;
+ return s;
+ }
+
+ inline std::vector<std::pair<discovered_path::source_type, _store::_discovered_path>> _all_temporary_directories();
+
+ span<discovered_path> all_temporary_directories(bool refresh) noexcept
+ {
+ auto &ps = path_store();
+ if(!refresh && !ps.all.empty())
+ {
+ return ps.all;
+ }
+ std::lock_guard<std::mutex> g(ps.lock);
+ if(refresh)
+ {
+ ps.all.clear();
+ ps._all.clear();
+ ps.verified = {};
+ ps.storage_backed = {};
+ ps.memory_backed = {};
+ }
+ if(!ps.all.empty())
+ {
+ return ps.all;
+ }
+ try
+ {
+ std::vector<std::pair<discovered_path::source_type, _store::_discovered_path>> raw = _all_temporary_directories();
+ if(raw.empty())
+ {
+ AFIO_LOG_FATAL(nullptr, "path_discovery::all_temporary_directories() sees no possible temporary directories, something has gone very wrong");
+ abort();
+ }
+ for(size_t n = 0; n < raw.size(); n++)
+ {
+ raw[n].second.priority = n;
+ }
+ // Firstly sort by source and path so duplicates are side by side
+ std::sort(raw.begin(), raw.end(), [](const auto &a, const auto &b) { return (a.first < b.first) || (a.first == b.first && a.second.path < b.second.path); });
+ // Remove duplicates
+ raw.erase(std::unique(raw.begin(), raw.end(), [](const auto &a, const auto &b) { return a.first == b.first && a.second.path == b.second.path; }), raw.end());
+ // Put them back into the order in which they were returned
+ std::sort(raw.begin(), raw.end(), [](const auto &a, const auto &b) { return a.second.priority < b.second.priority; });
+ ps.all.reserve(raw.size());
+ ps._all.reserve(raw.size());
+ for(auto &i : raw)
+ {
+ ps._all.push_back(std::move(i.second));
+ discovered_path dp;
+ dp.path = ps._all.back().path;
+ dp.source = i.first;
+ ps.all.push_back(std::move(dp));
+ }
+ }
+ catch(const std::exception &e)
+ {
+ std::string msg("path_discovery::all_temporary_directories() saw exception thrown: ");
+ msg.append(e.what());
+ AFIO_LOG_WARN(nullptr, msg.c_str());
+ }
+ catch(...)
+ {
+ AFIO_LOG_WARN(nullptr, "path_discovery::all_temporary_directories() saw unknown exception throw");
+ }
+ return ps.all;
+ }
+
+ span<discovered_path> verified_temporary_directories() noexcept
+ {
+ auto &ps = path_store();
+ if(!ps.verified.empty())
+ {
+ return ps.verified;
+ }
+ (void) all_temporary_directories();
+ std::lock_guard<std::mutex> g(ps.lock);
+ if(!ps.verified.empty())
+ {
+ return ps.verified;
+ }
+ try
+ {
+ // Firstly go try to open and stat all items
+ for(size_t n = 0; n < ps.all.size(); n++)
+ {
+ {
+ log_level_guard logg(log_level::fatal); // suppress log printing of failure
+ (void) logg;
+ auto _h = directory_handle::directory({}, ps.all[n].path);
+ if(!_h)
+ {
+ // Error during opening
+ continue;
+ }
+ ps._all[n].h = std::move(_h).value();
+ }
+ // Try to create a small file in that directory
+ auto _fh = file_handle::random_file(ps._all[n].h, file_handle::mode::write, file_handle::caching::temporary, file_handle::flag::unlink_on_first_close);
+ if(!_fh)
+ {
+#if AFIO_LOGGING_LEVEL >= 3
+ std::string msg("path_discovery::verified_temporary_directories() failed to create a file in ");
+ msg.append(ps._all[n].path.u8string());
+ msg.append(" due to ");
+ msg.append(_fh.error().message().c_str());
+ AFIO_LOG_WARN(nullptr, msg.c_str());
+#endif
+ ps._all[n].h = {};
+ continue;
+ }
+ ps.all[n].stat = stat_t(nullptr);
+ if(!ps.all[n].stat->fill(ps._all[n].h))
+ {
+ AFIO_LOG_WARN(nullptr, "path_discovery::verified_temporary_directories() failed to stat an open handle to a temp directory");
+ ps.all[n].stat = {};
+ ps._all[n].h = {};
+ continue;
+ }
+ statfs_t statfs;
+ auto statfsres = statfs.fill(_fh.value(), statfs_t::want::fstypename);
+ if(statfsres)
+ {
+ ps._all[n].fstypename = std::move(statfs.f_fstypename);
+ }
+ else
+ {
+#if AFIO_LOGGING_LEVEL >= 3
+ std::string msg("path_discovery::verified_temporary_directories() failed to statfs the temp directory ");
+ msg.append(ps._all[n].path.u8string());
+ msg.append(" due to ");
+ msg.append(statfsres.error().message().c_str());
+ AFIO_LOG_WARN(nullptr, msg.c_str());
+#endif
+ ps.all[n].stat = {};
+ ps._all[n].h = {};
+ continue;
+ }
+ }
+ // Now partition into those with valid stat directories and those without
+ std::stable_partition(ps._all.begin(), ps._all.end(), [](const _store::_discovered_path &a) { return a.h.is_valid(); });
+ auto it = std::stable_partition(ps.all.begin(), ps.all.end(), [](const discovered_path &a) { return a.stat; });
+ ps.verified = span<discovered_path>(ps.all.data(), it - ps.all.begin());
+ if(ps.verified.empty())
+ {
+ AFIO_LOG_FATAL(nullptr, "path_discovery::verified_temporary_directories() could not find at least one writable temporary directory");
+ abort();
+ }
+
+ // Finally, need to choose storage and memory backed directories
+ std::regex storage_backed_regex("btrfs|cifs|exfat|ext?|f2fs|hfs|jfs|lxfs|nfs|nilf2|ufs|vfat|xfs|zfs|msdosfs|newnfs|ntfs|smbfs|unionfs|fat|fat32", std::regex::icase);
+ std::regex memory_backed_regex("tmpfs|ramfs", std::regex::icase);
+ for(size_t n = 0; n < ps.verified.size(); n++)
+ {
+ if(!ps.storage_backed.is_valid() && std::regex_match(ps._all[n].fstypename, storage_backed_regex))
+ {
+ ps.storage_backed = std::move(ps._all[n].h);
+ }
+ if(!ps.memory_backed.is_valid() && std::regex_match(ps._all[n].fstypename, memory_backed_regex))
+ {
+ ps.memory_backed = std::move(ps._all[n].h);
+ }
+ ps.all[n].path = ps._all[n].path;
+ (void) ps._all[n].h.close();
+ }
+ }
+ catch(const std::exception &e)
+ {
+ std::string msg("path_discovery::verified_temporary_directories() saw exception thrown: ");
+ msg.append(e.what());
+ AFIO_LOG_FATAL(nullptr, msg.c_str());
+ abort();
+ }
+ catch(...)
+ {
+ AFIO_LOG_FATAL(nullptr, "path_discovery::verified_temporary_directories() saw unknown exception throw");
+ abort();
+ }
+ return ps.verified;
+ }
+
+ const path_handle &storage_backed_temporary_files_directory() noexcept
+ {
+ (void) verified_temporary_directories();
+ auto &ps = path_store();
+ return ps.storage_backed;
+ }
+ const path_handle &memory_backed_temporary_files_directory() noexcept
+ {
+ (void) verified_temporary_directories();
+ auto &ps = path_store();
+ return ps.memory_backed;
+ }
+} // namespace path_discovery
+
+AFIO_V2_NAMESPACE_END
+
+#define AFIO_PATH_DISCOVERY_INCLUDING
+#ifdef _WIN32
+#include "windows/path_discovery.ipp"
+#else
+#include "posix/path_discovery.ipp"
+#endif
+#undef AFIO_PATH_DISCOVERY_INCLUDING
diff --git a/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp
new file mode 100644
index 00000000..59f10eae
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp
@@ -0,0 +1,381 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+
+#include <fcntl.h>
+#include <unistd.h>
+#if AFIO_USE_POSIX_AIO
+#include <aio.h>
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_handle::barrier(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, bool wait_for_device, bool and_metadata, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ optional<io_result<const_buffers_type>> ret;
+ OUTCOME_TRY(io_state, async_barrier(reqs, [&ret](async_file_handle *, io_result<const_buffers_type> &result) { ret = result; }, wait_for_device, and_metadata));
+ (void) io_state;
+
+ // While i/o is not done pump i/o completion
+ while(!ret)
+ {
+ auto t(_service->run_until(d));
+ // If i/o service pump failed or timed out, cancel outstanding i/o and return
+ if(!t)
+ {
+ return t.error();
+ }
+#ifndef NDEBUG
+ if(!ret && t && !t.value())
+ {
+ AFIO_LOG_FATAL(_v.fd, "async_file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ return *ret;
+}
+
+template <class BuffersType, class IORoutine> result<async_file_handle::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, async_file_handle::io_request<BuffersType> reqs, async_file_handle::_erased_completion_handler &&completion, IORoutine && /*unused*/) noexcept
+{
+ // Need to keep a set of aiocbs matching the scatter-gather buffers
+ struct state_type : public _erased_io_state_type
+ {
+#if AFIO_USE_POSIX_AIO
+ struct aiocb aiocbs[1]{};
+#else
+#error todo
+#endif
+ _erased_completion_handler *completion;
+ state_type() = delete;
+ state_type(state_type &&) = delete;
+ state_type(const state_type &) = delete;
+ state_type &operator=(state_type &&) = delete;
+ state_type &operator=(const state_type &) = delete;
+ state_type(async_file_handle *_parent, operation_t _operation, bool must_deallocate_self, size_t _items)
+ : _erased_io_state_type(_parent, _operation, must_deallocate_self, _items)
+ , completion(nullptr)
+ {
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC _erased_completion_handler *erased_completion_handler() noexcept final { return completion; }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept final
+ {
+#if AFIO_USE_POSIX_AIO
+ auto **_paiocb = static_cast<struct aiocb **>(internal_state);
+ struct aiocb *aiocb = *_paiocb;
+ assert(aiocb >= aiocbs && aiocb < aiocbs + this->items);
+ *_paiocb = nullptr;
+#else
+#error todo
+#endif
+ auto &result = this->result.write;
+ if(result)
+ {
+ if(errcode)
+ {
+ result = posix_error(static_cast<int>(errcode));
+ }
+ else
+ {
+// Figure out which i/o I am and update the buffer in question
+#if AFIO_USE_POSIX_AIO
+ size_t idx = aiocb - aiocbs;
+#else
+#error todo
+#endif
+ if(idx >= this->items)
+ {
+ AFIO_LOG_FATAL(0, "file_handle::io_state::operator() called with invalid index");
+ std::terminate();
+ }
+ result.value()[idx].len = bytes_transferred;
+ }
+ }
+ this->parent->service()->_work_done();
+ // Are we done?
+ if(!--this->items_to_go)
+ {
+ (*completion)(this);
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~state_type() final
+ {
+ // Do we need to cancel pending i/o?
+ if(this->items_to_go)
+ {
+ for(size_t n = 0; n < this->items; n++)
+ {
+#if AFIO_USE_POSIX_AIO
+ int ret = aio_cancel(this->parent->native_handle().fd, aiocbs + n);
+ (void) ret;
+#if 0
+ if(ret<0 || ret==AIO_NOTCANCELED)
+ {
+ std::cout << "Failed to cancel " << (aiocbs+n) << std::endl;
+ }
+ else if(ret==AIO_CANCELED)
+ {
+ std::cout << "Cancelled " << (aiocbs+n) << std::endl;
+ }
+ else if(ret==AIO_ALLDONE)
+ {
+ std::cout << "Already done " << (aiocbs+n) << std::endl;
+ }
+#endif
+#else
+#error todo
+#endif
+ }
+ // Pump the i/o service until all pending i/o is completed
+ while(this->items_to_go)
+ {
+ auto res = this->parent->service()->run();
+ (void) res;
+#ifndef NDEBUG
+ if(res.has_error())
+ {
+ AFIO_LOG_FATAL(0, "file_handle: io_service failed");
+ std::terminate();
+ }
+ if(!res.value())
+ {
+ AFIO_LOG_FATAL(0, "file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ }
+ completion->~_erased_completion_handler();
+ }
+ } * state;
+ extent_type offset = reqs.offset;
+ size_t statelen = sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(struct aiocb) + completion.bytes();
+ if(!mem.empty() && statelen > mem.size())
+ {
+ return errc::not_enough_memory;
+ }
+ size_t items(reqs.buffers.size());
+#if AFIO_USE_POSIX_AIO && defined(AIO_LISTIO_MAX)
+ // If this i/o could never be done atomically, reject
+ if(items > AIO_LISTIO_MAX)
+ return errc::invalid_argument;
+#if defined(__FreeBSD__) || defined(__APPLE__)
+ if(!service()->using_kqueues())
+ {
+ // BSD and Apple put a tight limit on how many entries
+ // aio_suspend() will take in order to have reasonable
+ // performance. But their documentation lies, if you
+ // feed more than AIO_LISTIO_MAX items to aio_suspend
+ // it does NOT return EINVAL as specified, but rather
+ // simply marks all items past AIO_LISTIO_MAX as failed
+ // with EAGAIN. That punishes performance for AFIO
+ // because we loop setting up and tearing down
+ // the handlers, so if we would overload afio_suspend,
+ // better to error out now rather that later in io_service.
+ if(service()->_aiocbsv.size() + items > AIO_LISTIO_MAX)
+ {
+ return errc::resource_unavailable_try_again;
+ }
+ }
+#endif
+#endif
+ bool must_deallocate_self = false;
+ if(mem.empty())
+ {
+ void *_mem = ::calloc(1, statelen); // NOLINT
+ if(!_mem)
+ {
+ return errc::not_enough_memory;
+ }
+ mem = {static_cast<char *>(_mem), statelen};
+ must_deallocate_self = true;
+ }
+ io_state_ptr _state(reinterpret_cast<state_type *>(mem.data()));
+ new((state = reinterpret_cast<state_type *>(mem.data()))) state_type(this, operation, must_deallocate_self, items);
+ state->completion = reinterpret_cast<_erased_completion_handler *>(reinterpret_cast<uintptr_t>(state) + sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(struct aiocb));
+ completion.move(state->completion);
+
+ // Noexcept move the buffers from req into result
+ BuffersType &out = state->result.write.value();
+ out = std::move(reqs.buffers);
+ for(size_t n = 0; n < items; n++)
+ {
+#if AFIO_USE_POSIX_AIO
+#ifndef NDEBUG
+ if(_v.requires_aligned_io())
+ {
+ assert((offset & 511) == 0);
+ assert(((uintptr_t) out[n].data & 511) == 0);
+ assert((out[n].len & 511) == 0);
+ }
+#endif
+ struct aiocb *aiocb = state->aiocbs + n;
+ aiocb->aio_fildes = _v.fd;
+ aiocb->aio_offset = offset;
+ aiocb->aio_buf = reinterpret_cast<void *>(const_cast<byte *>(out[n].data));
+ aiocb->aio_nbytes = out[n].len;
+ aiocb->aio_sigevent.sigev_notify = SIGEV_NONE;
+ aiocb->aio_sigevent.sigev_value.sival_ptr = reinterpret_cast<void *>(state);
+ switch(operation)
+ {
+ case operation_t::read:
+ aiocb->aio_lio_opcode = LIO_READ;
+ break;
+ case operation_t::write:
+ aiocb->aio_lio_opcode = LIO_WRITE;
+ break;
+ case operation_t::fsync_async:
+ case operation_t::fsync_sync:
+ aiocb->aio_lio_opcode = LIO_NOP;
+ break;
+ case operation_t::dsync_async:
+ case operation_t::dsync_sync:
+ aiocb->aio_lio_opcode = LIO_NOP;
+ break;
+ }
+#else
+#error todo
+#endif
+ offset += out[n].len;
+ ++state->items_to_go;
+ }
+ int ret = 0;
+#if AFIO_USE_POSIX_AIO
+ if(service()->using_kqueues())
+ {
+#if AFIO_COMPILE_KQUEUES
+ // Only issue one kqueue event when entire scatter-gather has completed
+ struct _sigev = {0};
+#error todo
+#endif
+ }
+ else
+ {
+ // Add these i/o's to the quick aio_suspend list
+ service()->_aiocbsv.resize(service()->_aiocbsv.size() + items);
+ struct aiocb **thislist = service()->_aiocbsv.data() + service()->_aiocbsv.size() - items;
+ for(size_t n = 0; n < items; n++)
+ {
+ struct aiocb *aiocb = state->aiocbs + n;
+ thislist[n] = aiocb;
+ }
+ switch(operation)
+ {
+ case operation_t::read:
+ case operation_t::write:
+ ret = lio_listio(LIO_NOWAIT, thislist, items, nullptr);
+ break;
+ case operation_t::fsync_async:
+ case operation_t::fsync_sync:
+ case operation_t::dsync_async:
+ case operation_t::dsync_sync:
+ for(size_t n = 0; n < items; n++)
+ {
+ struct aiocb *aiocb = state->aiocbs + n;
+#if defined(__FreeBSD__) || defined(__APPLE__) // neither of these have fdatasync()
+ ret = aio_fsync(O_SYNC, aiocb);
+#else
+ ret = aio_fsync((operation == operation_t::dsync_async || operation == operation_t::dsync_sync) ? O_DSYNC : O_SYNC, aiocb);
+#endif
+ }
+ break;
+ }
+ }
+#else
+#error todo
+#endif
+ if(ret < 0)
+ {
+ service()->_aiocbsv.resize(service()->_aiocbsv.size() - items);
+ state->items_to_go = 0;
+ state->result.write = posix_error();
+ (*state->completion)(state);
+ return success(std::move(_state));
+ }
+ service()->_work_enqueued(items);
+ return success(std::move(_state));
+}
+
+result<async_file_handle::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, io_request<const_buffers_type> reqs, async_file_handle::_erased_completion_handler &&completion) noexcept
+{
+ return _begin_io(mem, operation, reqs, std::move(completion), nullptr);
+}
+
+async_file_handle::io_result<async_file_handle::buffers_type> async_file_handle::read(async_file_handle::io_request<async_file_handle::buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ optional<io_result<buffers_type>> ret;
+ OUTCOME_TRY(io_state, async_read(reqs, [&ret](async_file_handle *, io_result<buffers_type> &result) { ret = result; }));
+ (void) io_state;
+
+ // While i/o is not done pump i/o completion
+ while(!ret)
+ {
+ auto t(_service->run_until(d));
+ // If i/o service pump failed or timed out, cancel outstanding i/o and return
+ if(!t)
+ {
+ return t.error();
+ }
+#ifndef NDEBUG
+ if(!ret && t && !t.value())
+ {
+ AFIO_LOG_FATAL(_v.fd, "async_file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ return *ret;
+}
+
+async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_handle::write(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ optional<io_result<const_buffers_type>> ret;
+ OUTCOME_TRY(io_state, async_write(reqs, [&ret](async_file_handle *, io_result<const_buffers_type> &result) { ret = result; }));
+ (void) io_state;
+
+ // While i/o is not done pump i/o completion
+ while(!ret)
+ {
+ auto t(_service->run_until(d));
+ // If i/o service pump failed or timed out, cancel outstanding i/o and return
+ if(!t)
+ {
+ return t.error();
+ }
+#ifndef NDEBUG
+ if(!ret && t && !t.value())
+ {
+ AFIO_LOG_FATAL(_v.fd, "async_file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ return *ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp b/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp
new file mode 100644
index 00000000..b42ae784
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp
@@ -0,0 +1,424 @@
+/* A handle to a directory
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+(See accompanying file Licence.txt or copy at
+http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../directory_handle.hpp"
+#include "import.hpp"
+
+#ifdef QUICKCPPLIB_ENABLE_VALGRIND
+#include "../../../quickcpplib/valgrind/memcheck.h"
+#define AFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(a, b) VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE((a), (b))
+#else
+#define AFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(a, b)
+#endif
+
+#include <dirent.h> /* Defines DT_* constants */
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<directory_handle> directory_handle::directory(const path_handle &base, path_view_type path, mode _mode, creation _creation, caching _caching, flag flags) noexcept
+{
+ if(flags & flag::unlink_on_first_close)
+ {
+ return errc::invalid_argument;
+ }
+ result<directory_handle> ret(directory_handle(native_handle_type(), 0, 0, _caching, flags));
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::directory;
+ // POSIX does not permit directory opens with O_RDWR like Windows, so silently convert to read
+ if(_mode == mode::attr_write)
+ {
+ _mode = mode::attr_read;
+ }
+ else if(_mode == mode::write || _mode == mode::append)
+ {
+ _mode = mode::read;
+ }
+ // Also trying to truncate a directory returns EISDIR
+ if(_creation == creation::truncate)
+ {
+ return errc::is_a_directory;
+ }
+ OUTCOME_TRY(attribs, attribs_from_handle_mode_caching_and_flags(nativeh, _mode, _creation, _caching, flags));
+#ifdef O_DIRECTORY
+ attribs |= O_DIRECTORY;
+#endif
+#ifdef O_SEARCH
+ attribs |= O_SEARCH;
+#endif
+ if(base.is_valid() && path.empty())
+ {
+ // It can happen that we are passed a base fd and no leafname, in which case he
+ // really ought to be cloning the handle. But let's humour him.
+ path = ".";
+ }
+ path_view::c_str zpath(path);
+ if(base.is_valid())
+ {
+ if(_creation == creation::if_needed || _creation == creation::only_if_not_exist)
+ {
+ if(-1 == ::mkdirat(base.native_handle().fd, zpath.buffer, 0x1f8 /*770*/))
+ {
+ if(EEXIST != errno || _creation == creation::only_if_not_exist)
+ {
+ return posix_error();
+ }
+ }
+ attribs &= ~(O_CREAT | O_EXCL);
+ }
+ nativeh.fd = ::openat(base.native_handle().fd, zpath.buffer, attribs);
+ }
+ else
+ {
+ if(_creation == creation::if_needed || _creation == creation::only_if_not_exist)
+ {
+ if(-1 == ::mkdir(zpath.buffer, 0x1f8 /*770*/))
+ {
+ if(EEXIST != errno || _creation == creation::only_if_not_exist)
+ {
+ return posix_error();
+ }
+ }
+ attribs &= ~(O_CREAT | O_EXCL);
+ }
+ nativeh.fd = ::open(zpath.buffer, attribs);
+ }
+ if(-1 == nativeh.fd)
+ {
+ return posix_error();
+ }
+ if(!(flags & flag::disable_safety_unlinks))
+ {
+ if(!ret.value()._fetch_inode())
+ {
+ // If fetching inode failed e.g. were opening device, disable safety unlinks
+ ret.value()._flags &= ~flag::disable_safety_unlinks;
+ }
+ }
+ if(ret.value().are_safety_fsyncs_issued())
+ {
+ fsync(nativeh.fd);
+ }
+ return ret;
+}
+
+result<directory_handle> directory_handle::clone(mode mode_, caching caching_, deadline d) const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // Fast path
+ if(mode_ == mode::unchanged && caching_ == caching::unchanged)
+ {
+ result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags));
+ ret.value()._v.behaviour = _v.behaviour;
+ ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC);
+ if(-1 == ret.value()._v.fd)
+ {
+ return posix_error();
+ }
+ return ret;
+ }
+ // Slow path
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if(d.steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = d.to_time_point();
+ }
+ }
+ for(;;)
+ {
+ // Get the current path of myself
+ OUTCOME_TRY(currentpath, current_path());
+ // Open myself
+ auto fh = directory({}, currentpath, mode_, creation::open_existing, caching_, _flags);
+ if(fh)
+ {
+ if(fh.value().unique_id() == unique_id())
+ {
+ return fh;
+ }
+ }
+ else
+ {
+ if(fh.error() != errc::no_such_file_or_directory)
+ {
+ return fh.error();
+ }
+ }
+ // Check timeout
+ if(d)
+ {
+ if(d.steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ }
+}
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> directory_handle::clone_to_path_handle() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ result<path_handle> ret(path_handle(native_handle_type(), _caching, _flags));
+ ret.value()._v.behaviour = _v.behaviour;
+ ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC);
+ if(-1 == ret.value()._v.fd)
+ {
+ return posix_error();
+ }
+ return ret;
+}
+
+result<directory_handle::enumerate_info> directory_handle::enumerate(buffers_type &&tofill, path_view_type glob, filter /*unused*/, span<char> kernelbuffer) const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(tofill.empty())
+ {
+ return enumerate_info{std::move(tofill), stat_t::want::none, false};
+ }
+ // Is glob a single entry match? If so, this is really a stat call
+ path_view_type::c_str zglob(glob);
+ if(!glob.empty() && !glob.contains_glob())
+ {
+ struct stat s
+ {
+ };
+ if(-1 == ::fstatat(_v.fd, zglob.buffer, &s, AT_SYMLINK_NOFOLLOW))
+ {
+ return posix_error();
+ }
+ tofill[0].stat.st_dev = s.st_dev;
+ tofill[0].stat.st_ino = s.st_ino;
+ tofill[0].stat.st_type = to_st_type(s.st_mode);
+ tofill[0].stat.st_perms = s.st_mode & 0xfff;
+ tofill[0].stat.st_nlink = s.st_nlink;
+ tofill[0].stat.st_uid = s.st_uid;
+ tofill[0].stat.st_gid = s.st_gid;
+ tofill[0].stat.st_rdev = s.st_rdev;
+#ifdef __ANDROID__
+ tofill[0].stat.st_atim = to_timepoint(*((struct timespec *) &s.st_atime));
+ tofill[0].stat.st_mtim = to_timepoint(*((struct timespec *) &s.st_mtime));
+ tofill[0].stat.st_ctim = to_timepoint(*((struct timespec *) &s.st_ctime));
+#elif defined(__APPLE__)
+ tofill[0].stat.st_atim = to_timepoint(s.st_atimespec);
+ tofill[0].stat.st_mtim = to_timepoint(s.st_mtimespec);
+ tofill[0].stat.st_ctim = to_timepoint(s.st_ctimespec);
+#else // Linux and BSD
+ tofill[0].stat.st_atim = to_timepoint(s.st_atim);
+ tofill[0].stat.st_mtim = to_timepoint(s.st_mtim);
+ tofill[0].stat.st_ctim = to_timepoint(s.st_ctim);
+#endif
+ tofill[0].stat.st_size = s.st_size;
+ tofill[0].stat.st_allocated = static_cast<handle::extent_type>(s.st_blocks) * 512;
+ tofill[0].stat.st_blocks = s.st_blocks;
+ tofill[0].stat.st_blksize = s.st_blksize;
+#ifdef HAVE_STAT_FLAGS
+ tofill[0].stat.st_flags = s.st_flags;
+#endif
+#ifdef HAVE_STAT_GEN
+ tofill[0].stat.st_gen = s.st_gen;
+#endif
+#ifdef HAVE_BIRTHTIMESPEC
+#if defined(__APPLE__)
+ tofill[0].stat.st_birthtim = to_timepoint(s.st_birthtimespec);
+#else
+ tofill[0].stat.st_birthtim = to_timepoint(s.st_birthtim);
+#endif
+#endif
+ tofill[0].stat.st_sparse = static_cast<unsigned int>((static_cast<handle::extent_type>(s.st_blocks) * 512) < static_cast<handle::extent_type>(s.st_size));
+ tofill._resize(1);
+ static constexpr stat_t::want default_stat_contents = stat_t::want::dev | stat_t::want::ino | stat_t::want::type | stat_t::want::perms | stat_t::want::nlink | stat_t::want::uid | stat_t::want::gid | stat_t::want::rdev | stat_t::want::atim | stat_t::want::mtim | stat_t::want::ctim | stat_t::want::size |
+ stat_t::want::allocated | stat_t::want::blocks | stat_t::want::blksize
+#ifdef HAVE_STAT_FLAGS
+ | stat_t::want::flags
+#endif
+#ifdef HAVE_STAT_GEN
+ | stat_t::want::gen
+#endif
+#ifdef HAVE_BIRTHTIMESPEC
+ | stat_t::want::birthtim
+#endif
+ | stat_t::want::sparse;
+ return enumerate_info{std::move(tofill), default_stat_contents, true};
+ }
+#ifdef __linux__
+ // Unlike FreeBSD, Linux doesn't define a getdents() function, so we'll do that here.
+ using getdents64_t = int (*)(int, char *, unsigned int);
+ static auto getdents = static_cast<getdents64_t>([](int fd, char *buf, unsigned count) -> int { return syscall(SYS_getdents64, fd, buf, count); });
+ using dirent = dirent64;
+#endif
+#ifdef __APPLE__
+ // OS X defines a getdirentries64() kernel syscall which can emulate getdents
+ typedef int (*getdents_emulation_t)(int, char *, unsigned);
+ static getdents_emulation_t getdents = static_cast<getdents_emulation_t>([](int fd, char *buf, unsigned count) -> int {
+ off_t foo;
+ return syscall(SYS_getdirentries64, fd, buf, count, &foo);
+ });
+#endif
+ if(!tofill._kernel_buffer && kernelbuffer.empty())
+ {
+ // Let's assume the average leafname will be 64 characters long.
+ size_t toallocate = (sizeof(dirent) + 64) * tofill.size();
+ auto *mem = new(std::nothrow) char[toallocate];
+ if(mem == nullptr)
+ {
+ return errc::not_enough_memory;
+ }
+ tofill._kernel_buffer = std::unique_ptr<char[]>(mem);
+ tofill._kernel_buffer_size = toallocate;
+ }
+ stat_t::want default_stat_contents = stat_t::want::ino | stat_t::want::type;
+ dirent *buffer;
+ size_t bytesavailable;
+ int bytes;
+ bool done = false;
+ do
+ {
+ buffer = kernelbuffer.empty() ? reinterpret_cast<dirent *>(tofill._kernel_buffer.get()) : reinterpret_cast<dirent *>(kernelbuffer.data());
+ bytesavailable = kernelbuffer.empty() ? tofill._kernel_buffer_size : kernelbuffer.size();
+// Seek to start
+#ifdef __linux__
+ if(-1 == ::lseek64(_v.fd, 0, SEEK_SET))
+ {
+ return posix_error();
+ }
+#else
+ if(-1 == ::lseek(_v.fd, 0, SEEK_SET))
+ return posix_error();
+#endif
+ bytes = getdents(_v.fd, reinterpret_cast<char *>(buffer), bytesavailable);
+ if(kernelbuffer.empty() && bytes == -1 && EINVAL == errno)
+ {
+ tofill._kernel_buffer.reset();
+ size_t toallocate = tofill._kernel_buffer_size * 2;
+ auto *mem = new(std::nothrow) char[toallocate];
+ if(mem == nullptr)
+ {
+ return errc::not_enough_memory;
+ }
+ tofill._kernel_buffer = std::unique_ptr<char[]>(mem);
+ tofill._kernel_buffer_size = toallocate;
+ }
+ else
+ {
+ if(bytes == -1)
+ {
+ return posix_error();
+ }
+ done = true;
+ }
+ } while(!done);
+ if(bytes == 0)
+ {
+ tofill._resize(0);
+ return enumerate_info{std::move(tofill), default_stat_contents, true};
+ }
+ AFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(buffer, bytes); // NOLINT
+ size_t n = 0;
+ for(dirent *dent = buffer;; dent = reinterpret_cast<dirent *>(reinterpret_cast<uintptr_t>(dent) + dent->d_reclen))
+ {
+ if(dent->d_ino != 0u)
+ {
+ size_t length = strchr(dent->d_name, 0) - dent->d_name;
+ if(length <= 2 && '.' == dent->d_name[0])
+ {
+ if(1 == length || '.' == dent->d_name[1])
+ {
+ goto cont;
+ }
+ }
+ if(!glob.empty() && fnmatch(zglob.buffer, dent->d_name, 0) != 0)
+ {
+ goto cont;
+ }
+ directory_entry &item = tofill[n];
+ item.leafname = path_view(dent->d_name, length);
+ item.stat = stat_t(nullptr);
+ item.stat.st_ino = dent->d_ino;
+ char d_type = dent->d_type;
+ switch(d_type)
+ {
+ case DT_BLK:
+ item.stat.st_type = filesystem::file_type::block;
+ break;
+ case DT_CHR:
+ item.stat.st_type = filesystem::file_type::character;
+ break;
+ case DT_DIR:
+ item.stat.st_type = filesystem::file_type::directory;
+ break;
+ case DT_FIFO:
+ item.stat.st_type = filesystem::file_type::fifo;
+ break;
+ case DT_LNK:
+ item.stat.st_type = filesystem::file_type::symlink;
+ break;
+ case DT_REG:
+ item.stat.st_type = filesystem::file_type::regular;
+ break;
+ case DT_SOCK:
+ item.stat.st_type = filesystem::file_type::socket;
+ break;
+ default:
+ // Don't say we return type
+ default_stat_contents = default_stat_contents & ~stat_t::want::type;
+ break;
+ }
+ n++;
+ }
+ cont:
+ if((bytes -= dent->d_reclen) <= 0)
+ {
+ // Fill is complete
+ tofill._resize(n);
+ return enumerate_info{std::move(tofill), default_stat_contents, true};
+ }
+ if(n >= tofill.size())
+ {
+ // Fill is incomplete
+ return enumerate_info{std::move(tofill), default_stat_contents, false};
+ }
+ }
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/file_handle.ipp
new file mode 100644
index 00000000..7dcf286b
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/file_handle.ipp
@@ -0,0 +1,492 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (8 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../file_handle.hpp"
+
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<file_handle> file_handle::file(const path_handle &base, file_handle::path_view_type path, file_handle::mode _mode, file_handle::creation _creation, file_handle::caching _caching, file_handle::flag flags) noexcept
+{
+ result<file_handle> ret(file_handle(native_handle_type(), 0, 0, _caching, flags));
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::file;
+ OUTCOME_TRY(attribs, attribs_from_handle_mode_caching_and_flags(nativeh, _mode, _creation, _caching, flags));
+ path_view::c_str zpath(path);
+ if(base.is_valid())
+ {
+ nativeh.fd = ::openat(base.native_handle().fd, zpath.buffer, attribs, 0x1b0 /*660*/);
+ }
+ else
+ {
+ nativeh.fd = ::open(zpath.buffer, attribs, 0x1b0 /*660*/);
+ }
+ if(-1 == nativeh.fd)
+ {
+ return posix_error();
+ }
+ if((flags & flag::disable_prefetching) || (flags & flag::maximum_prefetching))
+ {
+#ifdef POSIX_FADV_SEQUENTIAL
+ int advice = (flags & flag::disable_prefetching) ? POSIX_FADV_RANDOM : (POSIX_FADV_SEQUENTIAL | POSIX_FADV_WILLNEED);
+ if(-1 == ::posix_fadvise(nativeh.fd, 0, 0, advice))
+ {
+ return posix_error();
+ }
+#elif __APPLE__
+ int advice = (flags & flag::disable_prefetching) ? 0 : 1;
+ if(-1 == ::fcntl(nativeh.fd, F_RDAHEAD, advice))
+ {
+ return posix_error();
+ }
+#endif
+ }
+ if(_creation == creation::truncate && ret.value().are_safety_fsyncs_issued())
+ {
+ fsync(nativeh.fd);
+ }
+ return ret;
+}
+
+result<file_handle> file_handle::temp_inode(const path_handle &dirh, mode _mode, flag flags) noexcept
+{
+ caching _caching = caching::temporary;
+ // No need to check inode before unlink
+ flags |= flag::unlink_on_first_close | flag::disable_safety_unlinks;
+ result<file_handle> ret(file_handle(native_handle_type(), 0, 0, _caching, flags));
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::file;
+ // Open file exclusively to prevent collision
+ OUTCOME_TRY(attribs, attribs_from_handle_mode_caching_and_flags(nativeh, _mode, creation::only_if_not_exist, _caching, flags));
+#ifdef O_TMPFILE
+ // Linux has a special flag just for this use case
+ attribs |= O_TMPFILE;
+ attribs &= ~O_EXCL; // allow relinking later
+ nativeh.fd = ::openat(dirh.native_handle().fd, "", attribs, 0600);
+ if(-1 != nativeh.fd)
+ {
+ ret.value()._flags |= flag::anonymous_inode;
+ ret.value()._flags &= ~flag::unlink_on_first_close; // It's already unlinked
+ return ret;
+ }
+ // If it failed, assume this kernel or FS doesn't support O_TMPFILE
+ attribs &= ~O_TMPFILE;
+ attribs |= O_EXCL;
+#endif
+ std::string random;
+ for(;;)
+ {
+ try
+ {
+ random = utils::random_string(32) + ".tmp";
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ nativeh.fd = ::openat(dirh.native_handle().fd, random.c_str(), attribs, 0600); // user read/write perms only
+ if(-1 == nativeh.fd)
+ {
+ int errcode = errno;
+ if(EEXIST == errcode)
+ {
+ continue;
+ }
+ return posix_error(errcode);
+ }
+ // Immediately unlink after creation
+ if(-1 == ::unlinkat(dirh.native_handle().fd, random.c_str(), 0))
+ {
+ return posix_error();
+ }
+ ret.value()._flags &= ~flag::unlink_on_first_close;
+ return ret;
+ }
+}
+
+file_handle::io_result<file_handle::const_buffers_type> file_handle::barrier(file_handle::io_request<file_handle::const_buffers_type> reqs, bool wait_for_device, bool and_metadata, deadline d) noexcept
+{
+ (void) wait_for_device;
+ (void) and_metadata;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(d)
+ {
+ return errc::not_supported;
+ }
+#ifdef __linux__
+ if(!and_metadata)
+ {
+ // Linux has a lovely dedicated syscall giving us exactly what we need here
+ extent_type offset = reqs.offset, bytes = 0;
+ // empty buffers means bytes = 0 which means sync entire file
+ for(const auto &req : reqs.buffers)
+ {
+ bytes += req.len;
+ }
+ unsigned flags = SYNC_FILE_RANGE_WRITE; // start writing all dirty pages in range now
+ if(wait_for_device)
+ {
+ flags |= SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WAIT_AFTER; // block until they're on storage
+ }
+ if(-1 != ::sync_file_range(_v.fd, offset, bytes, flags))
+ {
+ return {reqs.buffers};
+ }
+ }
+#endif
+#if !defined(__FreeBSD__) && !defined(__APPLE__) // neither of these have fdatasync()
+ if(!and_metadata)
+ {
+ if(-1 == ::fdatasync(_v.fd))
+ {
+ return posix_error();
+ }
+ return {reqs.buffers};
+ }
+#endif
+#ifdef __APPLE__
+ if(!wait_for_device)
+ {
+ // OS X fsync doesn't wait for the device to flush its buffers
+ if(-1 == ::fsync(_v.fd))
+ return posix_error();
+ return {std::move(reqs.buffers)};
+ }
+ // This is the fsync as on every other OS
+ if(-1 == ::fcntl(_v.fd, F_FULLFSYNC))
+ return posix_error();
+#else
+ if(-1 == ::fsync(_v.fd))
+ {
+ return posix_error();
+ }
+#endif
+ return {reqs.buffers};
+}
+
+result<file_handle> file_handle::clone(mode mode_, caching caching_, deadline d) const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // Fast path
+ if(mode_ == mode::unchanged)
+ {
+ result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, caching_, _flags));
+ ret.value()._service = _service;
+ ret.value()._v.behaviour = _v.behaviour;
+ ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC);
+ if(-1 == ret.value()._v.fd)
+ {
+ return posix_error();
+ }
+ if(caching_ == caching::unchanged)
+ {
+ return ret;
+ }
+
+ int attribs = fcntl(ret.value()._v.fd, F_GETFL);
+ if(-1 != attribs)
+ {
+ attribs &= ~(O_SYNC
+#ifdef O_DIRECT
+ | O_DIRECT
+#endif
+#ifdef O_DSYNC
+ | O_DSYNC
+#endif
+ );
+ switch(caching_)
+ {
+ case caching::unchanged:
+ break;
+ case caching::none:
+ attribs |= O_SYNC
+#ifdef O_DIRECT
+ | O_DIRECT
+#endif
+ ;
+ if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ ret.value()._v.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case caching::only_metadata:
+#ifdef O_DIRECT
+ attribs |= O_DIRECT;
+#endif
+ if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ ret.value()._v.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case caching::reads:
+ attribs |= O_SYNC;
+ if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ ret.value()._v.behaviour &= ~native_handle_type::disposition::aligned_io;
+ break;
+ case caching::reads_and_metadata:
+#ifdef O_DSYNC
+ attribs |= O_DSYNC;
+#else
+ attribs |= O_SYNC;
+#endif
+ if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ ret.value()._v.behaviour &= ~native_handle_type::disposition::aligned_io;
+ break;
+ case caching::all:
+ case caching::safety_fsyncs:
+ case caching::temporary:
+ if(-1 == fcntl(ret.value()._v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ ret.value()._v.behaviour &= ~native_handle_type::disposition::aligned_io;
+ break;
+ }
+ return ret;
+ }
+ }
+ // Slow path
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if(d.steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = d.to_time_point();
+ }
+ }
+ for(;;)
+ {
+ // Get the current path of myself
+ OUTCOME_TRY(currentpath, current_path());
+ // Open myself
+ auto fh = file({}, currentpath, mode_, creation::open_existing, caching_, _flags);
+ if(fh)
+ {
+ if(fh.value().unique_id() == unique_id())
+ {
+ return fh;
+ }
+ }
+ else
+ {
+ if(fh.error() != errc::no_such_file_or_directory)
+ {
+ return fh.error();
+ }
+ }
+ // Check timeout
+ if(d)
+ {
+ if(d.steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ }
+}
+
+result<file_handle::extent_type> file_handle::maximum_extent() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ struct stat s
+ {
+ };
+ memset(&s, 0, sizeof(s));
+ if(-1 == ::fstat(_v.fd, &s))
+ {
+ return posix_error();
+ }
+ return s.st_size;
+}
+
+result<file_handle::extent_type> file_handle::truncate(file_handle::extent_type newsize) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(ftruncate(_v.fd, newsize) < 0)
+ {
+ return posix_error();
+ }
+ if(are_safety_fsyncs_issued())
+ {
+ fsync(_v.fd);
+ }
+ return newsize;
+}
+
+result<std::vector<std::pair<file_handle::extent_type, file_handle::extent_type>>> file_handle::extents() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ try
+ {
+ std::vector<std::pair<file_handle::extent_type, file_handle::extent_type>> out;
+ out.reserve(64);
+ extent_type start = 0, end = 0;
+ for(;;)
+ {
+#ifdef __linux__
+#ifndef SEEK_DATA
+ errno = EINVAL;
+ break;
+#else
+ start = lseek64(_v.fd, end, SEEK_DATA);
+ if(static_cast<extent_type>(-1) == start)
+ {
+ break;
+ }
+ end = lseek64(_v.fd, start, SEEK_HOLE);
+ if(static_cast<extent_type>(-1) == end)
+ {
+ break;
+ }
+#endif
+#elif defined(__APPLE__)
+ // Can't find any support for extent enumeration in OS X
+ errno = EINVAL;
+ break;
+#elif defined(__FreeBSD__)
+ start = lseek(_v.fd, end, SEEK_DATA);
+ if((extent_type) -1 == start)
+ break;
+ end = lseek(_v.fd, start, SEEK_HOLE);
+ if((extent_type) -1 == end)
+ break;
+#else
+#error Unknown system
+#endif
+ // Data region may have been concurrently deleted
+ if(end > start)
+ {
+ out.emplace_back(start, end - start);
+ }
+ }
+ if(ENXIO != errno)
+ {
+ if(EINVAL == errno)
+ {
+ // If it failed with no output, probably this filing system doesn't support extents
+ if(out.empty())
+ {
+ OUTCOME_TRY(size, file_handle::maximum_extent());
+ out.emplace_back(0, size);
+ return out;
+ }
+ }
+ else
+ {
+ return posix_error();
+ }
+ }
+#if 0
+ // A problem with SEEK_DATA and SEEK_HOLE is that they are racy under concurrent extents changing
+ // Coalesce sequences of contiguous data e.g. 0, 64; 64, 64; 128, 64 ...
+ std::vector<std::pair<extent_type, extent_type>> outfixed; outfixed.reserve(out.size());
+ outfixed.push_back(out.front());
+ for (size_t n = 1; n<out.size(); n++)
+ {
+ if (outfixed.back().first + outfixed.back().second == out[n].first)
+ outfixed.back().second += out[n].second;
+ else
+ outfixed.push_back(out[n]);
+ }
+ return outfixed;
+#endif
+ return out;
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+result<file_handle::extent_type> file_handle::zero(file_handle::extent_type offset, file_handle::extent_type bytes, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+#if defined(__linux__)
+ if(-1 == fallocate(_v.fd, 0x02 /*FALLOC_FL_PUNCH_HOLE*/ | 0x01 /*FALLOC_FL_KEEP_SIZE*/, offset, bytes))
+ {
+ // The filing system may not support trim
+ if(EOPNOTSUPP != errno)
+ {
+ return posix_error();
+ }
+ }
+#endif
+ // Fall back onto a write of zeros
+ if(bytes < utils::page_size())
+ {
+ auto *buffer = static_cast<byte *>(alloca(bytes));
+ memset(buffer, 0, bytes);
+ OUTCOME_TRY(written, write(offset, {{buffer, bytes}}, d));
+ return written[0].len;
+ }
+ try
+ {
+ extent_type ret = 0;
+ auto blocksize = utils::file_buffer_default_size();
+ byte *buffer = utils::page_allocator<byte>().allocate(blocksize);
+ auto unbufferh = undoer([buffer, blocksize] { utils::page_allocator<byte>().deallocate(buffer, blocksize); });
+ (void) unbufferh;
+ while(bytes > 0)
+ {
+ auto towrite = (bytes < blocksize) ? bytes : blocksize;
+ OUTCOME_TRY(written, write(offset, {{buffer, towrite}}, d));
+ offset += written[0].len;
+ bytes -= written[0].len;
+ ret += written[0].len;
+ }
+ return ret;
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp b/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp
new file mode 100644
index 00000000..27f7673f
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp
@@ -0,0 +1,225 @@
+/* A filing system handle
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../fs_handle.hpp"
+#include "../../../stat.hpp"
+#include "../../../utils.hpp"
+#include "import.hpp"
+
+#include <climits> // for PATH_MAX
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<void> fs_handle::_fetch_inode() const noexcept
+{
+ stat_t s(nullptr);
+ OUTCOME_TRYV(s.fill(_get_handle(), stat_t::want::dev | stat_t::want::ino));
+ _devid = s.st_dev;
+ _inode = s.st_ino;
+ return success();
+}
+
+inline result<path_handle> containing_directory(optional<std::reference_wrapper<filesystem::path>> out_filename, const handle &h, const fs_handle &fsh, deadline d) noexcept
+{
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if(d.steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = d.to_time_point();
+ }
+ }
+ try
+ {
+ for(;;)
+ {
+ // Get current path for handle and open its containing dir
+ OUTCOME_TRY(_currentpath, h.current_path());
+ // If current path is empty, it's been deleted
+ if(_currentpath.empty())
+ {
+ return errc::no_such_file_or_directory;
+ }
+ // Split the path into root and leafname
+ path_view currentpath(_currentpath);
+ path_view filename = currentpath.filename();
+ currentpath.remove_filename();
+ // Zero terminate the root path so it doesn't get copied later
+ const_cast<filesystem::path::string_type &>(_currentpath.native())[currentpath.native_size()] = 0;
+ auto currentdirh_ = path_handle::path(currentpath);
+ if(!currentdirh_)
+ {
+ continue;
+ }
+ path_handle currentdirh = std::move(currentdirh_.value());
+ if(h.flags() & handle::flag::disable_safety_unlinks)
+ {
+ if(out_filename)
+ {
+ out_filename->get() = filename.path();
+ }
+ return success(std::move(currentdirh));
+ }
+ // Open the same file name, and compare dev and inode
+ path_view::c_str zpath(filename);
+ int fd = ::openat(currentdirh.native_handle().fd, zpath.buffer, O_CLOEXEC);
+ if(fd == -1)
+ {
+ if(ENOENT == errno)
+ {
+ continue;
+ }
+ return posix_error();
+ }
+ auto unfd = undoer([fd] { ::close(fd); });
+ (void) unfd;
+ struct stat s
+ {
+ };
+ if(-1 == ::fstat(fd, &s))
+ {
+ continue;
+ }
+ // If the same, we know for a fact that this is the correct containing dir for now at least
+ if(static_cast<fs_handle::dev_t>(s.st_dev) == fsh.st_dev() && s.st_ino == fsh.st_ino())
+ {
+ if(out_filename)
+ {
+ out_filename->get() = filename.path();
+ }
+ return success(std::move(currentdirh));
+ }
+ // Check timeout
+ if(d)
+ {
+ if(d.steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+result<path_handle> fs_handle::parent_path_handle(deadline d) const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ auto &h = _get_handle();
+ if(_devid == 0 && _inode == 0)
+ {
+ OUTCOME_TRY(_fetch_inode());
+ }
+ return containing_directory({}, h, *this, d);
+}
+
+result<void> fs_handle::relink(const path_handle &base, path_view_type path, bool atomic_replace, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ auto &h = const_cast<handle &>(_get_handle());
+ path_view::c_str zpath(path);
+#ifdef O_TMPFILE
+ // If the handle was created with O_TMPFILE, we need a different approach
+ if(h.flags() & handle::flag::anonymous_inode)
+ {
+ if(atomic_replace)
+ {
+ return errc::function_not_supported;
+ }
+ char _path[PATH_MAX];
+ snprintf(_path, PATH_MAX, "/proc/self/fd/%d", h.native_handle().fd);
+ if(-1 == ::linkat(AT_FDCWD, _path, base.is_valid() ? base.native_handle().fd : AT_FDCWD, zpath.buffer, AT_SYMLINK_FOLLOW))
+ {
+ return posix_error();
+ }
+ h._flags &= ~handle::flag::anonymous_inode;
+ return success();
+ }
+#endif
+ // Open our containing directory
+ filesystem::path filename;
+ if(_devid == 0 && _inode == 0)
+ {
+ OUTCOME_TRY(_fetch_inode());
+ }
+ OUTCOME_TRY(dirh, containing_directory(std::ref(filename), h, *this, d));
+ if(!atomic_replace)
+ {
+// Some systems provide an extension for atomic non-replacing renames
+#ifdef RENAME_NOREPLACE
+ if(-1 != ::renameat2(dirh.native_handle().fd, filename.c_str(), base.is_valid() ? base.native_handle().fd : AT_FDCWD, zpath.buffer, RENAME_NOREPLACE))
+ return success();
+ if(EEXIST == errno)
+ return posix_error();
+#endif
+ // Otherwise we need to use linkat followed by renameat (non-atomic)
+ if(-1 == ::linkat(dirh.native_handle().fd, filename.c_str(), base.is_valid() ? base.native_handle().fd : AT_FDCWD, zpath.buffer, 0))
+ {
+ return posix_error();
+ }
+ }
+ if(-1 == ::renameat(dirh.native_handle().fd, filename.c_str(), base.is_valid() ? base.native_handle().fd : AT_FDCWD, zpath.buffer))
+ {
+ return posix_error();
+ }
+ return success();
+}
+
+result<void> fs_handle::unlink(deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ auto &h = _get_handle();
+ // Open our containing directory
+ filesystem::path filename;
+ if(_devid == 0 && _inode == 0)
+ {
+ OUTCOME_TRY(_fetch_inode());
+ }
+ OUTCOME_TRY(dirh, containing_directory(std::ref(filename), h, *this, d));
+ if(-1 == ::unlinkat(dirh.native_handle().fd, filename.c_str(), h.is_directory() ? AT_REMOVEDIR : 0))
+ {
+ return posix_error();
+ }
+ return success();
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/handle.ipp b/include/llfio/v2.0/detail/impl/posix/handle.ipp
new file mode 100644
index 00000000..c5143830
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/handle.ipp
@@ -0,0 +1,197 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#include <sys/sysctl.h> // for sysctl()
+#include <sys/user.h> // for struct kinfo_file
+#endif
+
+#if defined(__APPLE__)
+#include <sys/stat.h> // for struct stat
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+handle::~handle()
+{
+ if(_v)
+ {
+ // Call close() below
+ auto ret = handle::close();
+ if(ret.has_error())
+ {
+ AFIO_LOG_FATAL(_v.fd, "handle::~handle() close failed");
+ abort();
+ }
+ }
+}
+
+result<handle::path_type> handle::current_path() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ try
+ {
+ // Most efficient, least memory copying method is direct fill of a string which is moved into filesystem::path
+ filesystem::path::string_type ret;
+#if defined(__linux__)
+ ret.resize(32769);
+ auto *out = const_cast<char *>(ret.data());
+ // Linux keeps a symlink at /proc/self/fd/n
+ char in[64];
+ snprintf(in, sizeof(in), "/proc/self/fd/%d", _v.fd);
+ ssize_t len;
+ if((len = readlink(in, out, 32768)) == -1)
+ {
+ return posix_error();
+ }
+ ret.resize(len);
+ // Linux prepends or appends a " (deleted)" when a fd is nameless
+ // TODO(ned): Should I stat the target to be really sure?
+ if(ret.size() >= 10 && ((ret.compare(0, 10, " (deleted)") == 0) || (ret.compare(ret.size() - 10, 10, " (deleted)") == 0)))
+ {
+ ret.clear();
+ }
+#elif defined(__APPLE__)
+ ret.resize(32769);
+ char *out = const_cast<char *>(ret.data());
+ // Yes, this API is instant memory corruption. Thank you Apple.
+ if(-1 == fcntl(_v.fd, F_GETPATH, out))
+ return posix_error();
+ ret.resize(strchr(out, 0) - out); // no choice :(
+ // Apple returns the previous path when deleted, so lstat to be sure
+ struct stat ls;
+ bool exists = (-1 != ::lstat(out, &ls));
+ if(!exists)
+ ret.clear();
+#elif defined(__FreeBSD__)
+ // Unfortunately this call is broken on FreeBSD 10 where it is currently returning
+ // null paths most of the time for regular files. Directories work perfectly. I've
+ // logged a bug with test case at https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=197695.
+ size_t len;
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_FILEDESC, getpid()};
+ if(-1 == sysctl(mib, 4, NULL, &len, NULL, 0))
+ return posix_error();
+ std::vector<char> buffer(len * 2);
+ if(-1 == sysctl(mib, 4, buffer.data(), &len, NULL, 0))
+ return posix_error();
+#if 0 // ndef NDEBUG
+ for (char *p = buffer.data(); p<buffer.data() + len;)
+ {
+ struct kinfo_file *kif = (struct kinfo_file *) p;
+ std::cout << kif->kf_type << " " << kif->kf_fd << " " << kif->kf_path << std::endl;
+ p += kif->kf_structsize;
+ }
+#endif
+ for(char *p = buffer.data(); p < buffer.data() + len;)
+ {
+ struct kinfo_file *kif = (struct kinfo_file *) p;
+ if(kif->kf_fd == _v.fd)
+ {
+ ret = std::string(kif->kf_path);
+ break;
+ }
+ p += kif->kf_structsize;
+ }
+#else
+#error Unknown system
+#endif
+ return path_type(std::move(ret));
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+result<void> handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_v)
+ {
+ if(are_safety_fsyncs_issued() && is_writable())
+ {
+ if(-1 == fsync(_v.fd))
+ {
+ return posix_error();
+ }
+ }
+ if(-1 == ::close(_v.fd))
+ {
+ return posix_error();
+ }
+ _v = native_handle_type();
+ }
+ return success();
+}
+
+result<handle> handle::clone() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ result<handle> ret(handle(native_handle_type(), _caching, _flags));
+ ret.value()._v.behaviour = _v.behaviour;
+ ret.value()._v.fd = ::fcntl(_v.fd, F_DUPFD_CLOEXEC);
+ if(-1 == ret.value()._v.fd)
+ {
+ return posix_error();
+ }
+ return ret;
+}
+
+result<void> handle::set_append_only(bool enable) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ int attribs = ::fcntl(_v.fd, F_GETFL);
+ if(-1 == attribs)
+ {
+ return posix_error();
+ }
+ if(enable)
+ {
+ // Set append_only
+ attribs |= O_APPEND;
+ if(-1 == ::fcntl(_v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ _v.behaviour |= native_handle_type::disposition::append_only;
+ }
+ else
+ {
+ // Remove append_only
+ attribs &= ~O_APPEND;
+ if(-1 == ::fcntl(_v.fd, F_SETFL, attribs))
+ {
+ return posix_error();
+ }
+ _v.behaviour &= ~native_handle_type::disposition::append_only;
+ }
+ return success();
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/import.hpp b/include/llfio/v2.0/detail/impl/posix/import.hpp
new file mode 100644
index 00000000..64f4bd2b
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/import.hpp
@@ -0,0 +1,116 @@
+/* Declarations for POSIX system APIs
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (8 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_POSIX_HPP
+#define AFIO_POSIX_HPP
+
+#include "../../../handle.hpp"
+
+#ifdef _WIN32
+#error You should not include posix/import.hpp on Windows platforms
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+inline result<int> attribs_from_handle_mode_caching_and_flags(native_handle_type &nativeh, handle::mode _mode, handle::creation _creation, handle::caching _caching, handle::flag /*unused*/) noexcept
+{
+ int attribs = O_CLOEXEC;
+ switch(_mode)
+ {
+ case handle::mode::unchanged:
+ return errc::invalid_argument;
+ case handle::mode::none:
+ break;
+ case handle::mode::attr_read:
+ case handle::mode::read:
+ attribs = O_RDONLY;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable;
+ break;
+ case handle::mode::attr_write:
+ case handle::mode::write:
+ attribs = O_RDWR;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
+ break;
+ case handle::mode::append:
+ attribs = O_WRONLY | O_APPEND;
+ nativeh.behaviour |= native_handle_type::disposition::writable | native_handle_type::disposition::append_only;
+ break;
+ }
+ switch(_creation)
+ {
+ case handle::creation::open_existing:
+ break;
+ case handle::creation::only_if_not_exist:
+ attribs |= O_CREAT | O_EXCL;
+ break;
+ case handle::creation::if_needed:
+ attribs |= O_CREAT;
+ break;
+ case handle::creation::truncate:
+ attribs |= O_TRUNC;
+ break;
+ }
+ switch(_caching)
+ {
+ case handle::caching::unchanged:
+ return errc::invalid_argument;
+ case handle::caching::none:
+ attribs |= O_SYNC
+#ifdef O_DIRECT
+ | O_DIRECT;
+#else
+ ;
+#endif
+ nativeh.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case handle::caching::only_metadata:
+#ifdef O_DIRECT
+ attribs |= O_DIRECT;
+#endif
+ nativeh.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case handle::caching::reads:
+ attribs |= O_SYNC;
+ break;
+ case handle::caching::reads_and_metadata:
+#ifdef O_DSYNC
+ attribs |= O_DSYNC;
+#else
+ attribs |= O_SYNC;
+#endif
+ break;
+ case handle::caching::all:
+ case handle::caching::safety_fsyncs:
+ case handle::caching::temporary:
+ break;
+ }
+ return attribs;
+}
+
+AFIO_V2_NAMESPACE_END
+
+#endif
diff --git a/include/llfio/v2.0/detail/impl/posix/io_handle.ipp b/include/llfio/v2.0/detail/impl/posix/io_handle.ipp
new file mode 100644
index 00000000..c33ec08a
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/io_handle.ipp
@@ -0,0 +1,317 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../io_handle.hpp"
+
+#include <climits> // for IOV_MAX
+#include <fcntl.h>
+#include <sys/uio.h> // for preadv etc
+#include <unistd.h>
+#if AFIO_USE_POSIX_AIO
+#include <aio.h>
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+size_t io_handle::max_buffers() const noexcept
+{
+ static size_t v;
+ if(v == 0u)
+ {
+#ifdef __APPLE__
+ v = 1;
+#else
+ long r = sysconf(_SC_IOV_MAX);
+ if(r == -1)
+ {
+#ifdef IOV_MAX
+ r = IOV_MAX;
+#else
+ r = 1;
+#endif
+ }
+ v = r;
+#endif
+ }
+ return v;
+}
+
+io_handle::io_result<io_handle::buffers_type> io_handle::read(io_handle::io_request<io_handle::buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(d)
+ {
+ return errc::not_supported;
+ }
+ if(reqs.buffers.size() > IOV_MAX)
+ {
+ return errc::argument_list_too_long;
+ }
+#if 0
+ struct iovec *iov = (struct iovec *) alloca(reqs.buffers.size() * sizeof(struct iovec));
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ iov[n].iov_base = reqs.buffers[n].data;
+ iov[n].iov_len = reqs.buffers[n].len;
+ }
+#else
+ static_assert(sizeof(buffer_type) == sizeof(iovec), "buffer_type and struct iovec do not match");
+ auto *iov = reinterpret_cast<struct iovec *>(reqs.buffers.data());
+#endif
+#ifndef NDEBUG
+ if(_v.requires_aligned_io())
+ {
+ assert((reqs.offset & 511) == 0);
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ assert((reinterpret_cast<uintptr_t>(iov[n].iov_base) & 511) == 0);
+ assert((iov[n].iov_len & 511) == 0);
+ }
+ }
+#endif
+ ssize_t bytesread = 0;
+#if AFIO_MISSING_PIOV
+ off_t offset = reqs.offset;
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ bytesread += ::pread(_v.fd, iov[n].iov_base, iov[n].iov_len, offset);
+ offset += iov[n].iov_len;
+ }
+#else
+ bytesread = ::preadv(_v.fd, iov, reqs.buffers.size(), reqs.offset);
+#endif
+ if(bytesread < 0)
+ {
+ return posix_error();
+ }
+ for(auto &buffer : reqs.buffers)
+ {
+ if(buffer.len >= static_cast<size_t>(bytesread))
+ {
+ bytesread -= buffer.len;
+ }
+ else if(bytesread > 0)
+ {
+ buffer.len = bytesread;
+ bytesread = 0;
+ }
+ else
+ {
+ buffer.len = 0;
+ }
+ }
+ return {reqs.buffers};
+}
+
+io_handle::io_result<io_handle::const_buffers_type> io_handle::write(io_handle::io_request<io_handle::const_buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(d)
+ {
+ return errc::not_supported;
+ }
+ if(reqs.buffers.size() > IOV_MAX)
+ {
+ return errc::argument_list_too_long;
+ }
+#if 0
+ struct iovec *iov = (struct iovec *) alloca(reqs.buffers.size() * sizeof(struct iovec));
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ iov[n].iov_base = const_cast<char *>(reqs.buffers[n].data);
+ iov[n].iov_len = reqs.buffers[n].len;
+ }
+#else
+ static_assert(sizeof(buffer_type) == sizeof(iovec), "buffer_type and struct iovec do not match");
+ auto *iov = reinterpret_cast<struct iovec *>(reqs.buffers.data());
+#endif
+#ifndef NDEBUG
+ if(_v.requires_aligned_io())
+ {
+ assert((reqs.offset & 511) == 0);
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ assert((reinterpret_cast<uintptr_t>(iov[n].iov_base) & 511) == 0);
+ assert((iov[n].iov_len & 511) == 0);
+ }
+ }
+#endif
+ ssize_t byteswritten = 0;
+#if AFIO_MISSING_PIOV
+ off_t offset = reqs.offset;
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ byteswritten += ::pwrite(_v.fd, iov[n].iov_base, iov[n].iov_len, offset);
+ offset += iov[n].iov_len;
+ }
+#else
+ byteswritten = ::pwritev(_v.fd, iov, reqs.buffers.size(), reqs.offset);
+#endif
+ if(byteswritten < 0)
+ {
+ return posix_error();
+ }
+ for(auto &buffer : reqs.buffers)
+ {
+ if(buffer.len >= static_cast<size_t>(byteswritten))
+ {
+ byteswritten -= buffer.len;
+ }
+ else if(byteswritten > 0)
+ {
+ buffer.len = byteswritten;
+ byteswritten = 0;
+ }
+ else
+ {
+ buffer.len = 0;
+ }
+ }
+ return {reqs.buffers};
+}
+
+result<io_handle::extent_guard> io_handle::lock(io_handle::extent_type offset, io_handle::extent_type bytes, bool exclusive, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(d && d.nsecs > 0)
+ {
+ return errc::not_supported;
+ }
+ bool failed = false;
+#if !defined(__linux__) && !defined(F_OFD_SETLK)
+ if(0 == bytes)
+ {
+ // Non-Linux has a sane locking system in flock() if you are willing to lock the entire file
+ int operation = ((d && !d.nsecs) ? LOCK_NB : 0) | (exclusive ? LOCK_EX : LOCK_SH);
+ if(-1 == flock(_v.fd, operation))
+ failed = true;
+ }
+ else
+#endif
+ {
+ struct flock fl
+ {
+ };
+ memset(&fl, 0, sizeof(fl));
+ fl.l_type = exclusive ? F_WRLCK : F_RDLCK;
+ constexpr extent_type extent_topbit = static_cast<extent_type>(1) << (8 * sizeof(extent_type) - 1);
+ if((offset & extent_topbit) != 0u)
+ {
+ AFIO_LOG_WARN(_v.fd, "io_handle::lock() called with offset with top bit set, masking out");
+ }
+ if((bytes & extent_topbit) != 0u)
+ {
+ AFIO_LOG_WARN(_v.fd, "io_handle::lock() called with bytes with top bit set, masking out");
+ }
+ fl.l_whence = SEEK_SET;
+ fl.l_start = offset & ~extent_topbit;
+ fl.l_len = bytes & ~extent_topbit;
+#ifdef F_OFD_SETLK
+ if(-1 == fcntl(_v.fd, (d && !d.nsecs) ? F_OFD_SETLK : F_OFD_SETLKW, &fl))
+ {
+ if(EINVAL == errno) // OFD locks not supported on this kernel
+ {
+ if(-1 == fcntl(_v.fd, (d && !d.nsecs) ? F_SETLK : F_SETLKW, &fl))
+ failed = true;
+ else
+ _flags |= flag::byte_lock_insanity;
+ }
+ else
+ failed = true;
+ }
+#else
+ if(-1 == fcntl(_v.fd, (d && (d.nsecs == 0u)) ? F_SETLK : F_SETLKW, &fl))
+ {
+ failed = true;
+ }
+ else
+ {
+ _flags |= flag::byte_lock_insanity;
+ }
+#endif
+ }
+ if(failed)
+ {
+ if(d && (d.nsecs == 0u) && (EACCES == errno || EAGAIN == errno || EWOULDBLOCK == errno))
+ {
+ return errc::timed_out;
+ }
+
+
+ return posix_error();
+ }
+ return extent_guard(this, offset, bytes, exclusive);
+}
+
+void io_handle::unlock(io_handle::extent_type offset, io_handle::extent_type bytes) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ bool failed = false;
+#if !defined(__linux__) && !defined(F_OFD_SETLK)
+ if(0 == bytes)
+ {
+ if(-1 == flock(_v.fd, LOCK_UN))
+ failed = true;
+ }
+ else
+#endif
+ {
+ struct flock fl
+ {
+ };
+ memset(&fl, 0, sizeof(fl));
+ fl.l_type = F_UNLCK;
+ constexpr extent_type extent_topbit = static_cast<extent_type>(1) << (8 * sizeof(extent_type) - 1);
+ fl.l_whence = SEEK_SET;
+ fl.l_start = offset & ~extent_topbit;
+ fl.l_len = bytes & ~extent_topbit;
+#ifdef F_OFD_SETLK
+ if(-1 == fcntl(_v.fd, F_OFD_SETLK, &fl))
+ {
+ if(EINVAL == errno) // OFD locks not supported on this kernel
+ {
+ if(-1 == fcntl(_v.fd, F_SETLK, &fl))
+ failed = true;
+ }
+ else
+ failed = true;
+ }
+#else
+ if(-1 == fcntl(_v.fd, F_SETLK, &fl))
+ {
+ failed = true;
+ }
+#endif
+ }
+ if(failed)
+ {
+ auto ret(posix_error());
+ (void) ret;
+ AFIO_LOG_FATAL(_v.fd, "io_handle::unlock() failed");
+ std::terminate();
+ }
+}
+
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/io_service.ipp b/include/llfio/v2.0/detail/impl/posix/io_service.ipp
new file mode 100644
index 00000000..72560b40
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/io_service.ipp
@@ -0,0 +1,410 @@
+/* Multiplex file i/o
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (4 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../async_file_handle.hpp"
+
+#include <pthread.h>
+#if AFIO_USE_POSIX_AIO
+#include <aio.h>
+#include <sys/mman.h>
+#if AFIO_COMPILE_KQUEUES
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#endif
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+static int interrupt_signal;
+static struct sigaction interrupt_signal_handler_old_action;
+struct ucontext;
+static inline void interrupt_signal_handler(int /*unused*/, siginfo_t * /*unused*/, void * /*unused*/)
+{
+ // We do nothing, and aio_suspend should exit with EINTR
+}
+
+int io_service::interruption_signal() noexcept
+{
+ return interrupt_signal;
+}
+
+int io_service::set_interruption_signal(int signo)
+{
+ int ret = interrupt_signal;
+ if(interrupt_signal != 0)
+ {
+ if(sigaction(interrupt_signal, &interrupt_signal_handler_old_action, nullptr) < 0)
+ {
+ throw std::system_error(errno, std::system_category()); // NOLINT
+ }
+ interrupt_signal = 0;
+ }
+ if(signo != 0)
+ {
+#if AFIO_HAVE_REALTIME_SIGNALS
+ if(-1 == signo)
+ {
+ for(signo = SIGRTMIN; signo < SIGRTMAX; signo++)
+ {
+ struct sigaction sigact
+ {
+ };
+ memset(&sigact, 0, sizeof(sigact));
+ if(sigaction(signo, nullptr, &sigact) >= 0)
+ {
+ if(sigact.sa_handler == SIG_DFL)
+ {
+ break;
+ }
+ }
+ }
+ }
+#endif
+ // Install process wide signal handler for signal
+ struct sigaction sigact
+ {
+ };
+ memset(&sigact, 0, sizeof(sigact));
+ sigact.sa_sigaction = &interrupt_signal_handler;
+ sigact.sa_flags = SA_SIGINFO;
+ sigemptyset(&sigact.sa_mask);
+ if(sigaction(signo, &sigact, &interrupt_signal_handler_old_action) < 0)
+ {
+ throw std::system_error(errno, std::system_category()); // NOLINT
+ }
+ interrupt_signal = signo;
+ }
+ return ret;
+}
+
+void io_service::_block_interruption() noexcept
+{
+ if(_use_kqueues)
+ {
+ return;
+ }
+ assert(!_blocked_interrupt_signal);
+ sigset_t set{};
+ sigemptyset(&set);
+ sigaddset(&set, interrupt_signal);
+ pthread_sigmask(SIG_BLOCK, &set, nullptr);
+ _blocked_interrupt_signal = interrupt_signal;
+ _need_signal = false;
+}
+
+void io_service::_unblock_interruption() noexcept
+{
+ if(_use_kqueues)
+ {
+ return;
+ }
+ assert(_blocked_interrupt_signal);
+ if(_blocked_interrupt_signal != 0)
+ {
+ sigset_t set{};
+ sigemptyset(&set);
+ sigaddset(&set, _blocked_interrupt_signal);
+ pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
+ _blocked_interrupt_signal = 0;
+ _need_signal = true;
+ }
+}
+
+io_service::io_service()
+ : _work_queued(0)
+{
+ _threadh = pthread_self();
+#if AFIO_USE_POSIX_AIO
+ _use_kqueues = true;
+ _blocked_interrupt_signal = 0;
+#if AFIO_COMPILE_KQUEUES
+ _kqueueh = 0;
+#error todo
+#else
+ disable_kqueues();
+#endif
+#else
+#error todo
+#endif
+}
+
+io_service::~io_service()
+{
+ if(_work_queued != 0u)
+ {
+#ifndef NDEBUG
+ fprintf(stderr, "WARNING: ~io_service() sees work still queued, blocking until no work queued\n");
+#endif
+ while(_work_queued != 0u)
+ {
+ std::this_thread::yield();
+ }
+ }
+#if AFIO_USE_POSIX_AIO
+#if AFIO_COMPILE_KQUEUES
+ if(_kqueueh)
+ ::close(_kqueueh);
+#endif
+ _aiocbsv.clear();
+ if(pthread_self() == _threadh)
+ {
+ _unblock_interruption();
+ }
+#else
+#error todo
+#endif
+}
+
+#if AFIO_USE_POSIX_AIO
+void io_service::disable_kqueues()
+{
+ if(_use_kqueues)
+ {
+ if(_work_queued != 0u)
+ {
+ throw std::runtime_error("Cannot disable kqueues if work is pending"); // NOLINT
+ }
+ if(pthread_self() != _threadh)
+ {
+ throw std::runtime_error("Cannot disable kqueues except from owning thread"); // NOLINT
+ }
+ // Is the global signal handler set yet?
+ if(interrupt_signal == 0)
+ {
+ set_interruption_signal();
+ }
+ _use_kqueues = false;
+ // Block interruption on this thread
+ _block_interruption();
+// Prepare for aio_suspend
+#ifdef AIO_LISTIO_MAX
+ _aiocbsv.reserve(AIO_LISTIO_MAX);
+#else
+ _aiocbsv.reserve(16);
+#endif
+ }
+}
+#endif
+
+result<bool> io_service::run_until(deadline d) noexcept
+{
+ if(_work_queued == 0u)
+ {
+ return false;
+ }
+ if(pthread_self() != _threadh)
+ {
+ return errc::operation_not_supported;
+ }
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if(d.steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = d.to_time_point();
+ }
+ }
+ struct timespec *ts = nullptr, _ts{};
+ memset(&_ts, 0, sizeof(_ts));
+ bool done = false;
+ do
+ {
+ if(d)
+ {
+ std::chrono::nanoseconds ns{};
+ if(d.steady)
+ {
+ ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds(d.nsecs)) - std::chrono::steady_clock::now());
+ }
+ else
+ {
+ ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end_utc - std::chrono::system_clock::now());
+ }
+ ts = &_ts;
+ if(ns.count() <= 0)
+ {
+ ts->tv_sec = 0;
+ ts->tv_nsec = 0;
+ }
+ else
+ {
+ ts->tv_sec = ns.count() / 1000000000ULL;
+ ts->tv_nsec = ns.count() % 1000000000ULL;
+ }
+ }
+ bool timedout = false;
+ // Unblock the interruption signal
+ _unblock_interruption();
+ // Execute any pending posts
+ {
+ std::unique_lock<decltype(_posts_lock)> g(_posts_lock);
+ if(!_posts.empty())
+ {
+ post_info *pi = &_posts.front();
+ g.unlock();
+ pi->f(this);
+ _post_done(pi);
+ // We did work, so exit
+ // Block the interruption signal
+ _block_interruption();
+ return _work_queued != 0;
+ }
+ }
+#if AFIO_USE_POSIX_AIO
+ int errcode = 0;
+ if(_use_kqueues)
+ {
+#if AFIO_COMPILE_KQUEUES
+#error todo
+#endif
+ }
+ else
+ {
+ if(aio_suspend(_aiocbsv.data(), _aiocbsv.size(), ts) < 0)
+ {
+ errcode = errno;
+ }
+ }
+ // Block the interruption signal
+ _block_interruption();
+ if(errcode != 0)
+ {
+ switch(errcode)
+ {
+ case EAGAIN:
+ if(d)
+ {
+ timedout = true;
+ }
+ break;
+ case EINTR:
+ // Let him loop, recalculate any timeout and check for posts to be executed
+ break;
+ default:
+ return posix_error(errcode);
+ }
+ }
+ else
+ {
+ // Poll the outstanding aiocbs to see which are ready
+ for(auto &aiocb : _aiocbsv)
+ {
+ int ioerr = aio_error(aiocb);
+ if(EINPROGRESS == ioerr)
+ {
+ continue;
+ }
+ if(0 == ioerr)
+ {
+ // Scavenge the aio
+ int ioret = aio_return(aiocb);
+ if(ioret < 0)
+ {
+ return posix_error();
+ }
+ // std::cout << "aiocb " << aiocb << " sees succesful return " << ioret << std::endl;
+ // The aiocb aio_sigevent.sigev_value.sival_ptr field will point to a file_handle::_io_state_type
+ auto io_state = static_cast<async_file_handle::_erased_io_state_type *>(aiocb->aio_sigevent.sigev_value.sival_ptr);
+ assert(io_state);
+ io_state->_system_io_completion(0, ioret, &aiocb);
+ }
+ else
+ {
+ // Either cancelled or errored out
+ // std::cout << "aiocb " << aiocb << " sees failed return " << ioerr << std::endl;
+ // The aiocb aio_sigevent.sigev_value.sival_ptr field will point to a file_handle::_io_state_type
+ auto io_state = static_cast<async_file_handle::_erased_io_state_type *>(aiocb->aio_sigevent.sigev_value.sival_ptr);
+ assert(io_state);
+ io_state->_system_io_completion(ioerr, 0, &aiocb);
+ }
+ }
+ // Eliminate any empty holes in the quick aiocbs vector
+ _aiocbsv.erase(std::remove(_aiocbsv.begin(), _aiocbsv.end(), nullptr), _aiocbsv.end());
+ done = true;
+ }
+#else
+#error todo
+#endif
+ if(timedout)
+ {
+ if(d.steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ } while(!done);
+ return _work_queued != 0;
+}
+
+void io_service::_post(detail::function_ptr<void(io_service *)> &&f)
+{
+ {
+ post_info pi(this, std::move(f));
+ std::lock_guard<decltype(_posts_lock)> g(_posts_lock);
+ _posts.push_back(std::move(pi));
+ }
+ _work_enqueued();
+#if AFIO_USE_POSIX_AIO
+ if(_use_kqueues)
+ {
+#if AFIO_COMPILE_KQUEUES
+#error todo
+#endif
+ }
+ else
+ {
+ // If run_until() is exactly between the unblock of the signal and the beginning
+ // of the aio_suspend(), we need to pump this until run_until() notices
+ while(_need_signal)
+ {
+ //# if AFIO_HAVE_REALTIME_SIGNALS
+ // sigval val = { 0 };
+ // pthread_sigqueue(_threadh, interrupt_signal, val);
+ //#else
+ pthread_kill(_threadh, interrupt_signal);
+ //# endif
+ }
+ }
+#else
+#error todo
+#endif
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/map_handle.ipp b/include/llfio/v2.0/detail/impl/posix/map_handle.ipp
new file mode 100644
index 00000000..0c4944d1
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/map_handle.ipp
@@ -0,0 +1,596 @@
+/* A handle to a source of mapped memory
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (10 commits)
+File Created: Apr 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../map_handle.hpp"
+#include "../../../utils.hpp"
+
+#ifdef __has_include
+#if __has_include("../../../quickcpplib/include/signal_guard.hpp")
+#include "../../../quickcpplib/include/signal_guard.hpp"
+#else
+#include "quickcpplib/include/signal_guard.hpp"
+#endif
+#else
+#include "quickcpplib/include/signal_guard.hpp"
+#endif
+
+#include <sys/mman.h>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+section_handle::~section_handle()
+{
+ if(_v)
+ {
+ auto ret = section_handle::close();
+ if(ret.has_error())
+ {
+ AFIO_LOG_FATAL(_v.h, "section_handle::~section_handle() close failed");
+ abort();
+ }
+ }
+}
+result<void> section_handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_v)
+ {
+ // We don't want ~handle() to close our handle borrowed from the backing file or _anonymous
+ _v = native_handle_type();
+ OUTCOME_TRYV(handle::close());
+ OUTCOME_TRYV(_anonymous.close());
+ _flag = flag::none;
+ }
+ return success();
+}
+
+result<section_handle> section_handle::section(file_handle &backing, extent_type /* unused */, flag _flag) noexcept
+{
+ result<section_handle> ret(section_handle(native_handle_type(), &backing, file_handle(), _flag));
+ native_handle_type &nativeh = ret.value()._v;
+ nativeh.fd = backing.native_handle().fd;
+ if(_flag & flag::read)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::readable;
+ }
+ if(_flag & flag::write)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::writable;
+ }
+ nativeh.behaviour |= native_handle_type::disposition::section;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ return ret;
+}
+
+result<section_handle> section_handle::section(extent_type bytes, const path_handle &dirh, flag _flag) noexcept
+{
+ OUTCOME_TRY(_anonh, file_handle::temp_inode(dirh));
+ OUTCOME_TRYV(_anonh.truncate(bytes));
+ result<section_handle> ret(section_handle(native_handle_type(), nullptr, std::move(_anonh), _flag));
+ native_handle_type &nativeh = ret.value()._v;
+ file_handle &anonh = ret.value()._anonymous;
+ nativeh.fd = anonh.native_handle().fd;
+ if(_flag & flag::read)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::readable;
+ }
+ if(_flag & flag::write)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::writable;
+ }
+ nativeh.behaviour |= native_handle_type::disposition::section;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ return ret;
+}
+
+result<section_handle::extent_type> section_handle::length() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ struct stat s
+ {
+ };
+ memset(&s, 0, sizeof(s));
+ if(-1 == ::fstat(_v.fd, &s))
+ {
+ return posix_error();
+ }
+ return s.st_size;
+}
+
+result<section_handle::extent_type> section_handle::truncate(extent_type newsize) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if((_backing == nullptr) && newsize > 0)
+ {
+ if(-1 == ::ftruncate(_anonymous.native_handle().fd, newsize))
+ {
+ return posix_error();
+ }
+ }
+ return newsize;
+}
+
+
+/******************************************* map_handle *********************************************/
+
+
+map_handle::~map_handle()
+{
+ if(_v)
+ {
+ // Unmap the view
+ auto ret = map_handle::close();
+ if(ret.has_error())
+ {
+ AFIO_LOG_FATAL(_v.fd, "map_handle::~map_handle() close failed");
+ abort();
+ }
+ }
+}
+
+result<void> map_handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_addr != nullptr)
+ {
+ if(is_writable() && (_flag & section_handle::flag::barrier_on_close))
+ {
+ OUTCOME_TRYV(map_handle::barrier({}, true, false));
+ }
+ // printf("%d munmap %p-%p\n", getpid(), _addr, _addr+_length);
+ if(-1 == ::munmap(_addr, _length))
+ {
+ return posix_error();
+ }
+ }
+ // We don't want ~handle() to close our borrowed handle
+ _v = native_handle_type();
+ _addr = nullptr;
+ _length = 0;
+ return success();
+}
+
+native_handle_type map_handle::release() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // We don't want ~handle() to close our borrowed handle
+ _v = native_handle_type();
+ _addr = nullptr;
+ _length = 0;
+ return {};
+}
+
+map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_handle::io_request<map_handle::const_buffers_type> reqs, bool wait_for_device, bool and_metadata, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ byte *addr = _addr + reqs.offset;
+ extent_type bytes = 0;
+ // Check for overflow
+ for(const auto &req : reqs.buffers)
+ {
+ if(bytes + req.len < bytes)
+ {
+ return errc::value_too_large;
+ }
+ bytes += req.len;
+ }
+ // If empty, do the whole file
+ if(reqs.buffers.empty())
+ {
+ bytes = _length;
+ }
+ // If nvram and not syncing metadata, use lightweight barrier
+ if(!and_metadata && is_nvram())
+ {
+ auto synced = barrier({addr, bytes});
+ if(synced.len >= bytes)
+ {
+ return {reqs.buffers};
+ }
+ }
+ int flags = (wait_for_device || and_metadata) ? MS_SYNC : MS_ASYNC;
+ if(-1 == ::msync(addr, bytes, flags))
+ {
+ return posix_error();
+ }
+ // Don't fsync temporary inodes
+ if((_section->backing() != nullptr) && (wait_for_device || and_metadata))
+ {
+ reqs.offset += _offset;
+ return _section->backing()->barrier(reqs, wait_for_device, and_metadata, d);
+ }
+ return {reqs.buffers};
+}
+
+
+static inline result<void *> do_mmap(native_handle_type &nativeh, void *ataddr, int extra_flags, section_handle *section, map_handle::size_type &bytes, map_handle::extent_type offset, section_handle::flag _flag) noexcept
+{
+ bool have_backing = (section != nullptr);
+ int prot = 0, flags = have_backing ? MAP_SHARED : (MAP_PRIVATE | MAP_ANONYMOUS);
+ void *addr = nullptr;
+ if(_flag == section_handle::flag::none)
+ {
+ prot |= PROT_NONE;
+ }
+ else if(_flag & section_handle::flag::cow)
+ {
+ prot |= PROT_READ | PROT_WRITE;
+ flags &= ~MAP_SHARED;
+ flags |= MAP_PRIVATE;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
+ }
+ else if(_flag & section_handle::flag::write)
+ {
+ prot |= PROT_READ | PROT_WRITE;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
+ }
+ else if(_flag & section_handle::flag::read)
+ {
+ prot |= PROT_READ;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable;
+ }
+ if(_flag & section_handle::flag::execute)
+ {
+ prot |= PROT_EXEC;
+ }
+#ifdef MAP_NORESERVE
+ if(_flag & section_handle::flag::nocommit)
+ {
+ flags |= MAP_NORESERVE;
+ }
+#endif
+#ifdef MAP_POPULATE
+ if(_flag & section_handle::flag::prefault)
+ {
+ flags |= MAP_POPULATE;
+ }
+#endif
+#ifdef MAP_PREFAULT_READ
+ if(_flag & section_handle::flag::prefault)
+ flags |= MAP_PREFAULT_READ;
+#endif
+#ifdef MAP_NOSYNC
+ if(have_backing && section->backing() != nullptr && (section->backing()->kernel_caching() == handle::caching::temporary))
+ flags |= MAP_NOSYNC;
+#endif
+ flags |= extra_flags;
+// printf("mmap(%p, %u, %d, %d, %d, %u)\n", ataddr, (unsigned) bytes, prot, flags, have_backing ? section->native_handle().fd : -1, (unsigned) offset);
+#ifdef MAP_SYNC // Linux kernel 4.15 or later only
+ // If backed by a file into persistent shared memory, ask the kernel to use persistent memory safe semantics
+ if(have_backing && section->is_nvram() && (flags & MAP_SHARED) != 0)
+ {
+ int flagscopy = flags & ~MAP_SHARED;
+ flagscopy |= MAP_SHARED_VALIDATE | MAP_SYNC;
+ addr = ::mmap(ataddr, bytes, prot, flagscopy, section->native_handle().fd, offset);
+ }
+#endif
+ if(addr == nullptr)
+ {
+ addr = ::mmap(ataddr, bytes, prot, flags, have_backing ? section->native_handle().fd : -1, offset);
+ }
+ // printf("%d mmap %p-%p\n", getpid(), addr, (char *) addr+bytes);
+ if(MAP_FAILED == addr) // NOLINT
+ {
+ return posix_error();
+ }
+#if 0 // not implemented yet, not seen any benefit over setting this at the fd level
+ if(have_backing && ((flags & map_handle::flag::disable_prefetching) || (flags & map_handle::flag::maximum_prefetching)))
+ {
+ int advice = (flags & map_handle::flag::disable_prefetching) ? MADV_RANDOM : MADV_SEQUENTIAL;
+ if(-1 == ::madvise(addr, bytes, advice))
+ return posix_error();
+ }
+#endif
+ return addr;
+}
+
+result<map_handle> map_handle::map(size_type bytes, section_handle::flag _flag) noexcept
+{
+ if(bytes == 0u)
+ {
+ return errc::argument_out_of_domain;
+ }
+ bytes = utils::round_up_to_page_size(bytes);
+ result<map_handle> ret(map_handle(nullptr));
+ native_handle_type &nativeh = ret.value()._v;
+ OUTCOME_TRY(addr, do_mmap(nativeh, nullptr, 0, nullptr, bytes, 0, _flag));
+ ret.value()._addr = static_cast<byte *>(addr);
+ ret.value()._reservation = bytes;
+ ret.value()._length = bytes;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ return ret;
+}
+
+result<map_handle> map_handle::map(section_handle &section, size_type bytes, extent_type offset, section_handle::flag _flag) noexcept
+{
+ OUTCOME_TRY(length, section.length()); // length of the backing file
+ if(bytes == 0u)
+ {
+ bytes = length - offset;
+ }
+ result<map_handle> ret{map_handle(&section)};
+ native_handle_type &nativeh = ret.value()._v;
+ OUTCOME_TRY(addr, do_mmap(nativeh, nullptr, 0, &section, bytes, offset, _flag));
+ ret.value()._addr = static_cast<byte *>(addr);
+ ret.value()._offset = offset;
+ ret.value()._reservation = bytes;
+ ret.value()._length = (length - offset < bytes) ? (length - offset) : bytes; // length of backing, not reservation
+ // Make my handle borrow the native handle of my backing storage
+ ret.value()._v.fd = section.native_handle().fd;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ return ret;
+}
+
+result<map_handle::size_type> map_handle::truncate(size_type newsize, bool permit_relocation) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ extent_type length = _length;
+ if(_section != nullptr)
+ {
+ OUTCOME_TRY(length_, _section->length()); // length of the backing file
+ length = length_;
+ }
+ newsize = utils::round_up_to_page_size(newsize);
+ if(newsize == _reservation)
+ {
+ return success();
+ }
+ if(newsize == 0)
+ {
+ if(-1 == ::munmap(_addr, _length))
+ {
+ return posix_error();
+ }
+ _addr = nullptr;
+ _reservation = 0;
+ _length = 0;
+ return 0;
+ }
+ if(_addr == nullptr)
+ {
+ OUTCOME_TRY(addr, do_mmap(_v, nullptr, 0, _section, newsize, _offset, _flag));
+ _addr = static_cast<byte *>(addr);
+ _reservation = newsize;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize; // length of backing, not reservation
+ return newsize;
+ }
+#ifdef __linux__
+ // Dead easy on Linux
+ void *newaddr = ::mremap(_addr, _reservation, newsize, permit_relocation ? MREMAP_MAYMOVE : 0);
+ if(MAP_FAILED == newaddr)
+ {
+ return posix_error();
+ }
+ _addr = static_cast<byte *>(newaddr);
+ _reservation = newsize;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize; // length of backing, not reservation
+ return newsize;
+#else
+ if(newsize > _length)
+ {
+#if defined(MAP_EXCL) // BSD type systems
+ byte *addrafter = _addr + _reservation;
+ size_type bytes = newsize - _reservation;
+ extent_type offset = _offset + _reservation;
+ OUTCOME_TRY(addr, do_mmap(_v, addrafter, MAP_FIXED | MAP_EXCL, _section, bytes, offset, _flag));
+ _reservation = newsize;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize; // length of backing, not reservation
+ return newsize;
+#else // generic POSIX, inefficient
+ byte *addrafter = _addr + _reservation;
+ size_type bytes = newsize - _reservation;
+ extent_type offset = _offset + _reservation;
+ OUTCOME_TRY(addr, do_mmap(_v, addrafter, 0, _section, bytes, offset, _flag));
+ if(addr != addrafter)
+ {
+ ::munmap(addr, bytes);
+ return errc::not_enough_memory;
+ }
+ _reservation = newsize;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize; // length of backing, not reservation
+ return newsize;
+#endif
+ }
+ // Shrink the map
+ if(-1 == ::munmap(_addr + newsize, _length - newsize))
+ {
+ return posix_error();
+ }
+ _reservation = newsize;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize;
+ return newsize;
+#endif
+}
+
+result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_handle::flag flag) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+ // Set permissions on the pages
+ region = utils::round_to_page_size(region);
+ extent_type offset = _offset + (region.data - _addr);
+ size_type bytes = region.len;
+ OUTCOME_TRYV(do_mmap(_v, region.data, MAP_FIXED, _section, bytes, offset, flag));
+ // Tell the kernel we will be using these pages soon
+ if(-1 == ::madvise(region.data, region.len, MADV_WILLNEED))
+ {
+ return posix_error();
+ }
+ return region;
+}
+
+result<map_handle::buffer_type> map_handle::decommit(buffer_type region) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+ region = utils::round_to_page_size(region);
+ // Tell the kernel to kick these pages into storage
+ if(-1 == ::madvise(region.data, region.len, MADV_DONTNEED))
+ {
+ return posix_error();
+ }
+ // Set permissions on the pages to no access
+ extent_type offset = _offset + (region.data - _addr);
+ size_type bytes = region.len;
+ OUTCOME_TRYV(do_mmap(_v, region.data, MAP_FIXED, _section, bytes, offset, section_handle::flag::none));
+ return region;
+}
+
+result<void> map_handle::zero_memory(buffer_type region) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+#ifdef MADV_REMOVE
+ buffer_type page_region{utils::round_up_to_page_size(region.data), utils::round_down_to_page_size(region.len)};
+ // Zero contents and punch a hole in any backing storage
+ if((page_region.len != 0u) && -1 != ::madvise(page_region.data, page_region.len, MADV_REMOVE))
+ {
+ memset(region.data, 0, page_region.data - region.data);
+ memset(page_region.data + page_region.len, 0, (region.data + region.len) - (page_region.data + page_region.len));
+ return success();
+ }
+#endif
+ //! Only Linux implements syscall zero(), and it's covered by MADV_REMOVE already
+ memset(region.data, 0, region.len);
+ return success();
+}
+
+result<span<map_handle::buffer_type>> map_handle::prefetch(span<buffer_type> regions) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(0);
+ for(const auto &region : regions)
+ {
+ if(-1 == ::madvise(region.data, region.len, MADV_WILLNEED))
+ {
+ return posix_error();
+ }
+ }
+ return regions;
+}
+
+result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(0);
+ region = utils::round_to_page_size(region);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+#ifdef MADV_FREE
+ // Lightweight unset of dirty bit for these pages. Needs FreeBSD or very recent Linux.
+ if(-1 != ::madvise(region.data, region.len, MADV_FREE))
+ return region;
+#endif
+#ifdef MADV_REMOVE
+ // This is rather heavy weight in that it also punches a hole in any backing storage
+ // but it works on Linux for donkey's years
+ if(-1 != ::madvise(region.data, region.len, MADV_REMOVE))
+ {
+ return region;
+ }
+#endif
+ // No support on this platform
+ region.len = 0;
+ return region;
+}
+
+map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buffers_type> reqs, deadline /*d*/) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ byte *addr = _addr + reqs.offset;
+ size_type togo = reqs.offset < _length ? static_cast<size_type>(_length - reqs.offset) : 0;
+ for(buffer_type &req : reqs.buffers)
+ {
+ if(togo != 0u)
+ {
+ req.data = addr;
+ if(req.len > togo)
+ {
+ req.len = togo;
+ }
+ addr += req.len;
+ togo -= req.len;
+ }
+ else
+ {
+ req.len = 0;
+ }
+ }
+ return reqs.buffers;
+}
+
+map_handle::io_result<map_handle::const_buffers_type> map_handle::write(io_request<const_buffers_type> reqs, deadline /*d*/) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ byte *addr = _addr + reqs.offset;
+ size_type togo = reqs.offset < _length ? static_cast<size_type>(_length - reqs.offset) : 0;
+ if(QUICKCPPLIB_NAMESPACE::signal_guard::signal_guard(QUICKCPPLIB_NAMESPACE::signal_guard::signalc::undefined_memory_access,
+ [&] {
+ for(const_buffer_type &req : reqs.buffers)
+ {
+ if(togo != 0u)
+ {
+ if(req.len > togo)
+ {
+ req.len = togo;
+ }
+ memcpy(addr, req.data, req.len);
+ req.data = addr;
+ addr += req.len;
+ togo -= req.len;
+ }
+ else
+ {
+ req.len = 0;
+ }
+ }
+ return false;
+ },
+ [&](const QUICKCPPLIB_NAMESPACE::signal_guard::raised_signal_info &_info) {
+ auto &info = const_cast<QUICKCPPLIB_NAMESPACE::signal_guard::raised_signal_info &>(_info);
+ auto *causingaddr = (byte *) info.address();
+ if(causingaddr < _addr || causingaddr >= (_addr + _length))
+ {
+ // Not caused by this map
+ QUICKCPPLIB_NAMESPACE::signal_guard::thread_local_raise_signal(info.signal(), info.raw_info(), info.raw_context());
+ abort();
+ }
+ return true;
+ }))
+ {
+ return errc::no_space_on_device;
+ }
+ return reqs.buffers;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/mapped_file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/mapped_file_handle.ipp
new file mode 100644
index 00000000..484bbdd2
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/mapped_file_handle.ipp
@@ -0,0 +1,162 @@
+/* An mapped handle to a file
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../mapped_file_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<mapped_file_handle::size_type> mapped_file_handle::reserve(size_type reservation) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ OUTCOME_TRY(length, underlying_file_maximum_extent());
+ if(length == 0)
+ {
+ // Not portable to map an empty file, so fail
+ return errc::invalid_seek;
+ }
+ if(reservation == 0)
+ {
+ reservation = length;
+ }
+ reservation = utils::round_up_to_page_size(reservation);
+ if(!_sh.is_valid())
+ {
+ section_handle::flag sectionflags = section_handle::flag::readwrite;
+ OUTCOME_TRY(sh, section_handle::section(*this, length, sectionflags));
+ _sh = std::move(sh);
+ }
+ if(_mh.is_valid() && reservation == _mh.length())
+ {
+ return reservation;
+ }
+ // Reserve the full reservation in address space
+ section_handle::flag mapflags = section_handle::flag::nocommit | section_handle::flag::read;
+ if(this->is_writable())
+ {
+ mapflags |= section_handle::flag::write;
+ }
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRY(mh, map_handle::map(_sh, reservation, 0, mapflags));
+ _mh = std::move(mh);
+ _reservation = reservation;
+ return reservation;
+}
+
+result<void> mapped_file_handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_mh.is_valid())
+ {
+ OUTCOME_TRYV(_mh.close());
+ }
+ if(_sh.is_valid())
+ {
+ OUTCOME_TRYV(_sh.close());
+ }
+ return file_handle::close();
+}
+native_handle_type mapped_file_handle::release() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_mh.is_valid())
+ {
+ (void) _mh.close();
+ }
+ if(_sh.is_valid())
+ {
+ (void) _sh.close();
+ }
+ return file_handle::release();
+}
+
+result<mapped_file_handle::extent_type> mapped_file_handle::truncate(extent_type newsize) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // Release all maps and sections and truncate the backing file to zero
+ if(newsize == 0)
+ {
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRYV(_sh.close());
+ return file_handle::truncate(newsize);
+ }
+ if(!_sh.is_valid())
+ {
+ OUTCOME_TRY(ret, file_handle::truncate(newsize));
+ // Reserve now we have resized, it'll create a new section for the new size
+ OUTCOME_TRYV(reserve(_reservation));
+ return ret;
+ }
+ // On POSIX the section's size is the file's size
+ OUTCOME_TRY(size, _sh.length());
+ if(size != newsize)
+ {
+ // If we are making this smaller, we must discard the pages about to get truncated
+ // otherwise some kernels keep them around until last fd close, effectively leaking them
+ if(newsize < size)
+ {
+ byte *start = utils::round_up_to_page_size(_mh.address() + newsize);
+ byte *end = utils::round_up_to_page_size(_mh.address() + size);
+ (void) _mh.do_not_store({start, static_cast<size_t>(end - start)});
+ }
+ // Resize the file, on unified page cache kernels it'll map any new pages into the reserved map
+ OUTCOME_TRYV(file_handle::truncate(newsize));
+ // Have we exceeded the reservation? If so, reserve a new reservation which will recreate the map.
+ if(newsize > _reservation)
+ {
+ OUTCOME_TRY(ret, reserve(newsize));
+ return ret;
+ }
+ size = newsize;
+ }
+ // Adjust the map to reflect the new size of the section
+ _mh._length = size;
+ return newsize;
+}
+
+result<mapped_file_handle::extent_type> mapped_file_handle::update_map() noexcept
+{
+ OUTCOME_TRY(length, underlying_file_maximum_extent());
+ if(length > _reservation)
+ {
+ // This API never exceeds the reservation
+ length = _reservation;
+ }
+ if(length == 0)
+ {
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRYV(_sh.close());
+ return length;
+ }
+ if(!_sh.is_valid())
+ {
+ OUTCOME_TRYV(reserve(_reservation));
+ return length;
+ }
+ // Adjust the map to reflect the new size of the section
+ _mh._length = length;
+ return length;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/path_discovery.ipp b/include/llfio/v2.0/detail/impl/posix/path_discovery.ipp
new file mode 100644
index 00000000..0d072a29
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/path_discovery.ipp
@@ -0,0 +1,145 @@
+/* Discovery of various useful filesystem paths
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_PATH_DISCOVERY_INCLUDING
+#error Must be included by ../path_discovery.ipp only
+#endif
+
+#include "../../../algorithm/mapped_span.hpp"
+
+#include <pwd.h>
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace path_discovery
+{
+ std::vector<std::pair<discovered_path::source_type, _store::_discovered_path>> _all_temporary_directories()
+ {
+ std::vector<std::pair<discovered_path::source_type, _store::_discovered_path>> ret;
+ filesystem::path::string_type buffer;
+ buffer.resize(PATH_MAX);
+ // Only observe environment variables if not a SUID or SGID situation
+ // FIXME? Is this actually enough? What about the non-standard saved uid/gid?
+ // Should I be checking if my executable is SUGID and its owning user is not mine?
+ if(getuid() == geteuid() && getgid() == getegid())
+ {
+ // Note that XDG_RUNTIME_DIR is the systemd runtime directory for the current user, usually mounted with tmpfs
+ // XDG_CACHE_HOME is the systemd cache directory for the current user, usually at $HOME/.cache
+ static const char *variables[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", "XDG_RUNTIME_DIR", "XDG_CACHE_HOME"};
+ for(auto &variable : variables)
+ {
+ const char *env = getenv(variable);
+ if(env != nullptr)
+ {
+ ret.emplace_back(discovered_path::source_type::environment, env);
+ }
+ }
+ // Also try $HOME/.cache
+ const char *env = getenv("HOME");
+ if(env != nullptr)
+ {
+ buffer = env;
+ buffer.append("/.cache");
+ ret.emplace_back(discovered_path::source_type::environment, buffer);
+ }
+ }
+
+ // Parse /etc/passwd for the effective user's home directory
+ // We do it by hand seeing as, amazingly, getpwent_r() isn't actually threadsafe :(
+ {
+ auto _passwdh = mapped_file_handle::mapped_file({}, "/etc/passwd");
+ if(!_passwdh)
+ {
+ std::string msg("path_discovery::all_temporary_directories() failed to open /etc/passwd due to ");
+ msg.append(_passwdh.error().message().c_str());
+ AFIO_LOG_WARN(nullptr, msg.c_str());
+ }
+ else
+ {
+ algorithm::mapped_span<const char> passwd(_passwdh.value());
+ /* This will consist of lines of the form:
+
+ jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
+ ned:x:1000:1000:"",,,:/home/ned:/bin/bash
+ # Comments and blank lines also possible
+
+ */
+ string_view passwdfile(passwd.data(), passwd.size());
+ size_t linestart = 0;
+ do
+ {
+ string_view line(passwdfile.substr(linestart));
+ if(auto lineend = line.find(10))
+ {
+ line = line.substr(0, lineend);
+ }
+ linestart += line.size() + 1;
+ // uid is two colons in
+ size_t colon = line.find(':');
+ if(colon != string_view::npos)
+ {
+ colon = line.find(':', colon + 1);
+ if(colon != string_view::npos)
+ {
+ long uid = strtol(line.data() + 1, nullptr, 10);
+ if(uid == geteuid())
+ {
+ // home directory is two colons from end
+ size_t homeend = line.rfind(':');
+ if(homeend != string_view::npos)
+ {
+ colon = line.rfind(':', homeend - 1);
+ if(colon != string_view::npos)
+ {
+ auto homedir = line.substr(colon + 1, homeend - colon - 1);
+ buffer.assign(homedir.data(), homedir.size());
+ buffer.append("/.cache");
+ ret.emplace_back(discovered_path::source_type::system, buffer);
+ if(-1 == ::access(buffer.c_str(), F_OK))
+ {
+ ::mkdir(buffer.c_str(), 0700); // only user has access
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ } while(linestart < passwd.size());
+ }
+ }
+
+ // If everything earlier failed e.g. if our environment block is zeroed,
+ // fall back to /tmp and then /var/tmp, the last of which should succeed even if tmpfs is not mounted
+ ret.emplace_back(discovered_path::source_type::hardcoded, "/tmp");
+ ret.emplace_back(discovered_path::source_type::hardcoded, "/var/tmp");
+ // Effective user, not real user, to not create files owned by a different user
+ buffer = "/run/user/" + std::to_string(geteuid());
+ ret.emplace_back(discovered_path::source_type::hardcoded, buffer);
+ ret.emplace_back(discovered_path::source_type::hardcoded, "/run/shm");
+ return ret;
+ }
+} // namespace path_discovery
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/path_handle.ipp b/include/llfio/v2.0/detail/impl/posix/path_handle.ipp
new file mode 100644
index 00000000..80a780e0
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/path_handle.ipp
@@ -0,0 +1,61 @@
+/* A handle to a filesystem location
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Jul 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../path_handle.hpp"
+
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<path_handle> path_handle::path(const path_handle &base, path_handle::path_view_type path) noexcept
+{
+ result<path_handle> ret{path_handle(native_handle_type())};
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::directory;
+ int attribs = O_CLOEXEC | O_RDONLY;
+#ifdef O_DIRECTORY
+ attribs |= O_DIRECTORY;
+#endif
+#ifdef O_PATH
+ // Linux provides this extension opening a super light weight fd to just an anchor on the filing system
+ attribs |= O_PATH;
+#endif
+ path_view::c_str zpath(path);
+ if(base.is_valid())
+ {
+ nativeh.fd = ::openat(base.native_handle().fd, zpath.buffer, attribs);
+ }
+ else
+ {
+ nativeh.fd = ::open(zpath.buffer, attribs);
+ }
+ if(-1 == nativeh.fd)
+ {
+ return posix_error();
+ }
+ return ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/stat.ipp b/include/llfio/v2.0/detail/impl/posix/stat.ipp
new file mode 100644
index 00000000..07b6c310
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/stat.ipp
@@ -0,0 +1,225 @@
+/* Information about a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (3 commits)
+File Created: Apr 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "../../../stat.hpp"
+
+#include <sys/stat.h>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+static inline filesystem::file_type to_st_type(uint16_t mode)
+{
+ switch(mode & S_IFMT)
+ {
+ case S_IFBLK:
+ return filesystem::file_type::block;
+ case S_IFCHR:
+ return filesystem::file_type::character;
+ case S_IFDIR:
+ return filesystem::file_type::directory;
+ case S_IFIFO:
+ return filesystem::file_type::fifo;
+ case S_IFLNK:
+ return filesystem::file_type::symlink;
+ case S_IFREG:
+ return filesystem::file_type::regular;
+ case S_IFSOCK:
+ return filesystem::file_type::socket;
+ default:
+ return filesystem::file_type::unknown;
+ }
+}
+
+static inline std::chrono::system_clock::time_point to_timepoint(struct timespec ts)
+{
+ // Need to have this self-adapt to the STL being used
+ static constexpr unsigned long long STL_TICKS_PER_SEC = static_cast<unsigned long long>(std::chrono::system_clock::period::den) / std::chrono::system_clock::period::num;
+ static constexpr unsigned long long multiplier = STL_TICKS_PER_SEC >= 1000000000ULL ? STL_TICKS_PER_SEC / 1000000000ULL : 1;
+ static constexpr unsigned long long divider = STL_TICKS_PER_SEC >= 1000000000ULL ? 1 : 1000000000ULL / STL_TICKS_PER_SEC;
+ // For speed we make the big assumption that the STL's system_clock is based on the time_t epoch 1st Jan 1970.
+ std::chrono::system_clock::duration duration(ts.tv_sec * STL_TICKS_PER_SEC + ts.tv_nsec * multiplier / divider);
+ return std::chrono::system_clock::time_point(duration);
+}
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> stat_t::fill(const handle &h, stat_t::want wanted) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(&h);
+ struct stat s
+ {
+ };
+ memset(&s, 0, sizeof(s));
+ size_t ret = 0;
+
+ if(-1 == ::fstat(h.native_handle().fd, &s))
+ {
+ return posix_error();
+ }
+ if(wanted & want::dev)
+ {
+ st_dev = s.st_dev;
+ ++ret;
+ }
+ if(wanted & want::ino)
+ {
+ st_ino = s.st_ino;
+ ++ret;
+ }
+ if(wanted & want::type)
+ {
+ st_type = to_st_type(s.st_mode);
+ ++ret;
+ }
+ if(wanted & want::perms)
+ {
+ st_perms = s.st_mode & 0xfff;
+ ++ret;
+ }
+ if(wanted & want::nlink)
+ {
+ st_nlink = s.st_nlink;
+ ++ret;
+ }
+ if(wanted & want::uid)
+ {
+ st_uid = s.st_uid;
+ ++ret;
+ }
+ if(wanted & want::gid)
+ {
+ st_gid = s.st_gid;
+ ++ret;
+ }
+ if(wanted & want::rdev)
+ {
+ st_rdev = s.st_rdev;
+ ++ret;
+ }
+#ifdef __ANDROID__
+ if(wanted & want::atim)
+ {
+ st_atim = to_timepoint(*((struct timespec *) &s.st_atime));
+ ++ret;
+ }
+ if(wanted & want::mtim)
+ {
+ st_mtim = to_timepoint(*((struct timespec *) &s.st_mtime));
+ ++ret;
+ }
+ if(wanted & want::ctim)
+ {
+ st_ctim = to_timepoint(*((struct timespec *) &s.st_ctime));
+ ++ret;
+ }
+#elif defined(__APPLE__)
+ if(wanted & want::atim)
+ {
+ st_atim = to_timepoint(s.st_atimespec);
+ ++ret;
+ }
+ if(wanted & want::mtim)
+ {
+ st_mtim = to_timepoint(s.st_mtimespec);
+ ++ret;
+ }
+ if(wanted & want::ctim)
+ {
+ st_ctim = to_timepoint(s.st_ctimespec);
+ ++ret;
+ }
+#else // Linux and BSD
+ if(wanted & want::atim)
+ {
+ st_atim = to_timepoint(s.st_atim);
+ ++ret;
+ }
+ if(wanted & want::mtim)
+ {
+ st_mtim = to_timepoint(s.st_mtim);
+ ++ret;
+ }
+ if(wanted & want::ctim)
+ {
+ st_ctim = to_timepoint(s.st_ctim);
+ ++ret;
+ }
+#endif
+ if(wanted & want::size)
+ {
+ st_size = s.st_size;
+ ++ret;
+ }
+ if(wanted & want::allocated)
+ {
+ st_allocated = static_cast<handle::extent_type>(s.st_blocks) * 512;
+ ++ret;
+ }
+ if(wanted & want::blocks)
+ {
+ st_blocks = s.st_blocks;
+ ++ret;
+ }
+ if(wanted & want::blksize)
+ {
+ st_blksize = s.st_blksize;
+ ++ret;
+ }
+#ifdef HAVE_STAT_FLAGS
+ if(wanted & want::flags)
+ {
+ st_flags = s.st_flags;
+ ++ret;
+ }
+#endif
+#ifdef HAVE_STAT_GEN
+ if(wanted & want::gen)
+ {
+ st_gen = s.st_gen;
+ ++ret;
+ }
+#endif
+#ifdef HAVE_BIRTHTIMESPEC
+#if defined(__APPLE__)
+ if(wanted & want::birthtim)
+ {
+ st_birthtim = to_timepoint(s.st_birthtimespec);
+ ++ret;
+ }
+#else
+ if(wanted & want::birthtim)
+ {
+ st_birthtim = to_timepoint(s.st_birthtim);
+ ++ret;
+ }
+#endif
+#endif
+ if(wanted & want::sparse)
+ {
+ st_sparse = static_cast<unsigned int>((static_cast<handle::extent_type>(s.st_blocks) * 512) < static_cast<handle::extent_type>(s.st_size));
+ ++ret;
+ }
+ return ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/statfs.ipp b/include/llfio/v2.0/detail/impl/posix/statfs.ipp
new file mode 100644
index 00000000..2f94ab8e
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/statfs.ipp
@@ -0,0 +1,300 @@
+/* Information about the volume storing a file
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
+File Created: Jan 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "../../../statfs.hpp"
+
+#include <sys/mount.h>
+#ifdef __linux__
+#include <mntent.h>
+#include <sys/statfs.h>
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> statfs_t::fill(const handle &h, statfs_t::want wanted) noexcept
+{
+ size_t ret = 0;
+#ifdef __linux__
+ struct statfs64 s
+ {
+ };
+ memset(&s, 0, sizeof(s));
+ if(-1 == fstatfs64(h.native_handle().fd, &s))
+ {
+ return posix_error();
+ }
+ if(!!(wanted & want::bsize))
+ {
+ f_bsize = s.f_bsize;
+ ++ret;
+ }
+ if(!!(wanted & want::iosize))
+ {
+ f_iosize = s.f_frsize;
+ ++ret;
+ }
+ if(!!(wanted & want::blocks))
+ {
+ f_blocks = s.f_blocks;
+ ++ret;
+ }
+ if(!!(wanted & want::bfree))
+ {
+ f_bfree = s.f_bfree;
+ ++ret;
+ }
+ if(!!(wanted & want::bavail))
+ {
+ f_bavail = s.f_bavail;
+ ++ret;
+ }
+ if(!!(wanted & want::files))
+ {
+ f_files = s.f_files;
+ ++ret;
+ }
+ if(!!(wanted & want::ffree))
+ {
+ f_ffree = s.f_ffree;
+ ++ret;
+ }
+ if(!!(wanted & want::namemax))
+ {
+ f_namemax = s.f_namelen;
+ ++ret;
+ }
+ // if(!!(wanted&&want::owner)) { f_owner =s.f_owner; ++ret; }
+ if(!!(wanted & want::fsid))
+ {
+ f_fsid[0] = static_cast<unsigned>(s.f_fsid.__val[0]);
+ f_fsid[1] = static_cast<unsigned>(s.f_fsid.__val[1]);
+ ++ret;
+ }
+ if(!!(wanted & want::flags) || !!(wanted & want::fstypename) || !!(wanted & want::mntfromname) || !!(wanted & want::mntonname))
+ {
+ try
+ {
+ struct mountentry
+ {
+ std::string mnt_fsname, mnt_dir, mnt_type, mnt_opts;
+ mountentry(const char *a, const char *b, const char *c, const char *d)
+ : mnt_fsname(a)
+ , mnt_dir(b)
+ , mnt_type(c)
+ , mnt_opts(d)
+ {
+ }
+ };
+ std::vector<std::pair<mountentry, struct statfs64>> mountentries;
+ {
+ // Need to parse mount options on Linux
+ FILE *mtab = setmntent("/etc/mtab", "r");
+ if(mtab == nullptr)
+ {
+ mtab = setmntent("/proc/mounts", "r");
+ }
+ if(mtab == nullptr)
+ {
+ return posix_error();
+ }
+ auto unmtab = undoer([mtab] { endmntent(mtab); });
+ struct mntent m
+ {
+ };
+ char buffer[32768];
+ while(getmntent_r(mtab, &m, buffer, sizeof(buffer)) != nullptr)
+ {
+ struct statfs64 temp
+ {
+ };
+ memset(&temp, 0, sizeof(temp));
+ // std::cout << m.mnt_fsname << "," << m.mnt_dir << "," << m.mnt_type << "," << m.mnt_opts << std::endl;
+ if(0 == statfs64(m.mnt_dir, &temp))
+ {
+ // std::cout << " " << temp.f_fsid.__val[0] << temp.f_fsid.__val[1] << " =? " << s.f_fsid.__val[0] << s.f_fsid.__val[1] << std::endl;
+ if(temp.f_type == s.f_type && (memcmp(&temp.f_fsid, &s.f_fsid, sizeof(s.f_fsid)) == 0))
+ {
+ mountentries.emplace_back(mountentry(m.mnt_fsname, m.mnt_dir, m.mnt_type, m.mnt_opts), temp);
+ }
+ }
+ }
+ }
+#ifndef AFIO_COMPILING_FOR_GCOV
+ if(mountentries.empty())
+ {
+ return errc::no_such_file_or_directory;
+ }
+ // Choose the mount entry with the most closely matching statfs. You can't choose
+ // exclusively based on mount point because of bind mounts
+ if(mountentries.size() > 1)
+ {
+ std::vector<std::pair<size_t, size_t>> scores(mountentries.size());
+ for(size_t n = 0; n < mountentries.size(); n++)
+ {
+ const auto *a = reinterpret_cast<const char *>(&mountentries[n].second);
+ const auto *b = reinterpret_cast<const char *>(&s);
+ scores[n].first = 0;
+ for(size_t x = 0; x < sizeof(struct statfs64); x++)
+ {
+ scores[n].first += abs(a[x] - b[x]);
+ }
+ scores[n].second = n;
+ }
+ std::sort(scores.begin(), scores.end());
+ auto temp(std::move(mountentries[scores.front().second]));
+ mountentries.clear();
+ mountentries.push_back(std::move(temp));
+ }
+#endif
+ if(!!(wanted & want::flags))
+ {
+ f_flags.rdonly = static_cast<uint32_t>((s.f_flags & MS_RDONLY) != 0);
+ f_flags.noexec = static_cast<uint32_t>((s.f_flags & MS_NOEXEC) != 0);
+ f_flags.nosuid = static_cast<uint32_t>((s.f_flags & MS_NOSUID) != 0);
+ f_flags.acls = static_cast<uint32_t>(std::string::npos != mountentries.front().first.mnt_opts.find("acl") && std::string::npos == mountentries.front().first.mnt_opts.find("noacl"));
+ f_flags.xattr = static_cast<uint32_t>(std::string::npos != mountentries.front().first.mnt_opts.find("xattr") && std::string::npos == mountentries.front().first.mnt_opts.find("nouser_xattr"));
+ // out.f_flags.compression=0;
+ // Those filing systems supporting FALLOC_FL_PUNCH_HOLE
+ f_flags.extents = static_cast<uint32_t>(mountentries.front().first.mnt_type == "btrfs" || mountentries.front().first.mnt_type == "ext4" || mountentries.front().first.mnt_type == "xfs" || mountentries.front().first.mnt_type == "tmpfs");
+ ++ret;
+ }
+ if(!!(wanted & want::fstypename))
+ {
+ f_fstypename = mountentries.front().first.mnt_type;
+ ++ret;
+ }
+ if(!!(wanted & want::mntfromname))
+ {
+ f_mntfromname = mountentries.front().first.mnt_fsname;
+ ++ret;
+ }
+ if(!!(wanted & want::mntonname))
+ {
+ f_mntonname = mountentries.front().first.mnt_dir;
+ ++ret;
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+#else
+ struct statfs s;
+ if(-1 == fstatfs(h.native_handle().fd, &s))
+ return posix_error();
+ if(!!(wanted & want::flags))
+ {
+ f_flags.rdonly = !!(s.f_flags & MNT_RDONLY);
+ f_flags.noexec = !!(s.f_flags & MNT_NOEXEC);
+ f_flags.nosuid = !!(s.f_flags & MNT_NOSUID);
+ f_flags.acls = 0;
+#if defined(MNT_ACLS) && defined(MNT_NFS4ACLS)
+ f_flags.acls = !!(s.f_flags & (MNT_ACLS | MNT_NFS4ACLS));
+#endif
+ f_flags.xattr = 1; // UFS and ZFS support xattr. TODO FIXME actually calculate this, zfs get xattr <f_mntfromname> would do it.
+ f_flags.compression = !strcmp(s.f_fstypename, "zfs");
+ f_flags.extents = !strcmp(s.f_fstypename, "ufs") || !strcmp(s.f_fstypename, "zfs");
+ ++ret;
+ }
+ if(!!(wanted & want::bsize))
+ {
+ f_bsize = s.f_bsize;
+ ++ret;
+ }
+ if(!!(wanted & want::iosize))
+ {
+ f_iosize = s.f_iosize;
+ ++ret;
+ }
+ if(!!(wanted & want::blocks))
+ {
+ f_blocks = s.f_blocks;
+ ++ret;
+ }
+ if(!!(wanted & want::bfree))
+ {
+ f_bfree = s.f_bfree;
+ ++ret;
+ }
+ if(!!(wanted & want::bavail))
+ {
+ f_bavail = s.f_bavail;
+ ++ret;
+ }
+ if(!!(wanted & want::files))
+ {
+ f_files = s.f_files;
+ ++ret;
+ }
+ if(!!(wanted & want::ffree))
+ {
+ f_ffree = s.f_ffree;
+ ++ret;
+ }
+#ifdef __APPLE__
+ if(!!(wanted & want::namemax))
+ {
+ f_namemax = 255;
+ ++ret;
+ }
+#else
+ if(!!(wanted & want::namemax))
+ {
+ f_namemax = s.f_namemax;
+ ++ret;
+ }
+#endif
+ if(!!(wanted & want::owner))
+ {
+ f_owner = s.f_owner;
+ ++ret;
+ }
+ if(!!(wanted & want::fsid))
+ {
+ f_fsid[0] = (unsigned) s.f_fsid.val[0];
+ f_fsid[1] = (unsigned) s.f_fsid.val[1];
+ ++ret;
+ }
+ if(!!(wanted & want::fstypename))
+ {
+ f_fstypename = s.f_fstypename;
+ ++ret;
+ }
+ if(!!(wanted & want::mntfromname))
+ {
+ f_mntfromname = s.f_mntfromname;
+ ++ret;
+ }
+ if(!!(wanted & want::mntonname))
+ {
+ f_mntonname = s.f_mntonname;
+ ++ret;
+ }
+#endif
+ return ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/storage_profile.ipp b/include/llfio/v2.0/detail/impl/posix/storage_profile.ipp
new file mode 100644
index 00000000..a92e0470
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/storage_profile.ipp
@@ -0,0 +1,325 @@
+/* A profile of an OS and filing system
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
+File Created: Jan 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "../../../storage_profile.hpp"
+
+#include <sys/ioctl.h>
+#include <sys/utsname.h> // for uname()
+#include <unistd.h>
+#if defined(__linux__)
+#include <linux/fs.h>
+#else
+#include <sys/disk.h>
+#include <sys/sysctl.h>
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace storage_profile
+{
+ namespace system
+ {
+ // OS name, version
+ outcome<void> os(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ static std::string os_name, os_ver;
+ if(!os_name.empty())
+ {
+ sp.os_name.value = os_name;
+ sp.os_ver.value = os_ver;
+ }
+ else
+ {
+ try
+ {
+ struct utsname name
+ {
+ };
+ memset(&name, 0, sizeof(name));
+ if(uname(&name) < 0)
+ {
+ return posix_error();
+ }
+ sp.os_name.value = os_name = name.sysname;
+ sp.os_ver.value = os_ver = name.release;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ }
+ return success();
+ }
+
+ // CPU name, architecture, physical cores
+ outcome<void> cpu(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ static std::string cpu_name, cpu_architecture;
+ static unsigned cpu_physical_cores;
+ if(!cpu_name.empty())
+ {
+ sp.cpu_name.value = cpu_name;
+ sp.cpu_architecture.value = cpu_architecture;
+ sp.cpu_physical_cores.value = cpu_physical_cores;
+ }
+ else
+ {
+ try
+ {
+ struct utsname name
+ {
+ };
+ memset(&name, 0, sizeof(name));
+ if(uname(&name) < 0)
+ {
+ return posix_error();
+ }
+ sp.cpu_name.value = sp.cpu_architecture.value = name.machine;
+ sp.cpu_physical_cores.value = 0;
+#if defined(__linux__)
+ {
+ int ih = ::open("/proc/cpuinfo", O_RDONLY | O_CLOEXEC);
+ if(ih >= 0)
+ {
+ char cpuinfo[8192];
+ cpuinfo[ ::read(ih, cpuinfo, sizeof(cpuinfo) - 1)] = 0;
+ ::close(ih);
+ /* If siblings > cpu cores hyperthread is enabled:
+ siblings : 8
+ cpu cores : 4
+ */
+ const char *siblings = strstr(cpuinfo, "siblings");
+ const char *cpucores = strstr(cpuinfo, "cpu cores");
+ if((siblings != nullptr) && (cpucores != nullptr))
+ {
+ for(siblings = strchr(siblings, ':'); ' ' == *siblings; siblings++)
+ {
+ ;
+ }
+ for(cpucores = strchr(cpucores, ':'); ' ' == *cpucores; cpucores++)
+ {
+ ;
+ }
+ int s = atoi(siblings), c = atoi(cpucores); // NOLINT
+ if((s != 0) && (c != 0))
+ {
+ sp.cpu_physical_cores.value = sysconf(_SC_NPROCESSORS_ONLN) * c / s;
+ }
+ }
+ }
+ }
+#else
+ // Currently only available on OS X
+ {
+ int physicalCores = 0;
+ size_t len = sizeof(physicalCores);
+ if(sysctlbyname("hw.physicalcpu", &physicalCores, &len, NULL, 0) >= 0)
+ sp.cpu_physical_cores.value = physicalCores;
+ }
+ if(!sp.cpu_physical_cores.value)
+ {
+ char topology[8192];
+ size_t len = sizeof(topology) - 1;
+ if(sysctlbyname("kern.sched.topology_spec", topology, &len, NULL, 0) >= 0)
+ {
+ topology[len] = 0;
+ sp.cpu_physical_cores.value = sysconf(_SC_NPROCESSORS_ONLN);
+ if(strstr(topology, "HTT"))
+ sp.cpu_physical_cores.value /= 2;
+ }
+ }
+#endif
+ // Doesn't account for any hyperthreading
+ if(sp.cpu_physical_cores.value == 0u)
+ {
+ sp.cpu_physical_cores.value = sysconf(_SC_NPROCESSORS_ONLN);
+ }
+#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+ // We can do a much better CPU name on x86/x64
+ sp.cpu_name.value.clear();
+ auto __cpuid = [](int *cpuInfo, int func) { __asm__ __volatile__("cpuid\n\t" : "=a"(cpuInfo[0]), "=b"(cpuInfo[1]), "=c"(cpuInfo[2]), "=d"(cpuInfo[3]) : "0"(func)); }; // NOLINT
+ {
+ char buffer[62];
+ memset(buffer, 32, 62);
+ int nBuff[4];
+ __cpuid(nBuff, 0);
+ memcpy(buffer + 0, nBuff + 1, 4);
+ *reinterpret_cast<int *>(buffer + 4) = nBuff[3];
+ *reinterpret_cast<int *>(buffer + 8) = nBuff[2];
+
+ // Do we have a brand string?
+ __cpuid(nBuff, 0x80000000);
+ if(static_cast<unsigned>(nBuff[0]) >= 0x80000004)
+ {
+ __cpuid(reinterpret_cast<int *>(&buffer[14]), 0x80000002);
+ __cpuid(reinterpret_cast<int *>(&buffer[30]), 0x80000003);
+ __cpuid(reinterpret_cast<int *>(&buffer[46]), 0x80000004);
+ }
+ else
+ {
+ memcpy(&buffer[14], "unbranded", 10);
+ }
+
+ // Trim string
+ for(size_t n = 0; n < 62; n++)
+ {
+ if((n == 0u) || buffer[n] != 32 || buffer[n - 1] != 32)
+ {
+ if(buffer[n] != 0)
+ {
+ sp.cpu_name.value.push_back(buffer[n]);
+ }
+ }
+ }
+ }
+#endif
+ cpu_name = sp.cpu_name.value;
+ cpu_architecture = sp.cpu_architecture.value;
+ cpu_physical_cores = sp.cpu_physical_cores.value;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ }
+ return success();
+ }
+ namespace posix
+ {
+ outcome<void> _mem(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+#if defined(_SC_PHYS_PAGES)
+ size_t physpages = sysconf(_SC_PHYS_PAGES), pagesize = sysconf(_SC_PAGESIZE);
+ sp.mem_quantity.value = static_cast<unsigned long long>(physpages) * pagesize;
+#if defined(_SC_AVPHYS_PAGES)
+ size_t freepages = sysconf(_SC_AVPHYS_PAGES);
+ sp.mem_in_use.value = static_cast<float>(physpages - freepages) / physpages;
+#elif defined(HW_USERMEM)
+ unsigned long long freemem = 0;
+ size_t len = sizeof(freemem);
+ int mib[2] = {CTL_HW, HW_USERMEM};
+ if(sysctl(mib, 2, &freemem, &len, nullptr, 0) >= 0)
+ {
+ size_t freepages = (size_t)(freemem / pagesize);
+ sp.mem_in_use.value = (float) (physpages - freepages) / physpages;
+ }
+#else
+#error Do not know how to get free physical RAM on this platform
+#endif
+#endif
+ return success();
+ }
+ } // namespace posix
+ } // namespace system
+ namespace storage
+ {
+ namespace posix
+ {
+ // Controller type, max transfer, max buffers. Device name, size
+ outcome<void> _device(storage_profile &sp, file_handle & /*unused*/, const std::string &_mntfromname, const std::string &fstypename) noexcept
+ {
+ (void) fstypename;
+ try
+ {
+ std::string mntfromname(_mntfromname);
+ // Firstly open a handle to the device
+ if(strncmp(mntfromname.data(), "/dev", 4) == 0)
+ {
+ if(std::isdigit(mntfromname.back()) != 0)
+ {
+ mntfromname.resize(mntfromname.size() - 1);
+ }
+ }
+ else
+ {
+// If the mount point doesn't begin with /dev we can't use that here, so return ENOSYS
+#ifdef __FreeBSD__
+ // If on ZFS and there is exactly one physical disk in the system, use that
+ if(fstypename == "zfs")
+ {
+ char buffer[4096];
+ size_t len = sizeof(buffer);
+ if(sysctlbyname("kern.disks", buffer, &len, NULL, 0) >= 0)
+ {
+ mntfromname.clear();
+ // Might be a string like "ada0 cd0 ..."
+ const char *s, *e = buffer;
+ for(; e < buffer + len; e++)
+ {
+ for(s = e; e < buffer + len && *e != ' '; e++)
+ ;
+ if(s[0] == 'c' && s[1] == 'd')
+ continue;
+ if(s[0] == 'f' && s[1] == 'd')
+ continue;
+ if(s[0] == 'm' && s[1] == 'c' && s[2] == 'd')
+ continue;
+ if(s[0] == 's' && s[1] == 'c' && s[2] == 'd')
+ continue;
+ // Is there more than one physical disk device?
+ if(!mntfromname.empty())
+ return errc::function_not_supported;
+ mntfromname = "/dev/" + std::string(s, e - s);
+ }
+ }
+ else
+ return errc::function_not_supported;
+ }
+ else
+#endif
+ return errc::function_not_supported;
+ }
+ OUTCOME_TRY(deviceh, file_handle::file({}, mntfromname, handle::mode::none, handle::creation::open_existing, handle::caching::only_metadata));
+
+// TODO(ned): See https://github.com/baruch/diskscan/blob/master/arch/arch-linux.c
+// sp.controller_type.value = "SCSI";
+// sp.controller_max_transfer.value = sad->MaximumTransferLength;
+// sp.controller_max_buffers.value = sad->MaximumPhysicalPages;
+// sp.device_name.value.resize(sp.device_name.value.size() - 1);
+
+#ifdef DIOCGMEDIASIZE
+ // BSDs
+ ioctl(deviceh.native_handle().fd, DIOCGMEDIASIZE, &sp.device_size.value);
+#endif
+#ifdef BLKGETSIZE64
+ // Linux
+ ioctl(deviceh.native_handle().fd, BLKGETSIZE64, &sp.device_size.value);
+#endif
+#ifdef DKIOCGETBLOCKCOUNT
+ // OS X
+ ioctl(deviceh.native_handle().fd, DKIOCGETBLOCKCOUNT, &sp.device_size.value);
+#endif
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ return success();
+ }
+ } // namespace posix
+ } // namespace storage
+} // namespace storage_profile
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/posix/utils.ipp b/include/llfio/v2.0/detail/impl/posix/utils.ipp
new file mode 100644
index 00000000..aecd0a03
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/posix/utils.ipp
@@ -0,0 +1,229 @@
+/* Misc utilities
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (6 commits)
+File Created: Jan 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../utils.hpp"
+
+#ifdef __has_include
+#if __has_include("../../../quickcpplib/include/spinlock.hpp")
+#include "../../../quickcpplib/include/spinlock.hpp"
+#else
+#include "quickcpplib/include/spinlock.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/spinlock.hpp"
+#else
+#include "../../../quickcpplib/include/spinlock.hpp"
+#endif
+
+#include <mutex> // for lock_guard
+
+#include <sys/mman.h>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace utils
+{
+ size_t page_size() noexcept
+ {
+ static size_t ret;
+ if(ret == 0u)
+ {
+ ret = getpagesize();
+ }
+ return ret;
+ }
+ std::vector<size_t> page_sizes(bool only_actually_available)
+ {
+ static QUICKCPPLIB_NAMESPACE::configurable_spinlock::spinlock<bool> lock;
+ static std::vector<size_t> pagesizes, pagesizes_available;
+ std::lock_guard<decltype(lock)> g(lock);
+ if(pagesizes.empty())
+ {
+#if defined(__FreeBSD__)
+ pagesizes.resize(32);
+ int out;
+ if(-1 == (out = getpagesizes(pagesizes.data(), 32)))
+ {
+ pagesizes.clear();
+ pagesizes.push_back(getpagesize());
+ pagesizes_available.push_back(getpagesize());
+ }
+ else
+ {
+ pagesizes.resize(out);
+ pagesizes_available = pagesizes;
+ }
+#elif defined(__APPLE__)
+ // I can't find how to determine what the super page size is on OS X programmatically
+ // It appears to be hard coded into mach/vm_statistics.h which says that it's always 2Mb
+ // Therefore, we hard code 2Mb
+ pagesizes.push_back(getpagesize());
+ pagesizes_available.push_back(getpagesize());
+ pagesizes.push_back(2 * 1024 * 1024);
+ pagesizes_available.push_back(2 * 1024 * 1024);
+#elif defined(__linux__)
+ pagesizes.push_back(getpagesize());
+ pagesizes_available.push_back(getpagesize());
+ int ih = ::open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
+ if(-1 != ih)
+ {
+ char buffer[4096], *hugepagesize, *hugepages;
+ buffer[ ::read(ih, buffer, sizeof(buffer) - 1)] = 0;
+ ::close(ih);
+ hugepagesize = strstr(buffer, "Hugepagesize:");
+ hugepages = strstr(buffer, "HugePages_Total:");
+ if((hugepagesize != nullptr) && (hugepages != nullptr))
+ {
+ unsigned _hugepages = 0, _hugepagesize = 0;
+ while(*++hugepagesize != ' ')
+ {
+ ;
+ }
+ while(*++hugepages != ' ')
+ {
+ ;
+ }
+ while(*++hugepagesize == ' ')
+ {
+ ;
+ }
+ while(*++hugepages == ' ')
+ {
+ ;
+ }
+ sscanf(hugepagesize, "%u", &_hugepagesize); // NOLINT
+ sscanf(hugepages, "%u", &_hugepages); // NOLINT
+ if(_hugepagesize != 0u)
+ {
+ pagesizes.push_back((static_cast<size_t>(_hugepagesize)) * 1024);
+ if(_hugepages != 0u)
+ {
+ pagesizes_available.push_back((static_cast<size_t>(_hugepagesize)) * 1024);
+ }
+ }
+ }
+ }
+#else
+#warning page_sizes() does not know this platform, so assuming getpagesize() is the best available
+ pagesizes.push_back(getpagesize());
+ pagesizes_available.push_back(getpagesize());
+#endif
+ }
+ return only_actually_available ? pagesizes_available : pagesizes;
+ }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+ void random_fill(char *buffer, size_t bytes) noexcept
+ {
+ static QUICKCPPLIB_NAMESPACE::configurable_spinlock::spinlock<bool> lock;
+ static std::atomic<int> randomfd(-1);
+ int fd = randomfd;
+ if(-1 == fd)
+ {
+ std::lock_guard<decltype(lock)> g(lock);
+ randomfd = fd = ::open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+ }
+ if(-1 == fd || ::read(fd, buffer, bytes) < static_cast<ssize_t>(bytes))
+ {
+ AFIO_LOG_FATAL(0, "afio: Kernel crypto function failed");
+ std::terminate();
+ }
+ }
+
+ result<void> flush_modified_data() noexcept
+ {
+ ::sync();
+ return success();
+ }
+
+ result<void> drop_filesystem_cache() noexcept
+ {
+ (void) flush_modified_data();
+#ifdef __linux__
+ int h = ::open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC);
+ if(h == -1)
+ {
+ return posix_error();
+ }
+ auto unh = undoer([&h] { ::close(h); });
+ char v = '3'; // drop everything
+ if(-1 == ::write(h, &v, 1))
+ {
+ return posix_error();
+ }
+ return success();
+#endif
+ return errc::not_supported;
+ }
+
+ namespace detail
+ {
+ large_page_allocation allocate_large_pages(size_t bytes)
+ {
+ large_page_allocation ret(calculate_large_page_allocation(bytes));
+ int flags = MAP_SHARED | MAP_ANON;
+ if(ret.page_size_used > 65536)
+ {
+#ifdef MAP_HUGETLB
+ flags |= MAP_HUGETLB;
+#endif
+#ifdef MAP_ALIGNED_SUPER
+ flags |= MAP_ALIGNED_SUPER;
+#endif
+#ifdef VM_FLAGS_SUPERPAGE_SIZE_ANY
+ flags |= VM_FLAGS_SUPERPAGE_SIZE_ANY;
+#endif
+ }
+ if((ret.p = mmap(nullptr, ret.actual_size, PROT_WRITE, flags, -1, 0)) == nullptr)
+ {
+ if(ENOMEM == errno)
+ {
+ if((ret.p = mmap(nullptr, ret.actual_size, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0)) != nullptr)
+ {
+ return ret;
+ }
+ }
+ }
+#ifndef NDEBUG
+ else if(ret.page_size_used > 65536)
+ {
+ printf("afio: Large page allocation successful\n");
+ }
+#endif
+ return ret;
+ }
+ void deallocate_large_pages(void *p, size_t bytes)
+ {
+ if(munmap(p, bytes) < 0)
+ {
+ AFIO_LOG_FATAL(p, "afio: Freeing large pages failed");
+ std::terminate();
+ }
+ }
+ } // namespace detail
+} // namespace utils
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/safe_byte_ranges.ipp b/include/llfio/v2.0/detail/impl/safe_byte_ranges.ipp
new file mode 100644
index 00000000..f4760896
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/safe_byte_ranges.ipp
@@ -0,0 +1,438 @@
+/* Safe small actor read-write lock
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#if defined(_WIN32)
+#error This file should never be compiled on Windows.
+#endif
+
+#include "../../algorithm/shared_fs_mutex/safe_byte_ranges.hpp"
+
+#ifdef __has_include
+#if __has_include("../../quickcpplib/include/uint128.hpp")
+#include "../../quickcpplib/include/uint128.hpp"
+#include "../../quickcpplib/include/utils/thread.hpp"
+#else
+#include "quickcpplib/include/uint128.hpp"
+#include "quickcpplib/include/utils/thread.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/uint128.hpp"
+#include "quickcpplib/include/utils/thread.hpp"
+#else
+#include "../../quickcpplib/include/uint128.hpp"
+#include "../../quickcpplib/include/utils/thread.hpp"
+#endif
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <unordered_map>
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace algorithm
+{
+ namespace shared_fs_mutex
+ {
+ namespace detail
+ {
+#if 0
+ struct _byte_ranges : public byte_ranges
+ {
+ using byte_ranges::byte_ranges;
+ using byte_ranges::_lock;
+ _byte_ranges(byte_ranges &&o)
+ : byte_ranges(std::move(o))
+ {
+ }
+ };
+#endif
+ class threaded_byte_ranges : public shared_fs_mutex
+ {
+ public:
+ using entity_type = shared_fs_mutex::entity_type;
+ using entities_type = shared_fs_mutex::entities_type;
+
+ private:
+ std::mutex _m;
+ file_handle _h;
+ std::condition_variable _changed;
+ struct _entity_info
+ {
+ std::vector<unsigned> reader_tids; // thread ids of all shared lock holders
+ unsigned writer_tid; // thread id of exclusive lock holder
+ io_handle::extent_guard filelock; // exclusive if writer_tid, else shared
+ _entity_info(bool exclusive, unsigned tid, io_handle::extent_guard _filelock)
+ : writer_tid(exclusive ? tid : 0)
+ , filelock(std::move(_filelock))
+ {
+ reader_tids.reserve(4);
+ if(!exclusive)
+ {
+ reader_tids.push_back(tid);
+ }
+ }
+ };
+ std::unordered_map<entity_type::value_type, _entity_info> _thread_locks; // entity to thread lock
+ // _m mutex must be held on entry!
+ void _unlock(unsigned mythreadid, entity_type entity)
+ {
+ auto it = _thread_locks.find(entity.value); // NOLINT
+ assert(it != _thread_locks.end());
+ assert(it->second.writer_tid == mythreadid || it->second.writer_tid == 0);
+ if(it->second.writer_tid == mythreadid)
+ {
+ if(!it->second.reader_tids.empty())
+ {
+ // Downgrade the lock from exclusive to shared
+ auto l = _h.lock(entity.value, 1, false).value();
+#ifndef _WIN32
+ // On POSIX byte range locks replace
+ it->second.filelock.release();
+#endif
+ it->second.filelock = std::move(l);
+ it->second.writer_tid = 0;
+ return;
+ }
+ }
+ else
+ {
+ // Remove me from reader tids
+ auto reader_tid_it = std::find(it->second.reader_tids.begin(), it->second.reader_tids.end(), mythreadid);
+ assert(reader_tid_it != it->second.reader_tids.end());
+ if(reader_tid_it != it->second.reader_tids.end())
+ {
+ // We don't care about the order, so fastest is to swap this tid with final tid and resize down
+ std::swap(*reader_tid_it, it->second.reader_tids.back());
+ it->second.reader_tids.pop_back();
+ }
+ }
+ if(it->second.reader_tids.empty())
+ {
+ // Release the lock and delete this entity from the map
+ _h.unlock(entity.value, 1);
+ _thread_locks.erase(it);
+ }
+ }
+
+ public:
+ threaded_byte_ranges(const path_handle &base, path_view lockfile)
+ {
+ AFIO_LOG_FUNCTION_CALL(0);
+ _h = file_handle::file(base, lockfile, file_handle::mode::write, file_handle::creation::if_needed, file_handle::caching::temporary).value();
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _lock(entities_guard &out, deadline d, bool spin_not_sleep) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ unsigned mythreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id();
+ std::chrono::steady_clock::time_point began_steady;
+ std::chrono::system_clock::time_point end_utc;
+ if(d)
+ {
+ if((d).steady)
+ {
+ began_steady = std::chrono::steady_clock::now();
+ }
+ else
+ {
+ end_utc = (d).to_time_point();
+ }
+ }
+ // Fire this if an error occurs
+ auto disableunlock = undoer([&] { out.release(); });
+ size_t n;
+ for(;;)
+ {
+ auto was_contended = static_cast<size_t>(-1);
+ bool pls_sleep = true;
+ std::unique_lock<decltype(_m)> guard(_m);
+ {
+ auto undo = undoer([&] {
+ // 0 to (n-1) need to be closed
+ if(n > 0)
+ {
+ --n;
+ // Now 0 to n needs to be closed
+ for(; n > 0; n--)
+ {
+ _unlock(mythreadid, out.entities[n]);
+ }
+ _unlock(mythreadid, out.entities[0]);
+ }
+ });
+ for(n = 0; n < out.entities.size(); n++)
+ {
+ auto it = _thread_locks.find(out.entities[n].value);
+ if(it == _thread_locks.end())
+ {
+ // This entity has not been locked before
+ deadline nd;
+ // Only for very first entity will we sleep until its lock becomes available
+ if(n != 0u)
+ {
+ nd = deadline(std::chrono::seconds(0));
+ }
+ else
+ {
+ nd = deadline();
+ if(d)
+ {
+ if((d).steady)
+ {
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now());
+ if(ns.count() < 0)
+ {
+ (nd).nsecs = 0;
+ }
+ else
+ {
+ (nd).nsecs = ns.count();
+ }
+ }
+ else
+ {
+ (nd) = (d);
+ }
+ }
+ }
+ // Allow other threads to use this threaded_byte_ranges
+ guard.unlock();
+ auto outcome = _h.lock(out.entities[n].value, 1, out.entities[n].exclusive != 0u, nd);
+ guard.lock();
+ if(!outcome)
+ {
+ was_contended = n;
+ pls_sleep = false;
+ goto failed;
+ }
+ // Did another thread already fill this in?
+ it = _thread_locks.find(out.entities[n].value);
+ if(it == _thread_locks.end())
+ {
+ it = _thread_locks.insert(std::make_pair(static_cast<entity_type::value_type>(out.entities[n].value), _entity_info(out.entities[n].exclusive != 0u, mythreadid, std::move(outcome).value()))).first;
+ continue;
+ }
+ // Otherwise throw away the presumably shared superfluous byte range lock
+ assert(!out.entities[n].exclusive);
+ }
+
+ // If we are here, then this entity has been locked by someone before
+ auto reader_tid_it = std::find(it->second.reader_tids.begin(), it->second.reader_tids.end(), mythreadid);
+ bool already_have_shared_lock = (reader_tid_it != it->second.reader_tids.end());
+ // Is somebody already locking this entity exclusively?
+ if(it->second.writer_tid != 0)
+ {
+ if(it->second.writer_tid == mythreadid)
+ {
+ // If I am relocking myself, return deadlock
+ if((out.entities[n].exclusive != 0u) || already_have_shared_lock)
+ {
+ return errc::resource_deadlock_would_occur;
+ }
+ // Otherwise just add myself to the reader list
+ it->second.reader_tids.push_back(mythreadid);
+ continue;
+ }
+ // Some other thread holds the exclusive lock, so we cannot take it
+ was_contended = n;
+ pls_sleep = true;
+ goto failed;
+ }
+ // If reached here, nobody is holding the exclusive lock
+ assert(it->second.writer_tid == 0);
+ if(out.entities[n].exclusive == 0u)
+ {
+ // If I am relocking myself, return deadlock
+ if(already_have_shared_lock)
+ {
+ return errc::resource_deadlock_would_occur;
+ }
+ // Otherwise just add myself to the reader list
+ it->second.reader_tids.push_back(mythreadid);
+ continue;
+ }
+ // We are thus now upgrading shared to exclusive
+ assert(out.entities[n].exclusive);
+ deadline nd;
+ // Only for very first entity will we sleep until its lock becomes available
+ if(n != 0u)
+ {
+ nd = deadline(std::chrono::seconds(0));
+ }
+ else
+ {
+ nd = deadline();
+ if(d)
+ {
+ if((d).steady)
+ {
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now());
+ if(ns.count() < 0)
+ {
+ (nd).nsecs = 0;
+ }
+ else
+ {
+ (nd).nsecs = ns.count();
+ }
+ }
+ else
+ {
+ (nd) = (d);
+ }
+ }
+ }
+ // Allow other threads to use this threaded_byte_ranges
+ guard.unlock();
+ auto outcome = _h.lock(out.entities[n].value, 1, true, nd);
+ guard.lock();
+ if(!outcome)
+ {
+ was_contended = n;
+ goto failed;
+ }
+// unordered_map iterators do not invalidate, so no need to refresh
+#ifndef _WIN32
+ // On POSIX byte range locks replace
+ it->second.filelock.release();
+#endif
+ it->second.filelock = std::move(outcome).value();
+ it->second.writer_tid = mythreadid;
+ }
+ // Dismiss unwind of thread locking and return success
+ undo.dismiss();
+ disableunlock.dismiss();
+ return success();
+ }
+ failed:
+ if(d)
+ {
+ if((d).steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs)))
+ {
+ return errc::timed_out;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return errc::timed_out;
+ }
+ }
+ }
+ // Move was_contended to front and randomise rest of out.entities
+ std::swap(out.entities[was_contended], out.entities[0]);
+ auto front = out.entities.begin();
+ ++front;
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::random_shuffle(front, out.entities.end());
+ if(pls_sleep && !spin_not_sleep)
+ {
+ // Sleep until the thread locks next change
+ if((d).steady)
+ {
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now());
+ _changed.wait_for(guard, ns);
+ }
+ else
+ {
+ _changed.wait_until(guard, d.to_time_point());
+ }
+ }
+ }
+ // return success();
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(entities_type entities, unsigned long long /*unused*/) noexcept final
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ unsigned mythreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id();
+ std::unique_lock<decltype(_m)> guard(_m);
+ for(auto &entity : entities)
+ {
+ _unlock(mythreadid, entity);
+ }
+ }
+ };
+ struct threaded_byte_ranges_list
+ {
+ using key_type = QUICKCPPLIB_NAMESPACE::integers128::uint128;
+ std::mutex lock;
+ std::unordered_map<key_type, std::weak_ptr<threaded_byte_ranges>, QUICKCPPLIB_NAMESPACE::integers128::uint128_hasher> db;
+ };
+ inline threaded_byte_ranges_list &tbrlist()
+ {
+ static threaded_byte_ranges_list v;
+ return v;
+ }
+ AFIO_HEADERS_ONLY_FUNC_SPEC result<std::shared_ptr<shared_fs_mutex>> inode_to_fs_mutex(const path_handle &base, path_view lockfile) noexcept
+ {
+ try
+ {
+ path_view::c_str zpath(lockfile);
+ struct stat s
+ {
+ };
+ if(-1 == ::fstatat(base.is_valid() ? base.native_handle().fd : AT_FDCWD, zpath.buffer, &s, AT_SYMLINK_NOFOLLOW))
+ {
+ return posix_error();
+ }
+ threaded_byte_ranges_list::key_type key;
+ key.as_longlongs[0] = s.st_ino;
+ key.as_longlongs[1] = s.st_dev;
+ std::shared_ptr<threaded_byte_ranges> ret;
+ auto &list = tbrlist();
+ std::lock_guard<decltype(list.lock)> g(list.lock);
+ auto it = list.db.find(key);
+ if(it != list.db.end())
+ {
+ ret = it->second.lock();
+ if(ret)
+ {
+ return ret;
+ }
+ }
+ ret = std::make_shared<threaded_byte_ranges>(base, lockfile);
+ if(it != list.db.end())
+ {
+ it->second = std::weak_ptr<threaded_byte_ranges>(ret);
+ }
+ else
+ {
+ list.db.insert({key, std::weak_ptr<threaded_byte_ranges>(ret)});
+ }
+ return ret;
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ } // namespace detail
+ } // namespace shared_fs_mutex
+} // namespace algorithm
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/storage_profile.ipp b/include/llfio/v2.0/detail/impl/storage_profile.ipp
new file mode 100644
index 00000000..b442f74a
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/storage_profile.ipp
@@ -0,0 +1,1382 @@
+/* A profile of an OS and filing system
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../directory_handle.hpp"
+#include "../../file_handle.hpp"
+#include "../../statfs.hpp"
+#include "../../storage_profile.hpp"
+#include "../../utils.hpp"
+
+#ifdef __has_include
+#if __has_include("../../quickcpplib/include/algorithm/small_prng.hpp")
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#else
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#endif
+#elif __PCPP_ALWAYS_TRUE__
+#include "quickcpplib/include/algorithm/small_prng.hpp"
+#else
+#include "../../quickcpplib/include/algorithm/small_prng.hpp"
+#endif
+
+#include <future>
+#include <vector>
+#ifndef NDEBUG
+#include <fstream>
+#include <iostream>
+#endif
+
+#define AFIO_STORAGE_PROFILE_TIME_DIVIDER 10
+
+// Work around buggy Windows scheduler
+//#define AFIO_STORAGE_PROFILE_PIN_THREADS
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace storage_profile
+{
+
+ /* YAML's syntax is amazingly powerful ... we can express a map
+ of a map to a map using this syntax:
+
+ ?
+ direct: 0
+ sync: 0
+ :
+ concurrency:
+ atomicity:
+ min_atomic_write: 1
+ max_atomic_write: 1
+
+ Some YAML parsers appear to accept this more terse form too:
+
+ {direct: 0, sync: 0}:
+ concurrency:
+ atomicity:
+ min_atomic_write: 1
+ max_atomic_write: 1
+
+ We don't do any of this as some YAML parsers are basically JSON parsers with
+ some rules relaxed. We just use:
+
+ direct=0 sync=0:
+ concurrency:
+ atomicity:
+ min_atomic_write: 1
+ max_atomic_write: 1
+ */
+ void storage_profile::write(std::ostream &out, const std::regex &which, size_t _indent, bool invert_match) const
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ std::vector<std::string> lastsection;
+ auto print = [_indent, &out, &lastsection](auto &i) {
+ size_t indent = _indent;
+ if(i.value != default_value<decltype(i.value)>())
+ {
+ std::vector<std::string> thissection;
+ const char *s, *e;
+ for(s = i.name, e = i.name; *e != 0; e++)
+ {
+ if(*e == ':')
+ {
+ thissection.emplace_back(s, e - s);
+ s = e + 1;
+ }
+ }
+ std::string name(s, e - s);
+ for(size_t n = 0; n < thissection.size(); n++)
+ {
+ indent += 4;
+ if(n >= lastsection.size() || thissection[n] != lastsection[n])
+ {
+ out << std::string(indent - 4, ' ') << thissection[n] << ":\n";
+ }
+ }
+ if(i.description)
+ {
+ std::string text(i.description);
+ std::vector<std::string> lines;
+ for(;;)
+ {
+ size_t idx = 78;
+ if(idx < text.size())
+ {
+ while(text[idx] != ' ')
+ {
+ --idx;
+ }
+ }
+ else
+ {
+ idx = text.size();
+ }
+ lines.push_back(text.substr(0, idx));
+ if(idx < text.size())
+ {
+ text = text.substr(idx + 1);
+ }
+ else
+ {
+ break;
+ }
+ }
+ for(auto &line : lines)
+ {
+ out << std::string(indent, ' ') << "# " << line << "\n";
+ }
+ }
+ out << std::string(indent, ' ') << name << ": " << i.value << "\n";
+ if(i.description && strlen(i.description) > 78)
+ {
+ out << "\n";
+ }
+ lastsection = std::move(thissection);
+ }
+ };
+ for(const item_erased &i : *this)
+ {
+ bool matches = std::regex_match(i.name, which);
+ if((matches && !invert_match) || (!matches && invert_match))
+ {
+ i.invoke(print);
+ }
+ }
+ }
+
+ namespace system
+ {
+ // System memory quantity, in use, max and min bandwidth
+ outcome<void> mem(storage_profile &sp, file_handle &h) noexcept
+ {
+ static unsigned long long mem_quantity, mem_max_bandwidth, mem_min_bandwidth;
+ static float mem_in_use;
+ if(mem_quantity != 0u)
+ {
+ sp.mem_quantity.value = mem_quantity;
+ sp.mem_in_use.value = mem_in_use;
+ sp.mem_max_bandwidth.value = mem_max_bandwidth;
+ sp.mem_min_bandwidth.value = mem_min_bandwidth;
+ }
+ else
+ {
+ try
+ {
+ size_t chunksize = 256 * 1024 * 1024;
+#ifdef WIN32
+ OUTCOME_TRYV(windows::_mem(sp, h));
+#else
+ OUTCOME_TRYV(posix::_mem(sp, h));
+#endif
+
+ if(sp.mem_quantity.value / 4 < chunksize)
+ {
+ chunksize = static_cast<size_t>(sp.mem_quantity.value / 4);
+ }
+ char *buffer = utils::page_allocator<char>().allocate(chunksize);
+ auto unbuffer = AFIO_V2_NAMESPACE::undoer([buffer, chunksize] { utils::page_allocator<char>().deallocate(buffer, chunksize); });
+ // Make sure all memory is really allocated first
+ memset(buffer, 1, chunksize);
+
+ // Max bandwidth is sequential writes of min(25% of system memory or 256Mb)
+ auto begin = std::chrono::high_resolution_clock::now();
+ unsigned long long count;
+ for(count = 0; std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - begin).count() < (10 / AFIO_STORAGE_PROFILE_TIME_DIVIDER); count++)
+ {
+ memset(buffer, count & 0xff, chunksize);
+ }
+ sp.mem_max_bandwidth.value = static_cast<unsigned long long>(static_cast<double>(count) * chunksize / 10);
+
+ // Min bandwidth is randomised 4Kb copies of the same
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::small_prng ctx(78);
+ begin = std::chrono::high_resolution_clock::now();
+ for(count = 0; std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - begin).count() < (10 / AFIO_STORAGE_PROFILE_TIME_DIVIDER); count++)
+ {
+ for(size_t n = 0; n < chunksize; n += 4096)
+ {
+ auto offset = ctx() * 4096;
+ offset = offset % chunksize;
+ memset(buffer + offset, count & 0xff, 4096);
+ }
+ }
+ sp.mem_min_bandwidth.value = static_cast<unsigned long long>(static_cast<double>(count) * chunksize / 10);
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ mem_quantity = sp.mem_quantity.value;
+ mem_in_use = sp.mem_in_use.value;
+ mem_max_bandwidth = sp.mem_max_bandwidth.value;
+ mem_min_bandwidth = sp.mem_min_bandwidth.value;
+ }
+ return success();
+ }
+
+ // High resolution clock granularity
+ struct clock_info_t
+ {
+ unsigned granularity; // in nanoseconds
+ unsigned overhead; // in nanoseconds
+ };
+ inline clock_info_t _clock_granularity_and_overhead()
+ {
+ static clock_info_t info;
+ if(info.granularity == 0u)
+ {
+ auto count = static_cast<unsigned>(-1);
+ for(size_t n = 0; n < 20; n++)
+ {
+ std::chrono::high_resolution_clock::time_point begin = std::chrono::high_resolution_clock::now(), end;
+ do
+ {
+ end = std::chrono::high_resolution_clock::now();
+ } while(begin == end);
+ auto diff = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
+ if(diff < count)
+ {
+ count = static_cast<unsigned>(diff);
+ }
+ }
+ info.granularity = count;
+ std::chrono::high_resolution_clock::time_point begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < 1000000; n++)
+ {
+ (void) std::chrono::high_resolution_clock::now();
+ }
+ std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
+ info.overhead = static_cast<unsigned>(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) / 1000000);
+ }
+ return info;
+ }
+ outcome<void> clock_granularity(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ auto info = _clock_granularity_and_overhead();
+ sp.clock_granularity.value = info.granularity;
+ sp.clock_overhead.value = info.overhead;
+ return success();
+ }
+ inline unsigned _sleep_wake_overhead()
+ {
+ static unsigned v;
+ if(v == 0u)
+ {
+ unsigned count = static_cast<unsigned>(-1), period = 1000; // 1us
+ for(size_t n = 0; n < 20; n++)
+ {
+ std::chrono::high_resolution_clock::time_point begin, end;
+ unsigned diff;
+ do
+ {
+ begin = std::chrono::high_resolution_clock::now();
+ std::this_thread::sleep_for(std::chrono::nanoseconds(period));
+ end = std::chrono::high_resolution_clock::now();
+ period *= 2; // 2^20 = ~1ms
+ diff = static_cast<unsigned>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count());
+ } while(diff == 0);
+ if(diff < count)
+ {
+ count = diff;
+ }
+ }
+ v = count;
+ }
+ return v;
+ }
+ outcome<void> sleep_wake_overhead(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ sp.sleep_wake_overhead.value = _sleep_wake_overhead();
+ return success();
+ }
+ inline unsigned _yield_overhead()
+ {
+ static unsigned v;
+ if(v == 0u)
+ {
+ auto count = static_cast<unsigned>(-1);
+ for(size_t n = 0; n < 20; n++)
+ {
+ std::chrono::high_resolution_clock::time_point begin, end;
+ begin = std::chrono::high_resolution_clock::now();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ std::this_thread::yield();
+ end = std::chrono::high_resolution_clock::now();
+ unsigned diff = static_cast<unsigned>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count());
+ if(diff < count)
+ {
+ count = diff / 10;
+ }
+ }
+ v = count;
+ }
+ return v;
+ }
+ outcome<void> yield_overhead(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ sp.yield_overhead.value = _yield_overhead();
+ return success();
+ }
+ outcome<void> drop_filesystem_cache_support(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ static bool v = !!utils::drop_filesystem_cache();
+ sp.drop_filesystem_cache_support.value = static_cast<unsigned int>(v);
+ return success();
+ }
+ } // namespace system
+ namespace storage
+ {
+ // Device name, size, min i/o size
+ outcome<void> device(storage_profile &sp, file_handle &h) noexcept
+ {
+ try
+ {
+ statfs_t fsinfo;
+ OUTCOME_TRYV(fsinfo.fill(h, statfs_t::want::iosize | statfs_t::want::mntfromname | statfs_t::want::fstypename));
+ sp.device_min_io_size.value = static_cast<unsigned>(fsinfo.f_iosize);
+#ifdef WIN32
+ OUTCOME_TRYV(windows::_device(sp, h, fsinfo.f_mntfromname, fsinfo.f_fstypename));
+#else
+ OUTCOME_TRYV(posix::_device(sp, h, fsinfo.f_mntfromname, fsinfo.f_fstypename));
+#endif
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ return success();
+ }
+ // FS name, config, size, in use
+ outcome<void> fs(storage_profile &sp, file_handle &h) noexcept
+ {
+ try
+ {
+ statfs_t fsinfo;
+ OUTCOME_TRYV(fsinfo.fill(h));
+ sp.fs_name.value = fsinfo.f_fstypename;
+ sp.fs_config.value = "todo";
+ sp.fs_size.value = fsinfo.f_blocks * fsinfo.f_bsize;
+ sp.fs_in_use.value = static_cast<float>(fsinfo.f_blocks - fsinfo.f_bfree) / fsinfo.f_blocks;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ return success();
+ }
+ } // namespace storage
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4459) // off_t shadows global namespace
+#endif
+ namespace concurrency
+ {
+ outcome<void> atomic_rewrite_quantum(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.atomic_rewrite_quantum.value != static_cast<io_service::extent_type>(-1))
+ {
+ return success();
+ }
+ try
+ {
+ using off_t = io_service::extent_type;
+ sp.max_aligned_atomic_rewrite.value = 1;
+ sp.atomic_rewrite_quantum.value = static_cast<off_t>(-1);
+ size_t size = srch.requires_aligned_io() ?
+#ifdef _WIN32
+ 4096
+#else
+ 512
+#endif
+ :
+ 64;
+ for(; size <= 1 * 1024 * 1024 && size < sp.atomic_rewrite_quantum.value; size = size * 2)
+ {
+ // Create two concurrent writer threads and as many reader threads as additional CPU cores
+ // The excessive unique_ptr works around a bug in libc++'s thread implementation
+ std::vector<std::pair<std::unique_ptr<std::thread>, std::future<void>>> writers, readers;
+ std::atomic<size_t> done(2);
+ for(char no = '1'; no <= '2'; no++)
+ {
+ std::packaged_task<void()> task([size, &srch, no, &done] {
+ auto _h(srch.clone());
+ if(!_h)
+ {
+ throw std::runtime_error(std::string("concurrency::atomic_rewrite_quantum: " // NOLINT
+ "Could not open work file due to ") +
+ _h.error().message().c_str());
+ }
+ file_handle h(std::move(_h.value()));
+ std::vector<byte, utils::page_allocator<byte>> buffer(size, to_byte(no));
+ file_handle::const_buffer_type _reqs[1] = {{buffer.data(), size}};
+ file_handle::io_request<file_handle::const_buffers_type> reqs(_reqs, 0);
+ --done;
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ while(done == 0u)
+ {
+ h.write(reqs).value();
+ }
+ });
+ auto f(task.get_future());
+ writers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+ // Wait till the writers launch
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ unsigned concurrency = std::thread::hardware_concurrency() - 2;
+ if(concurrency < 4)
+ {
+ concurrency = 4;
+ }
+ std::atomic<io_service::extent_type> atomic_rewrite_quantum(sp.atomic_rewrite_quantum.value);
+ std::atomic<bool> failed(false);
+ for(unsigned no = 0; no < concurrency; no++)
+ {
+ std::packaged_task<void()> task([size, &srch, &done, &atomic_rewrite_quantum, &failed] {
+ auto _h(srch.clone());
+ if(!_h)
+ {
+ throw std::runtime_error(std::string("concurrency::atomic_rewrite_quantum: " // NOLINT
+ "Could not open work file due to ") +
+ _h.error().message().c_str());
+ }
+ file_handle h(std::move(_h.value()));
+ std::vector<byte, utils::page_allocator<byte>> buffer(size, to_byte(0)), tocmp(size, to_byte(0));
+ file_handle::buffer_type _reqs[1] = {{buffer.data(), size}};
+ file_handle::io_request<file_handle::buffers_type> reqs(_reqs, 0);
+ while(done == 0u)
+ {
+ h.read(reqs).value();
+ // memset(tocmp.data(), buffer.front(), size);
+ // if (memcmp(buffer.data(), tocmp.data(), size))
+ {
+ const size_t *data = reinterpret_cast<size_t *>(buffer.data()), *end = reinterpret_cast<size_t *>(buffer.data() + size);
+ for(const size_t *d = data; d < end; d++)
+ {
+ if(*d != *data)
+ {
+ failed = true;
+ off_t failedat = d - data;
+ if(failedat < atomic_rewrite_quantum)
+ {
+#ifndef NDEBUG
+ std::cout << " Torn rewrite at offset " << failedat << std::endl;
+#endif
+ atomic_rewrite_quantum = failedat;
+ }
+ break;
+ }
+ }
+ }
+ }
+ });
+ auto f(task.get_future());
+ readers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+
+#ifndef NDEBUG
+ std::cout << "direct=" << !srch.are_reads_from_cache() << " sync=" << srch.are_writes_durable() << " testing atomicity of rewrites of " << size << " bytes ..." << std::endl;
+#endif
+ auto begin = std::chrono::high_resolution_clock::now();
+ while(!failed && std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - begin).count() < (20 / AFIO_STORAGE_PROFILE_TIME_DIVIDER))
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ done = 1u;
+ for(auto &writer : writers)
+ {
+ writer.first->join();
+ }
+ for(auto &reader : readers)
+ {
+ reader.first->join();
+ }
+ for(auto &writer : writers)
+ {
+ writer.second.get();
+ }
+ for(auto &reader : readers)
+ {
+ reader.second.get();
+ }
+ sp.atomic_rewrite_quantum.value = atomic_rewrite_quantum;
+ if(!failed)
+ {
+ if(size > sp.max_aligned_atomic_rewrite.value)
+ {
+ sp.max_aligned_atomic_rewrite.value = size;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ if(sp.atomic_rewrite_quantum.value > sp.max_aligned_atomic_rewrite.value)
+ {
+ sp.atomic_rewrite_quantum.value = sp.max_aligned_atomic_rewrite.value;
+ }
+
+ // If burst quantum exceeds rewrite quantum, make sure it does so at
+ // offsets not at the front of the file
+ if(sp.max_aligned_atomic_rewrite.value > sp.atomic_rewrite_quantum.value)
+ {
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4456) // declaration hides previous local declaration
+#endif
+ auto size = static_cast<size_t>(sp.max_aligned_atomic_rewrite.value);
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ for(off_t offset = sp.max_aligned_atomic_rewrite.value; offset < sp.max_aligned_atomic_rewrite.value * 4; offset += sp.max_aligned_atomic_rewrite.value)
+ {
+ // Create two concurrent writer threads and as many reader threads as additional CPU cores
+ // The excessive unique_ptr works around a bug in libc++'s thread implementation
+ std::vector<std::pair<std::unique_ptr<std::thread>, std::future<void>>> writers, readers;
+ std::atomic<size_t> done(2);
+ for(char no = '1'; no <= '2'; no++)
+ {
+ std::packaged_task<void()> task([size, offset, &srch, no, &done] {
+ auto _h(srch.clone());
+ if(!_h)
+ {
+ throw std::runtime_error(std::string("concurrency::atomic_rewrite_" // NOLINT
+ "quantum: Could not open work file "
+ "due to ") +
+ _h.error().message().c_str());
+ }
+ file_handle h(std::move(_h.value()));
+ std::vector<byte, utils::page_allocator<byte>> buffer(size, to_byte(no));
+ file_handle::const_buffer_type _reqs[1] = {{buffer.data(), size}};
+ file_handle::io_request<file_handle::const_buffers_type> reqs(_reqs, offset);
+ --done;
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ while(done == 0u)
+ {
+ h.write(reqs).value();
+ }
+ });
+ auto f(task.get_future());
+ writers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+ // Wait till the writers launch
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ unsigned concurrency = std::thread::hardware_concurrency() - 2;
+ if(concurrency < 4)
+ {
+ concurrency = 4;
+ }
+ std::atomic<io_service::extent_type> max_aligned_atomic_rewrite(sp.max_aligned_atomic_rewrite.value);
+ std::atomic<bool> failed(false);
+ for(unsigned no = 0; no < concurrency; no++)
+ {
+ std::packaged_task<void()> task([size, offset, &srch, &done, &max_aligned_atomic_rewrite, &failed] {
+ auto _h(srch.clone());
+ if(!_h)
+ {
+ throw std::runtime_error(std::string("concurrency::atomic_rewrite_" // NOLINT
+ "quantum: Could not open work file "
+ "due to ") +
+ _h.error().message().c_str());
+ }
+ file_handle h(std::move(_h.value()));
+ std::vector<byte, utils::page_allocator<byte>> buffer(size, to_byte(0)), tocmp(size, to_byte(0));
+ file_handle::buffer_type _reqs[1] = {{buffer.data(), size}};
+ file_handle::io_request<file_handle::buffers_type> reqs(_reqs, offset);
+ while(done == 0u)
+ {
+ h.read(reqs).value();
+ // memset(tocmp.data(), buffer.front(), size);
+ // if (memcmp(buffer.data(), tocmp.data(), size))
+ {
+ const size_t *data = reinterpret_cast<size_t *>(buffer.data()), *end = reinterpret_cast<size_t *>(buffer.data() + size);
+ for(const size_t *d = data; d < end; d++)
+ {
+ if(*d != *data)
+ {
+ failed = true;
+ off_t failedat = (d - data);
+ if(failedat < max_aligned_atomic_rewrite)
+ {
+#ifndef NDEBUG
+ std::cout << " Torn rewrite at offset " << failedat << std::endl;
+#endif
+ max_aligned_atomic_rewrite = failedat;
+ }
+ break;
+ }
+ }
+ }
+ }
+ });
+ auto f(task.get_future());
+ readers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+
+#ifndef NDEBUG
+ std::cout << "direct=" << !srch.are_reads_from_cache() << " sync=" << srch.are_writes_durable() << " testing atomicity of rewrites of " << size << " bytes to offset " << offset << " ..." << std::endl;
+#endif
+ auto begin = std::chrono::high_resolution_clock::now();
+ while(!failed && std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - begin).count() < (20 / AFIO_STORAGE_PROFILE_TIME_DIVIDER))
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ done = 1u;
+ for(auto &writer : writers)
+ {
+ writer.first->join();
+ }
+ for(auto &reader : readers)
+ {
+ reader.first->join();
+ }
+ for(auto &writer : writers)
+ {
+ writer.second.get();
+ }
+ for(auto &reader : readers)
+ {
+ reader.second.get();
+ }
+ sp.max_aligned_atomic_rewrite.value = max_aligned_atomic_rewrite;
+ if(failed)
+ {
+ return success();
+ }
+ }
+ }
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ return success();
+ }
+
+ outcome<void> atomic_rewrite_offset_boundary(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.atomic_rewrite_offset_boundary.value != static_cast<io_service::extent_type>(-1))
+ {
+ return success();
+ }
+#ifdef _WIN32 // The 4Kb min i/o makes this test take too long
+ if(srch.requires_aligned_io())
+ {
+ return success();
+ }
+#endif
+ try
+ {
+ using off_t = io_service::extent_type;
+ auto size = static_cast<size_t>(sp.max_aligned_atomic_rewrite.value);
+ auto maxsize = static_cast<size_t>(sp.max_aligned_atomic_rewrite.value);
+ if(size > 1024)
+ {
+ size = 1024;
+ }
+ if(maxsize > 8192)
+ {
+ maxsize = 8192;
+ }
+ sp.atomic_rewrite_offset_boundary.value = static_cast<off_t>(-1);
+ if(size > 1)
+ {
+ for(; size <= maxsize; size = size * 2)
+ {
+ for(off_t offset = 512; offset < size; offset += 512)
+ {
+ // Create two concurrent writer threads and as many reader threads as additional CPU cores
+ // The excessive unique_ptr works around a bug in libc++'s thread implementation
+ std::vector<std::pair<std::unique_ptr<std::thread>, std::future<void>>> writers, readers;
+ std::atomic<size_t> done(2);
+ for(char no = '1'; no <= '2'; no++)
+ {
+ std::packaged_task<void()> task([size, offset, &srch, no, &done] {
+ auto _h(srch.clone());
+ if(!_h)
+ {
+ throw std::runtime_error(std::string("concurrency::atomic_rewrite_" // NOLINT
+ "offset_boundary: Could not open "
+ "work file due to ") +
+ _h.error().message().c_str());
+ }
+ file_handle h(std::move(_h.value()));
+ std::vector<byte, utils::page_allocator<byte>> buffer(size, to_byte(no));
+ file_handle::const_buffer_type _reqs[1] = {{buffer.data(), size}};
+ file_handle::io_request<file_handle::const_buffers_type> reqs(_reqs, offset);
+ --done;
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ while(done == 0u)
+ {
+ h.write(reqs).value();
+ }
+ });
+ auto f(task.get_future());
+ writers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+ // Wait till the writers launch
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ unsigned concurrency = std::thread::hardware_concurrency() - 2;
+ if(concurrency < 4)
+ {
+ concurrency = 4;
+ }
+ std::atomic<io_service::extent_type> atomic_rewrite_offset_boundary(sp.atomic_rewrite_offset_boundary.value);
+ std::atomic<bool> failed(false);
+ for(unsigned no = 0; no < concurrency; no++)
+ {
+ std::packaged_task<void()> task([size, offset, &srch, &done, &atomic_rewrite_offset_boundary, &failed] {
+ auto _h(srch.clone());
+ if(!_h)
+ {
+ throw std::runtime_error(std::string("concurrency::atomic_rewrite_" // NOLINT
+ "offset_boundary: Could not open "
+ "work file due to ") +
+ _h.error().message().c_str());
+ }
+ file_handle h(std::move(_h.value()));
+ std::vector<byte, utils::page_allocator<byte>> buffer(size, to_byte(0)), tocmp(size, to_byte(0));
+ file_handle::buffer_type _reqs[1] = {{buffer.data(), size}};
+ file_handle::io_request<file_handle::buffers_type> reqs(_reqs, offset);
+ while(done == 0u)
+ {
+ h.read(reqs).value();
+ // memset(tocmp.data(), buffer.front(), size);
+ // if (memcmp(buffer.data(), tocmp.data(), size))
+ {
+ const size_t *data = reinterpret_cast<size_t *>(buffer.data()), *end = reinterpret_cast<size_t *>(buffer.data() + size);
+ for(const size_t *d = data; d < end; d++)
+ {
+ if(*d != *data)
+ {
+ failed = true;
+ off_t failedat = (d - data) + offset;
+ if(failedat < atomic_rewrite_offset_boundary)
+ {
+#ifndef NDEBUG
+ std::cout << " Torn rewrite at offset " << failedat << std::endl;
+#endif
+ atomic_rewrite_offset_boundary = failedat;
+ }
+ break;
+ }
+ }
+ }
+ }
+ });
+ auto f(task.get_future());
+ readers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+
+#ifndef NDEBUG
+ std::cout << "direct=" << !srch.are_reads_from_cache() << " sync=" << srch.are_writes_durable() << " testing atomicity of rewrites of " << size << " bytes to offset " << offset << " ..." << std::endl;
+#endif
+ auto begin = std::chrono::high_resolution_clock::now();
+ while(!failed && std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - begin).count() < (20 / AFIO_STORAGE_PROFILE_TIME_DIVIDER))
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ done = 1u;
+ for(auto &writer : writers)
+ {
+ writer.first->join();
+ }
+ for(auto &reader : readers)
+ {
+ reader.first->join();
+ }
+ for(auto &writer : writers)
+ {
+ writer.second.get();
+ }
+ for(auto &reader : readers)
+ {
+ reader.second.get();
+ }
+ sp.atomic_rewrite_offset_boundary.value = atomic_rewrite_offset_boundary;
+ if(failed)
+ {
+ return success();
+ }
+ }
+ }
+ }
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ return success();
+ }
+ } // namespace concurrency
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ namespace latency
+ {
+ struct stats
+ {
+ unsigned long long min{0}, mean{0}, max{0}, _50{0}, _95{0}, _99{0}, _99999{0};
+ };
+ inline outcome<stats> _latency_test(file_handle &srch, size_t noreaders, size_t nowriters, bool ownfiles)
+ {
+ static constexpr size_t memory_to_use = 128 * 1024 * 1024; // 1Gb
+ // static const unsigned clock_overhead = system::_clock_granularity_and_overhead().overhead;
+ static const unsigned clock_granularity = system::_clock_granularity_and_overhead().granularity;
+ try
+ {
+ std::vector<file_handle> _workfiles;
+ _workfiles.reserve(noreaders + nowriters);
+ std::vector<file_handle *> workfiles(noreaders + nowriters, &srch);
+ path_handle base = srch.parent_path_handle().value();
+ if(ownfiles)
+ {
+ std::vector<byte, utils::page_allocator<byte>> buffer(1024 * 1024 * 1024);
+ for(size_t n = 0; n < noreaders + nowriters; n++)
+ {
+ auto fh = file_handle::file(base, std::to_string(n), file_handle::mode::write, file_handle::creation::open_existing, srch.kernel_caching(), srch.flags());
+ if(!fh)
+ {
+ fh = file_handle::file(base, std::to_string(n), file_handle::mode::write, file_handle::creation::if_needed, srch.kernel_caching(), srch.flags() | file_handle::flag::unlink_on_first_close);
+ fh.value().write(0, {{buffer.data(), buffer.size()}}).value();
+ }
+ _workfiles.push_back(std::move(fh.value()));
+ workfiles[n] = &_workfiles.back();
+ }
+ }
+ (void) utils::drop_filesystem_cache();
+
+ std::vector<std::vector<unsigned long long>> results(noreaders + nowriters);
+ // The excessive unique_ptr works around a bug in libc++'s thread implementation
+ std::vector<std::pair<std::unique_ptr<std::thread>, std::future<void>>> writers, readers;
+ std::atomic<size_t> done(noreaders + nowriters);
+ for(auto &i : results)
+ {
+ i.resize(memory_to_use / results.size());
+ memset(i.data(), 0, i.size() * sizeof(unsigned long long)); // prefault
+ i.resize(0);
+ }
+ for(size_t no = 0; no < nowriters; no++)
+ {
+ std::packaged_task<void()> task([no, &done, &workfiles, &results] {
+#ifdef AFIO_STORAGE_PROFILE_PIN_THREADS
+ SetThreadAffinityMask(GetCurrentThread(), 1ULL << (no * 2));
+#endif
+ file_handle &h = *workfiles[no];
+ alignas(4096) byte buffer[4096];
+ memset(buffer, static_cast<int>(no), 4096);
+ file_handle::const_buffer_type _reqs[1] = {{buffer, 4096}};
+ file_handle::io_request<file_handle::const_buffers_type> reqs(_reqs, 0);
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::small_prng rand(static_cast<uint32_t>(no));
+ auto maxsize = h.maximum_extent().value();
+ --done;
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ while(done == 0u)
+ {
+ reqs.offset = (rand() % maxsize) & ~4095ULL;
+ auto begin = std::chrono::high_resolution_clock::now();
+ h.write(reqs).value();
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ns = (std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count());
+ if(ns == 0)
+ {
+ ns = clock_granularity / 2;
+ }
+ results[no].push_back(ns);
+ if(results[no].size() == results[no].capacity())
+ {
+ return;
+ }
+ }
+ });
+ auto f(task.get_future());
+ writers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+ for(size_t no = nowriters; no < nowriters + noreaders; no++)
+ {
+ std::packaged_task<void()> task([no, &done, &workfiles, &results] {
+#ifdef AFIO_STORAGE_PROFILE_PIN_THREADS
+ SetThreadAffinityMask(GetCurrentThread(), 1ULL << (no * 2));
+#endif
+ file_handle &h = *workfiles[no];
+ alignas(4096) byte buffer[4096];
+ memset(buffer, static_cast<int>(no), 4096);
+ file_handle::buffer_type _reqs[1] = {{buffer, 4096}};
+ file_handle::io_request<file_handle::buffers_type> reqs(_reqs, 0);
+ QUICKCPPLIB_NAMESPACE::algorithm::small_prng::small_prng rand(static_cast<uint32_t>(no));
+ auto maxsize = h.maximum_extent().value();
+ --done;
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ while(done == 0u)
+ {
+ reqs.offset = (rand() % maxsize) & ~4095ULL;
+ auto begin = std::chrono::high_resolution_clock::now();
+ h.read(reqs).value();
+ auto end = std::chrono::high_resolution_clock::now();
+ auto ns = (std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()); // / 16;
+ if(ns == 0)
+ {
+ ns = clock_granularity / 2;
+ }
+ results[no].push_back(ns);
+ if(results[no].size() == results[no].capacity())
+ {
+ return;
+ }
+ }
+ });
+ auto f(task.get_future());
+ readers.emplace_back(std::make_unique<std::thread>(std::move(task)), std::move(f));
+ }
+ // Wait till the readers and writers launch
+ while(done != 0u)
+ {
+ std::this_thread::yield();
+ }
+ auto begin = std::chrono::high_resolution_clock::now();
+ while(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::high_resolution_clock::now() - begin).count() < (10))
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ done = 1u;
+ for(auto &writer : writers)
+ {
+ writer.first->join();
+ }
+ for(auto &reader : readers)
+ {
+ reader.first->join();
+ }
+ for(auto &writer : writers)
+ {
+ writer.second.get();
+ }
+ for(auto &reader : readers)
+ {
+ reader.second.get();
+ }
+
+#if 0 // ndef NDEBUG
+ {
+ std::ofstream out("latencies.csv");
+ for(const auto &r : results)
+ {
+ for(const auto &i : r)
+ {
+ out << i << "\n";
+ }
+ }
+ }
+#endif
+ std::vector<unsigned long long> totalresults;
+ unsigned long long sum = 0;
+ stats s;
+ s.min = static_cast<unsigned long long>(-1);
+ for(auto &result : results)
+ {
+ for(const auto &i : result)
+ {
+ if(i < s.min)
+ {
+ s.min = i;
+ }
+ if(i > s.max)
+ {
+ s.max = i;
+ }
+ sum += i;
+ totalresults.push_back(i);
+ }
+ result.clear();
+ result.shrink_to_fit();
+ }
+#ifndef NDEBUG
+ std::cout << "Total results = " << totalresults.size() << std::endl;
+#endif
+ s.mean = static_cast<unsigned long long>(static_cast<double>(sum) / totalresults.size());
+ // Latency distributions are definitely not normally distributed, but here we have the
+ // advantage of tons of sample points. So simply sort into order, and pluck out the values
+ // at 99.999%, 99% and 95%. It'll be accurate enough.
+ std::sort(totalresults.begin(), totalresults.end());
+ s._50 = totalresults[static_cast<size_t>(0.5 * totalresults.size())];
+ s._95 = totalresults[static_cast<size_t>(0.95 * totalresults.size())];
+ s._99 = totalresults[static_cast<size_t>(0.99 * totalresults.size())];
+ s._99999 = totalresults[static_cast<size_t>(0.99999 * totalresults.size())];
+ return s;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ }
+ outcome<void> read_qd1(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.read_qd1_mean.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ OUTCOME_TRY(s, _latency_test(srch, 1, 0, false));
+ sp.read_qd1_min.value = s.min;
+ sp.read_qd1_mean.value = s.mean;
+ sp.read_qd1_max.value = s.max;
+ sp.read_qd1_50.value = s._50;
+ sp.read_qd1_95.value = s._95;
+ sp.read_qd1_99.value = s._99;
+ sp.read_qd1_99999.value = s._99999;
+ return success();
+ }
+ outcome<void> write_qd1(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.write_qd1_mean.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ OUTCOME_TRY(s, _latency_test(srch, 0, 1, false));
+ sp.write_qd1_min.value = s.min;
+ sp.write_qd1_mean.value = s.mean;
+ sp.write_qd1_max.value = s.max;
+ sp.write_qd1_50.value = s._50;
+ sp.write_qd1_95.value = s._95;
+ sp.write_qd1_99.value = s._99;
+ sp.write_qd1_99999.value = s._99999;
+ return success();
+ }
+ outcome<void> read_qd16(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.read_qd16_mean.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ OUTCOME_TRY(s, _latency_test(srch, 16, 0, true));
+ sp.read_qd16_min.value = s.min;
+ sp.read_qd16_mean.value = s.mean;
+ sp.read_qd16_max.value = s.max;
+ sp.read_qd16_50.value = s._50;
+ sp.read_qd16_95.value = s._95;
+ sp.read_qd16_99.value = s._99;
+ sp.read_qd16_99999.value = s._99999;
+ return success();
+ }
+ outcome<void> write_qd16(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.write_qd16_mean.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ OUTCOME_TRY(s, _latency_test(srch, 0, 16, true));
+ sp.write_qd16_min.value = s.min;
+ sp.write_qd16_mean.value = s.mean;
+ sp.write_qd16_max.value = s.max;
+ sp.write_qd16_50.value = s._50;
+ sp.write_qd16_95.value = s._95;
+ sp.write_qd16_99.value = s._99;
+ sp.write_qd16_99999.value = s._99999;
+ return success();
+ }
+ outcome<void> readwrite_qd4(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.readwrite_qd4_mean.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ OUTCOME_TRY(s, _latency_test(srch, 3, 1, true));
+ sp.readwrite_qd4_min.value = s.min;
+ sp.readwrite_qd4_mean.value = s.mean;
+ sp.readwrite_qd4_max.value = s.max;
+ sp.readwrite_qd4_50.value = s._50;
+ sp.readwrite_qd4_95.value = s._95;
+ sp.readwrite_qd4_99.value = s._99;
+ sp.readwrite_qd4_99999.value = s._99999;
+ return success();
+ }
+ outcome<void> read_nothing(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.read_nothing.value != static_cast<unsigned>(-1))
+ {
+ return success();
+ }
+ volatile size_t errors = 0;
+ auto begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < 1000000; n++)
+ {
+ if(!srch.read(0, {{nullptr, 0}}))
+ {
+ ++errors;
+ }
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto diff = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
+ sp.read_nothing.value = static_cast<unsigned>(diff / 1000000);
+ return success();
+ }
+ outcome<void> write_nothing(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.write_nothing.value != static_cast<unsigned>(-1))
+ {
+ return success();
+ }
+ volatile size_t errors = 0;
+ auto begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < 1000000; n++)
+ {
+ if(!srch.write(0, {{nullptr, 0}}))
+ {
+ ++errors;
+ }
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto diff = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
+ sp.write_nothing.value = static_cast<unsigned>(diff / 1000000);
+ return success();
+ }
+ } // namespace latency
+ namespace response_time
+ {
+ struct stats
+ {
+ unsigned long long create{0}, enumerate{0}, open_read{0}, open_write{0}, destroy{0};
+ };
+ inline outcome<stats> _traversal_N(file_handle &srch, size_t no, size_t bytes, bool cold_cache, bool race_free, bool reduced) noexcept
+ {
+ stats s;
+#ifdef AFIO_STORAGE_PROFILE_PIN_THREADS
+ SetThreadAffinityMask(GetCurrentThread(), 1ULL << (no * 2));
+#endif
+ try
+ {
+ directory_handle dirh(directory_handle::directory(srch.parent_path_handle().value(), "testdir", directory_handle::mode::write, directory_handle::creation::if_needed).value());
+ auto flags = srch.flags();
+ std::string filename;
+ filename.reserve(16);
+ std::chrono::high_resolution_clock::time_point begin, end;
+ alignas(4096) byte buffer[4096];
+ memset(buffer, 78, sizeof(buffer));
+ if(!race_free)
+ {
+ flags |= handle::flag::disable_safety_unlinks | handle::flag::win_disable_unlink_emulation;
+ }
+ if(srch.requires_aligned_io())
+ {
+ bytes = utils::round_down_to_page_size(bytes);
+ }
+
+ if(cold_cache)
+ {
+ OUTCOME_TRYV(utils::drop_filesystem_cache());
+ }
+ begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < no; n++)
+ {
+ filename = std::to_string(n);
+ file_handle fileh(file_handle::file(dirh, filename, file_handle::mode::write, file_handle::creation::if_needed, srch.kernel_caching(), flags).value());
+ if(bytes > 0)
+ {
+ fileh.write(0, {{buffer, bytes}}).value();
+ }
+ }
+ if(srch.kernel_caching() == file_handle::caching::reads || srch.kernel_caching() == file_handle::caching::none)
+ {
+ (void) utils::flush_modified_data();
+ }
+ end = std::chrono::high_resolution_clock::now();
+ s.create = static_cast<unsigned long long>(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) / no);
+ if(cold_cache)
+ {
+ (void) utils::drop_filesystem_cache();
+ }
+
+ std::vector<directory_entry> entries(no);
+ begin = std::chrono::high_resolution_clock::now();
+ directory_handle::enumerate_info ei(dirh.enumerate(entries).value());
+ assert(ei.done == true);
+ assert(ei.filled.size() == no);
+ end = std::chrono::high_resolution_clock::now();
+ s.enumerate = static_cast<unsigned long long>(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) / no);
+ if(cold_cache)
+ {
+ (void) utils::drop_filesystem_cache();
+ }
+
+ if(!reduced)
+ {
+ begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < no; n++)
+ {
+ filename = std::to_string(n);
+ file_handle fileh(file_handle::file(dirh, filename, file_handle::mode::read, file_handle::creation::open_existing, srch.kernel_caching(), flags).value());
+ }
+ // For atime updating
+ if(srch.kernel_caching() == file_handle::caching::reads || srch.kernel_caching() == file_handle::caching::none)
+ {
+ (void) utils::flush_modified_data();
+ }
+ end = std::chrono::high_resolution_clock::now();
+ s.open_read = static_cast<unsigned long long>(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) / no);
+ if(cold_cache)
+ {
+ (void) utils::drop_filesystem_cache();
+ }
+
+ begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < no; n++)
+ {
+ filename = std::to_string(n);
+ file_handle fileh(file_handle::file(dirh, filename, file_handle::mode::write, file_handle::creation::open_existing, srch.kernel_caching(), flags).value());
+ }
+ // For atime updating
+ if(srch.kernel_caching() == file_handle::caching::reads || srch.kernel_caching() == file_handle::caching::none)
+ {
+ (void) utils::flush_modified_data();
+ }
+ end = std::chrono::high_resolution_clock::now();
+ s.open_write = static_cast<unsigned long long>(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) / no);
+ if(cold_cache)
+ {
+ (void) utils::drop_filesystem_cache();
+ }
+ }
+
+ begin = std::chrono::high_resolution_clock::now();
+ for(size_t n = 0; n < no; n++)
+ {
+ filename = std::to_string(n);
+ file_handle fileh(file_handle::file(dirh, filename, file_handle::mode::write, file_handle::creation::open_existing, srch.kernel_caching(), flags).value());
+ fileh.unlink().value();
+ }
+ if(srch.kernel_caching() == file_handle::caching::reads || srch.kernel_caching() == file_handle::caching::none)
+ {
+ (void) utils::flush_modified_data();
+ }
+ end = std::chrono::high_resolution_clock::now();
+ s.destroy = static_cast<unsigned long long>(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) / no);
+
+ dirh.unlink().value();
+ return s;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ }
+ outcome<void> traversal_warm_racefree_0b(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.create_file_warm_racefree_0b.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ size_t items = srch.are_writes_durable() ? 256 : 16384;
+ OUTCOME_TRY(s, _traversal_N(srch, items, 0, false, true, false));
+ sp.create_file_warm_racefree_0b.value = s.create;
+ sp.enumerate_file_warm_racefree_0b.value = s.enumerate;
+ sp.open_file_read_warm_racefree_0b.value = s.open_read;
+ sp.open_file_write_warm_racefree_0b.value = s.open_write;
+ sp.delete_file_warm_racefree_0b.value = s.destroy;
+ return success();
+ }
+ outcome<void> traversal_warm_nonracefree_0b(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.create_file_warm_nonracefree_0b.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ size_t items = srch.are_writes_durable() ? 256 : 16384;
+ OUTCOME_TRY(s, _traversal_N(srch, items, 0, false, false, false));
+ sp.create_file_warm_nonracefree_0b.value = s.create;
+ sp.enumerate_file_warm_nonracefree_0b.value = s.enumerate;
+ sp.open_file_read_warm_nonracefree_0b.value = s.open_read;
+ sp.open_file_write_warm_nonracefree_0b.value = s.open_write;
+ sp.delete_file_warm_nonracefree_0b.value = s.destroy;
+ return success();
+ }
+ outcome<void> traversal_cold_racefree_0b(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.create_file_cold_racefree_0b.value != static_cast<unsigned long long>(-1))
+ {
+ return success();
+ }
+ size_t items = srch.are_writes_durable() ? 256 : 16384;
+ OUTCOME_TRY(s, _traversal_N(srch, items, 0, true, true, false));
+ sp.create_file_cold_racefree_0b.value = s.create;
+ sp.enumerate_file_cold_racefree_0b.value = s.enumerate;
+ sp.open_file_read_cold_racefree_0b.value = s.open_read;
+ sp.open_file_write_cold_racefree_0b.value = s.open_write;
+ sp.delete_file_cold_racefree_0b.value = s.destroy;
+ return success();
+ }
+ /*
+ outcome<void> traversal_warm_nonracefree_1M(storage_profile &sp, file_handle &srch) noexcept
+ {
+ if(sp.create_1M_files.value != (unsigned) -1)
+ return success();
+ if(srch.kernel_caching() != file_handle::caching::all)
+ {
+ return errc::invalid_argument;
+ }
+ OUTCOME_TRY(s, _traversal_N(srch, 1000000, 0, false, false, true));
+ sp.create_1M_files.value = s.create;
+ sp.enumerate_1M_files.value = s.enumerate;
+ sp.delete_1M_files.value = s.destroy;
+ return success();
+ }
+ */
+ } // namespace response_time
+} // namespace storage_profile
+AFIO_V2_NAMESPACE_END
+
+#ifdef WIN32
+#include "windows/storage_profile.ipp"
+#else
+#include "posix/storage_profile.ipp"
+#endif
diff --git a/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp b/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp
new file mode 100644
index 00000000..b70971d3
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp
@@ -0,0 +1,277 @@
+/* A handle to a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (7 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_handle::barrier(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, bool wait_for_device, bool and_metadata, deadline d) noexcept
+{
+ // Pass through the file_handle's implementation, it understands overlapped handles
+ return file_handle::barrier(reqs, wait_for_device, and_metadata, d);
+}
+
+template <class BuffersType, class IORoutine> result<async_file_handle::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, async_file_handle::io_request<BuffersType> reqs, async_file_handle::_erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept
+{
+ // Need to keep a set of OVERLAPPED matching the scatter-gather buffers
+ struct state_type : public _erased_io_state_type
+ {
+ OVERLAPPED ols[1];
+ _erased_completion_handler *completion;
+ state_type(async_file_handle *_parent, operation_t _operation, bool must_deallocate_self, size_t _items) // NOLINT
+ : _erased_io_state_type(_parent, _operation, must_deallocate_self, _items),
+ completion(nullptr)
+ {
+ }
+ state_type(state_type &&) = delete;
+ state_type(const state_type &) = delete;
+ state_type &operator=(state_type &&) = delete;
+ state_type &operator=(const state_type &) = delete;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC _erased_completion_handler *erased_completion_handler() noexcept override final { return completion; }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept override final
+ {
+ auto ol = static_cast<LPOVERLAPPED>(internal_state);
+ ol->hEvent = nullptr;
+ auto &result = this->result.write;
+ if(result)
+ {
+ if(errcode)
+ {
+ result = win32_error(errcode);
+ }
+ else
+ {
+ // Figure out which i/o I am and update the buffer in question
+ size_t idx = ol - ols;
+ if(idx >= this->items)
+ {
+ AFIO_LOG_FATAL(0, "async_file_handle::io_state::operator() called with invalid index");
+ std::terminate();
+ }
+ result.value()[idx].len = bytes_transferred;
+ }
+ }
+ this->parent->service()->_work_done();
+ // Are we done?
+ if(!--this->items_to_go)
+ {
+ (*completion)(this);
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~state_type() override final
+ {
+ // Do we need to cancel pending i/o?
+ if(this->items_to_go)
+ {
+ for(size_t n = 0; n < this->items; n++)
+ {
+ // If this is non-zero, probably this i/o still in flight
+ if(ols[n].hEvent)
+ {
+ CancelIoEx(this->parent->native_handle().h, ols + n);
+ }
+ }
+ // Pump the i/o service until all pending i/o is completed
+ while(this->items_to_go)
+ {
+ auto res = this->parent->service()->run();
+ (void) res;
+#ifndef NDEBUG
+ if(res.has_error())
+ {
+ AFIO_LOG_FATAL(0, "async_file_handle: io_service failed");
+ std::terminate();
+ }
+ if(!res.value())
+ {
+ AFIO_LOG_FATAL(0, "async_file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ }
+ completion->~_erased_completion_handler();
+ }
+ } * state;
+ extent_type offset = reqs.offset;
+ size_t statelen = sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(OVERLAPPED) + completion.bytes();
+ if(!mem.empty() && statelen > mem.size())
+ {
+ return errc::not_enough_memory;
+ }
+ size_t items(reqs.buffers.size());
+ // On Windows i/o must be scheduled on the same thread pumping completion
+ if(GetCurrentThreadId() != service()->_threadid)
+ {
+ return errc::operation_not_supported;
+ }
+
+ bool must_deallocate_self = false;
+ if(mem.empty())
+ {
+ void *_mem = ::calloc(1, statelen); // NOLINT
+ if(!_mem)
+ {
+ return errc::not_enough_memory;
+ }
+ mem = {static_cast<char *>(_mem), statelen};
+ must_deallocate_self = true;
+ }
+ io_state_ptr _state(reinterpret_cast<state_type *>(mem.data()));
+ new((state = reinterpret_cast<state_type *>(mem.data()))) state_type(this, operation, must_deallocate_self, items);
+ state->completion = reinterpret_cast<_erased_completion_handler *>(reinterpret_cast<uintptr_t>(state) + sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(OVERLAPPED));
+ completion.move(state->completion);
+
+ // To be called once each buffer is read
+ struct handle_completion
+ {
+ static VOID CALLBACK Do(DWORD errcode, DWORD bytes_transferred, LPOVERLAPPED ol)
+ {
+ auto *state = reinterpret_cast<state_type *>(ol->hEvent);
+ state->_system_io_completion(errcode, bytes_transferred, ol);
+ }
+ };
+ // Noexcept move the buffers from req into result
+ auto &out = state->result.write.value();
+ out = std::move(reqs.buffers);
+ for(size_t n = 0; n < items; n++)
+ {
+ LPOVERLAPPED ol = state->ols + n;
+ ol->Internal = static_cast<ULONG_PTR>(-1);
+ if(_v.is_append_only())
+ {
+ ol->OffsetHigh = ol->Offset = 0xffffffff;
+ }
+ else
+ {
+#ifndef NDEBUG
+ if(_v.requires_aligned_io())
+ {
+ assert((offset & 511) == 0);
+ }
+#endif
+ ol->Offset = offset & 0xffffffff;
+ ol->OffsetHigh = (offset >> 32) & 0xffffffff;
+ }
+ // Use the unused hEvent member to pass through the state
+ ol->hEvent = reinterpret_cast<HANDLE>(state);
+ offset += out[n].len;
+ ++state->items_to_go;
+#ifndef NDEBUG
+ if(_v.requires_aligned_io())
+ {
+ assert((reinterpret_cast<uintptr_t>(out[n].data) & 511) == 0);
+ assert((out[n].len & 511) == 0);
+ }
+#endif
+ if(!ioroutine(_v.h, const_cast<byte *>(out[n].data), static_cast<DWORD>(out[n].len), ol, handle_completion::Do))
+ {
+ --state->items_to_go;
+ state->result.write = win32_error();
+ // Fire completion now if we didn't schedule anything
+ if(!n)
+ {
+ (*state->completion)(state);
+ }
+ return _state;
+ }
+ service()->_work_enqueued();
+ }
+ return _state;
+}
+
+result<async_file_handle::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, io_request<const_buffers_type> reqs, async_file_handle::_erased_completion_handler &&completion) noexcept
+{
+ switch(operation)
+ {
+ case operation_t::read:
+ return _begin_io(mem, operation, reqs, std::move(completion), ReadFileEx);
+ case operation_t::write:
+ return _begin_io(mem, operation, reqs, std::move(completion), WriteFileEx);
+ case operation_t::fsync_async:
+ case operation_t::dsync_async:
+ case operation_t::fsync_sync:
+ case operation_t::dsync_sync:
+ break;
+ }
+ return errc::operation_not_supported;
+}
+
+async_file_handle::io_result<async_file_handle::buffers_type> async_file_handle::read(async_file_handle::io_request<async_file_handle::buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ optional<io_result<buffers_type>> ret;
+ OUTCOME_TRY(io_state, async_read(reqs, [&ret](async_file_handle *, io_result<buffers_type> &result) { ret = result; }));
+ (void) io_state; // holds i/o open until it completes
+
+ // While i/o is not done pump i/o completion
+ while(!ret)
+ {
+ auto t(_service->run_until(d));
+ // If i/o service pump failed or timed out, cancel outstanding i/o and return
+ if(!t)
+ {
+ return t.error();
+ }
+#ifndef NDEBUG
+ if(!ret && t && !t.value())
+ {
+ AFIO_LOG_FATAL(_v.h, "async_file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ return *ret;
+}
+
+async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_handle::write(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ optional<io_result<const_buffers_type>> ret;
+ OUTCOME_TRY(io_state, async_write(reqs, [&ret](async_file_handle *, io_result<const_buffers_type> &result) { ret = result; }));
+ (void) io_state; // holds i/o open until it completes
+
+ // While i/o is not done pump i/o completion
+ while(!ret)
+ {
+ auto t(_service->run_until(d));
+ // If i/o service pump failed or timed out, cancel outstanding i/o and return
+ if(!t)
+ {
+ return t.error();
+ }
+#ifndef NDEBUG
+ if(!ret && t && !t.value())
+ {
+ AFIO_LOG_FATAL(_v.h, "async_file_handle: io_service returns no work when i/o has not completed");
+ std::terminate();
+ }
+#endif
+ }
+ return *ret;
+}
+
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp b/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp
new file mode 100644
index 00000000..185f5077
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp
@@ -0,0 +1,367 @@
+/* A handle to a directory
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+(See accompanying file Licence.txt or copy at
+http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../directory_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<directory_handle> directory_handle::directory(const path_handle &base, path_view_type path, mode _mode, creation _creation, caching _caching, flag flags) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ if(flags & flag::unlink_on_first_close)
+ {
+ return errc::invalid_argument;
+ }
+ result<directory_handle> ret(directory_handle(native_handle_type(), 0, 0, _caching, flags));
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::directory;
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ // Trying to truncate a directory returns EISDIR rather than some internal Win32 error code uncomparable to errc
+ if(_creation == creation::truncate)
+ {
+ return errc::is_a_directory;
+ }
+ OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, _mode, flags));
+ OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(nativeh, _caching, flags));
+ /* It is super important that we remove the DELETE permission for directories as otherwise relative renames
+ will always fail due to an unfortunate design choice by Microsoft.
+ */
+ access &= ~DELETE;
+ if(base.is_valid() || path.is_ntpath())
+ {
+ DWORD creatdisp = 0x00000001 /*FILE_OPEN*/;
+ switch(_creation)
+ {
+ case creation::open_existing:
+ break;
+ case creation::only_if_not_exist:
+ creatdisp = 0x00000002 /*FILE_CREATE*/;
+ break;
+ case creation::if_needed:
+ creatdisp = 0x00000003 /*FILE_OPEN_IF*/;
+ break;
+ case creation::truncate:
+ creatdisp = 0x00000004 /*FILE_OVERWRITE*/;
+ break;
+ }
+
+ attribs &= 0x00ffffff; // the real attributes only, not the win32 flags
+ OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, _caching, flags));
+ ntflags |= 0x01 /*FILE_DIRECTORY_FILE*/; // required to open a directory
+ IO_STATUS_BLOCK isb = make_iostatus();
+
+ path_view::c_str zpath(path, true);
+ UNICODE_STRING _path{};
+ _path.Buffer = const_cast<wchar_t *>(zpath.buffer);
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t);
+ if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\')
+ {
+ _path.Buffer += 3;
+ _path.Length -= 3 * sizeof(wchar_t);
+ _path.MaximumLength -= 3 * sizeof(wchar_t);
+ }
+
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.RootDirectory = base.is_valid() ? base.native_handle().h : nullptr;
+ oa.Attributes = 0x40 /*OBJ_CASE_INSENSITIVE*/;
+ // if(!!(flags & file_flags::int_opening_link))
+ // oa.Attributes|=0x100/*OBJ_OPENLINK*/;
+
+ LARGE_INTEGER AllocationSize{};
+ memset(&AllocationSize, 0, sizeof(AllocationSize));
+ NTSTATUS ntstat = NtCreateFile(&nativeh.h, access, &oa, &isb, &AllocationSize, attribs, fileshare, creatdisp, ntflags, nullptr, 0);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ else
+ {
+ DWORD creation = OPEN_EXISTING;
+ switch(_creation)
+ {
+ case creation::open_existing:
+ break;
+ case creation::only_if_not_exist:
+ creation = CREATE_NEW;
+ break;
+ case creation::if_needed:
+ creation = OPEN_ALWAYS;
+ break;
+ case creation::truncate:
+ creation = TRUNCATE_EXISTING;
+ break;
+ }
+ attribs |= FILE_FLAG_BACKUP_SEMANTICS; // required to open a directory
+ path_view::c_str zpath(path, false);
+ if(INVALID_HANDLE_VALUE == (nativeh.h = CreateFileW_(zpath.buffer, access, fileshare, nullptr, creation, attribs, nullptr, true))) // NOLINT
+ {
+ DWORD errcode = GetLastError();
+ // assert(false);
+ return win32_error(errcode);
+ }
+ }
+ return ret;
+}
+
+result<directory_handle> directory_handle::clone(mode mode_, caching caching_, deadline /* unused */) const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // Fast path
+ if(mode_ == mode::unchanged && caching_ == caching::unchanged)
+ {
+ result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, _caching, _flags));
+ ret.value()._v.behaviour = _v.behaviour;
+ if(DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, 0, DUPLICATE_SAME_ACCESS) == 0)
+ {
+ return win32_error();
+ }
+ return ret;
+ }
+ // Slow path
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ result<directory_handle> ret(directory_handle(native_handle_type(), _devid, _inode, caching_, _flags));
+ native_handle_type &nativeh = ret.value()._v;
+ nativeh.behaviour |= native_handle_type::disposition::directory;
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, mode_, _flags));
+ OUTCOME_TRYV(attributes_from_handle_caching_and_flags(nativeh, caching_, _flags));
+ /* It is super important that we remove the DELETE permission for directories as otherwise relative renames
+ will always fail due to an unfortunate design choice by Microsoft.
+ */
+ access &= ~DELETE;
+ OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, caching_, _flags));
+ ntflags |= 0x01 /*FILE_DIRECTORY_FILE*/; // required to open a directory
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ // It is entirely undocumented that this is how you clone a file handle with new privs
+ UNICODE_STRING _path{};
+ memset(&_path, 0, sizeof(_path));
+ oa.ObjectName = &_path;
+ oa.RootDirectory = _v.h;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtOpenFile(&nativeh.h, access, &oa, &isb, fileshare, ntflags);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return ret;
+}
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> directory_handle::clone_to_path_handle() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ result<path_handle> ret(path_handle(native_handle_type(), _caching, _flags));
+ ret.value()._v.behaviour = _v.behaviour;
+ if(DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, 0, DUPLICATE_SAME_ACCESS) == 0)
+ {
+ return win32_error();
+ }
+ return ret;
+}
+
+namespace detail
+{
+ inline result<file_handle> duplicate_handle_with_delete_privs(directory_handle *o) noexcept
+ {
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ native_handle_type nativeh = o->native_handle();
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ // It is entirely undocumented that this is how you clone a file handle with new privs
+ UNICODE_STRING _path{};
+ memset(&_path, 0, sizeof(_path));
+ oa.ObjectName = &_path;
+ oa.RootDirectory = o->native_handle().h;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtOpenFile(&nativeh.h, GENERIC_READ | SYNCHRONIZE | DELETE, &oa, &isb, fileshare, 0x01 /*FILE_DIRECTORY_FILE*/ | 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ // Return as a file handle so the direct relink and unlink are used
+ return file_handle(nativeh, 0, 0, file_handle::caching::all);
+ }
+} // namespace detail
+
+result<void> directory_handle::relink(const path_handle &base, directory_handle::path_view_type newpath, bool atomic_replace, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ /* We can never hold DELETE permission on an open handle to a directory as otherwise
+ race free renames into that directory will fail, so we are forced to duplicate the
+ handle with DELETE privs temporarily in order to issue the rename
+ */
+ OUTCOME_TRY(h, detail::duplicate_handle_with_delete_privs(this));
+ return h.relink(base, newpath, atomic_replace, d);
+}
+
+result<void> directory_handle::unlink(deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ /* We can never hold DELETE permission on an open handle to a directory as otherwise
+ race free renames into that directory will fail, so we are forced to duplicate the
+ handle with DELETE privs temporarily in order to issue the unlink
+ */
+ OUTCOME_TRY(h, detail::duplicate_handle_with_delete_privs(this));
+ return h.unlink(d);
+}
+
+result<directory_handle::enumerate_info> directory_handle::enumerate(buffers_type &&tofill, path_view_type glob, filter filtering, span<char> kernelbuffer) const noexcept
+{
+ static constexpr stat_t::want default_stat_contents = stat_t::want::ino | stat_t::want::type | stat_t::want::atim | stat_t::want::mtim | stat_t::want::ctim | stat_t::want::size | stat_t::want::allocated | stat_t::want::birthtim | stat_t::want::sparse | stat_t::want::compressed | stat_t::want::reparse_point;
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(tofill.empty())
+ {
+ return enumerate_info{std::move(tofill), stat_t::want::none, false};
+ }
+ UNICODE_STRING _glob{};
+ memset(&_glob, 0, sizeof(_glob));
+ path_view_type::c_str zglob(glob, true);
+ if(!glob.empty())
+ {
+ _glob.Buffer = const_cast<wchar_t *>(zglob.buffer);
+ _glob.Length = zglob.length * sizeof(wchar_t);
+ _glob.MaximumLength = _glob.Length + sizeof(wchar_t);
+ }
+ if(!tofill._kernel_buffer && kernelbuffer.empty())
+ {
+ // Let's assume the average leafname will be 64 characters long.
+ size_t toallocate = (sizeof(FILE_ID_FULL_DIR_INFORMATION) + 64 * sizeof(wchar_t)) * tofill.size();
+ auto *mem = new(std::nothrow) char[toallocate];
+ if(mem == nullptr)
+ {
+ return errc::not_enough_memory;
+ }
+ tofill._kernel_buffer = std::unique_ptr<char[]>(mem);
+ tofill._kernel_buffer_size = toallocate;
+ }
+ FILE_ID_FULL_DIR_INFORMATION *buffer;
+ ULONG bytes;
+ bool done = false;
+ do
+ {
+ buffer = kernelbuffer.empty() ? reinterpret_cast<FILE_ID_FULL_DIR_INFORMATION *>(tofill._kernel_buffer.get()) : reinterpret_cast<FILE_ID_FULL_DIR_INFORMATION *>(kernelbuffer.data());
+ bytes = kernelbuffer.empty() ? static_cast<ULONG>(tofill._kernel_buffer_size) : static_cast<ULONG>(kernelbuffer.size());
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtQueryDirectoryFile(_v.h, nullptr, nullptr, nullptr, &isb, buffer, bytes, FileIdFullDirectoryInformation, FALSE, glob.empty() ? nullptr : &_glob, TRUE);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(_v.h, isb, deadline());
+ }
+ if(kernelbuffer.empty() && STATUS_BUFFER_OVERFLOW == ntstat)
+ {
+ tofill._kernel_buffer.reset();
+ size_t toallocate = tofill._kernel_buffer_size * 2;
+ auto *mem = new(std::nothrow) char[toallocate];
+ if(mem == nullptr)
+ {
+ return errc::not_enough_memory;
+ }
+ tofill._kernel_buffer = std::unique_ptr<char[]>(mem);
+ tofill._kernel_buffer_size = toallocate;
+ }
+ else
+ {
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ done = true;
+ }
+ } while(!done);
+ size_t n = 0;
+ for(FILE_ID_FULL_DIR_INFORMATION *ffdi = buffer;; ffdi = reinterpret_cast<FILE_ID_FULL_DIR_INFORMATION *>(reinterpret_cast<uintptr_t>(ffdi) + ffdi->NextEntryOffset))
+ {
+ size_t length = ffdi->FileNameLength / sizeof(wchar_t);
+ if(length <= 2 && '.' == ffdi->FileName[0])
+ {
+ if(1 == length || '.' == ffdi->FileName[1])
+ {
+ continue;
+ }
+ }
+ // Try to zero terminate leafnames where possible for later efficiency
+ if(reinterpret_cast<uintptr_t>(ffdi->FileName + length) + sizeof(wchar_t) <= reinterpret_cast<uintptr_t>(ffdi) + ffdi->NextEntryOffset)
+ {
+ ffdi->FileName[length] = 0;
+ }
+ directory_entry &item = tofill[n];
+ item.leafname = path_view(wstring_view(ffdi->FileName, length));
+ if(filtering == filter::fastdeleted && item.leafname.is_afio_deleted())
+ {
+ continue;
+ }
+ item.stat = stat_t(nullptr);
+ item.stat.st_ino = ffdi->FileId.QuadPart;
+ item.stat.st_type = to_st_type(ffdi->FileAttributes, ffdi->ReparsePointTag);
+ item.stat.st_atim = to_timepoint(ffdi->LastAccessTime);
+ item.stat.st_mtim = to_timepoint(ffdi->LastWriteTime);
+ item.stat.st_ctim = to_timepoint(ffdi->ChangeTime);
+ item.stat.st_size = ffdi->EndOfFile.QuadPart;
+ item.stat.st_allocated = ffdi->AllocationSize.QuadPart;
+ item.stat.st_birthtim = to_timepoint(ffdi->CreationTime);
+ item.stat.st_sparse = static_cast<unsigned int>((ffdi->FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0u);
+ item.stat.st_compressed = static_cast<unsigned int>((ffdi->FileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0u);
+ item.stat.st_reparse_point = static_cast<unsigned int>((ffdi->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u);
+ n++;
+ if(ffdi->NextEntryOffset == 0u)
+ {
+ // Fill is complete
+ tofill._resize(n);
+ return enumerate_info{std::move(tofill), default_stat_contents, true};
+ }
+ if(n >= tofill.size())
+ {
+ // Fill is incomplete
+ return enumerate_info{std::move(tofill), default_stat_contents, false};
+ }
+ }
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/file_handle.ipp b/include/llfio/v2.0/detail/impl/windows/file_handle.ipp
new file mode 100644
index 00000000..e3d7fb27
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/file_handle.ipp
@@ -0,0 +1,499 @@
+/* A handle to a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (16 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../file_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<file_handle> file_handle::file(const path_handle &base, file_handle::path_view_type path, file_handle::mode _mode, file_handle::creation _creation, file_handle::caching _caching, file_handle::flag flags) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ result<file_handle> ret(file_handle(native_handle_type(), 0, 0, _caching, flags));
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::file;
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, _mode, flags));
+ OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(nativeh, _caching, flags));
+ bool need_to_set_sparse = false;
+ if(base.is_valid() || path.is_ntpath())
+ {
+ DWORD creatdisp = 0x00000001 /*FILE_OPEN*/;
+ switch(_creation)
+ {
+ case creation::open_existing:
+ break;
+ case creation::only_if_not_exist:
+ creatdisp = 0x00000002 /*FILE_CREATE*/;
+ break;
+ case creation::if_needed:
+ creatdisp = 0x00000003 /*FILE_OPEN_IF*/;
+ break;
+ case creation::truncate:
+ creatdisp = 0x00000004 /*FILE_OVERWRITE*/;
+ break;
+ }
+
+ attribs &= 0x00ffffff; // the real attributes only, not the win32 flags
+ OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, _caching, flags));
+ ntflags |= 0x040 /*FILE_NON_DIRECTORY_FILE*/; // do not open a directory
+ IO_STATUS_BLOCK isb = make_iostatus();
+
+ path_view::c_str zpath(path, true);
+ UNICODE_STRING _path{};
+ _path.Buffer = const_cast<wchar_t *>(zpath.buffer);
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t);
+ if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\')
+ {
+ _path.Buffer += 3;
+ _path.Length -= 3 * sizeof(wchar_t);
+ _path.MaximumLength -= 3 * sizeof(wchar_t);
+ }
+
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.RootDirectory = base.is_valid() ? base.native_handle().h : nullptr;
+ oa.Attributes = 0x40 /*OBJ_CASE_INSENSITIVE*/;
+ // if(!!(flags & file_flags::int_opening_link))
+ // oa.Attributes|=0x100/*OBJ_OPENLINK*/;
+
+ LARGE_INTEGER AllocationSize{};
+ memset(&AllocationSize, 0, sizeof(AllocationSize));
+ NTSTATUS ntstat = NtCreateFile(&nativeh.h, access, &oa, &isb, &AllocationSize, attribs, fileshare, creatdisp, ntflags, nullptr, 0);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ switch(_creation)
+ {
+ case creation::open_existing:
+ need_to_set_sparse = false;
+ break;
+ case creation::only_if_not_exist:
+ need_to_set_sparse = true;
+ break;
+ case creation::if_needed:
+ need_to_set_sparse = (isb.Information == 2 /*FILE_CREATED*/);
+ break;
+ case creation::truncate:
+ need_to_set_sparse = true;
+ break;
+ }
+ }
+ else
+ {
+ DWORD creation = OPEN_EXISTING;
+ switch(_creation)
+ {
+ case creation::open_existing:
+ break;
+ case creation::only_if_not_exist:
+ creation = CREATE_NEW;
+ break;
+ case creation::if_needed:
+ creation = OPEN_ALWAYS;
+ break;
+ case creation::truncate:
+ creation = TRUNCATE_EXISTING;
+ break;
+ }
+ path_view::c_str zpath(path, false);
+ if(INVALID_HANDLE_VALUE == (nativeh.h = CreateFileW_(zpath.buffer, access, fileshare, nullptr, creation, attribs, nullptr))) // NOLINT
+ {
+ DWORD errcode = GetLastError();
+ // assert(false);
+ return win32_error(errcode);
+ }
+ switch(_creation)
+ {
+ case creation::open_existing:
+ need_to_set_sparse = false;
+ break;
+ case creation::only_if_not_exist:
+ need_to_set_sparse = true;
+ break;
+ case creation::if_needed:
+ need_to_set_sparse = (GetLastError() == ERROR_ALREADY_EXISTS);
+ break;
+ case creation::truncate:
+ need_to_set_sparse = true;
+ break;
+ }
+ }
+ if(need_to_set_sparse && !(flags & flag::win_disable_sparse_file_creation))
+ {
+ DWORD bytesout = 0;
+ FILE_SET_SPARSE_BUFFER fssb = {1u};
+ if(DeviceIoControl(nativeh.h, FSCTL_SET_SPARSE, &fssb, sizeof(fssb), nullptr, 0, &bytesout, nullptr) == 0)
+ {
+#if AFIO_LOGGING_LEVEL >= 3
+ DWORD errcode = GetLastError();
+ AFIO_LOG_WARN(&ret, "Failed to set file to sparse");
+ result<void> r = win32_error(errcode);
+ (void) r; // throw away
+#endif
+ }
+ }
+ if(flags & flag::unlink_on_first_close)
+ {
+ // Hide this item
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_BASIC_INFORMATION fbi{};
+ memset(&fbi, 0, sizeof(fbi));
+ fbi.FileAttributes = FILE_ATTRIBUTE_HIDDEN;
+ NtSetInformationFile(nativeh.h, &isb, &fbi, sizeof(fbi), FileBasicInformation);
+ if(flags & flag::overlapped)
+ {
+ ntwait(nativeh.h, isb, deadline());
+ }
+ }
+ if(_creation == creation::truncate && ret.value().are_safety_fsyncs_issued())
+ {
+ FlushFileBuffers(nativeh.h);
+ }
+ return ret;
+}
+
+result<file_handle> file_handle::temp_inode(const path_handle &dirh, mode _mode, flag flags) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ caching _caching = caching::temporary;
+ // No need to rename to random on unlink or check inode before unlink
+ flags |= flag::disable_safety_unlinks | flag::win_disable_unlink_emulation;
+ result<file_handle> ret(file_handle(native_handle_type(), 0, 0, _caching, flags));
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::file;
+ DWORD fileshare = /* no read nor write access for others */ FILE_SHARE_DELETE;
+ OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, _mode, flags));
+ OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(nativeh, _caching, flags));
+ DWORD creatdisp = 0x00000002 /*FILE_CREATE*/;
+
+ attribs &= 0x00ffffff; // the real attributes only, not the win32 flags
+ OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, _caching, flags));
+ ntflags |= 0x040 /*FILE_NON_DIRECTORY_FILE*/; // do not open a directory
+ UNICODE_STRING _path{};
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(68 * sizeof(wchar_t))) + sizeof(wchar_t);
+
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.RootDirectory = dirh.native_handle().h;
+
+ LARGE_INTEGER AllocationSize{};
+ memset(&AllocationSize, 0, sizeof(AllocationSize));
+ std::string _random;
+ std::wstring random(68, 0);
+ for(;;)
+ {
+ try
+ {
+ _random = utils::random_string(32) + ".tmp";
+ for(size_t n = 0; n < _random.size(); n++)
+ {
+ random[n] = _random[n];
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ _path.Buffer = const_cast<wchar_t *>(random.c_str());
+ {
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtCreateFile(&nativeh.h, access, &oa, &isb, &AllocationSize, attribs, fileshare, creatdisp, ntflags, nullptr, 0);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0 && ntstat != static_cast<NTSTATUS>(0xC0000035L) /*STATUS_OBJECT_NAME_COLLISION*/)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ // std::cerr << random << std::endl;
+ if(nativeh.h != nullptr)
+ {
+ HANDLE duph;
+ {
+ // It is entirely undocumented that this is how you clone a file handle with new privs
+ memset(&_path, 0, sizeof(_path));
+ oa.ObjectName = &_path;
+ oa.RootDirectory = nativeh.h;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtOpenFile(&duph, SYNCHRONIZE | DELETE, &oa, &isb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ auto unduph = undoer([&duph] { CloseHandle(duph); });
+ (void) unduph;
+ bool failed = true;
+ // Immediately delete, try by POSIX delete first
+ {
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_DISPOSITION_INFORMATION_EX fdie{};
+ memset(&fdie, 0, sizeof(fdie));
+ fdie.Flags = 0x1 /*FILE_DISPOSITION_DELETE*/ | 0x2 /*FILE_DISPOSITION_POSIX_SEMANTICS*/;
+ NTSTATUS ntstat = NtSetInformationFile(duph, &isb, &fdie, sizeof(fdie), FileDispositionInformationEx);
+ if(ntstat >= 0)
+ {
+ failed = false;
+ }
+ }
+ if(failed)
+ {
+ // Hide this item
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_BASIC_INFORMATION fbi{};
+ memset(&fbi, 0, sizeof(fbi));
+ fbi.FileAttributes = FILE_ATTRIBUTE_HIDDEN;
+ NtSetInformationFile(nativeh.h, &isb, &fbi, sizeof(fbi), FileBasicInformation);
+
+ // Mark the item as delete on close
+ isb = make_iostatus();
+ FILE_DISPOSITION_INFORMATION fdi{};
+ memset(&fdi, 0, sizeof(fdi));
+ fdi._DeleteFile = 1u;
+ NTSTATUS ntstat = NtSetInformationFile(duph, &isb, &fdi, sizeof(fdi), FileDispositionInformation);
+ if(ntstat >= 0)
+ {
+ // No need to delete it again on close
+ ret.value()._flags &= ~flag::unlink_on_first_close;
+ }
+ }
+ }
+ return ret;
+ }
+}
+
+file_handle::io_result<file_handle::const_buffers_type> file_handle::barrier(file_handle::io_request<file_handle::const_buffers_type> reqs, bool wait_for_device, bool and_metadata, deadline d) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(d && !_v.is_overlapped())
+ {
+ return errc::not_supported;
+ }
+ AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
+ OVERLAPPED ol{};
+ memset(&ol, 0, sizeof(ol));
+ auto *isb = reinterpret_cast<IO_STATUS_BLOCK *>(&ol);
+ *isb = make_iostatus();
+ ULONG flags = 0;
+ if(!wait_for_device && !and_metadata)
+ {
+ flags = 1 /*FLUSH_FLAGS_FILE_DATA_ONLY*/;
+ }
+ else if(!wait_for_device)
+ {
+ flags = 2 /*FLUSH_FLAGS_NO_SYNC*/;
+ }
+ NTSTATUS ntstat = NtFlushBuffersFileEx(_v.h, flags, nullptr, 0, isb);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(_v.h, ol, d);
+ if(STATUS_TIMEOUT == ntstat)
+ {
+ CancelIoEx(_v.h, &ol);
+ AFIO_WIN_DEADLINE_TO_TIMEOUT(d);
+ }
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return {reqs.buffers};
+}
+
+result<file_handle> file_handle::clone(mode mode_, caching caching_, deadline /*unused*/) const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // Fast path
+ if(mode_ == mode::unchanged && caching_ == caching::unchanged)
+ {
+ result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, caching_, _flags));
+ ret.value()._service = _service;
+ ret.value()._v.behaviour = _v.behaviour;
+ if(DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, 0, DUPLICATE_SAME_ACCESS) == 0)
+ {
+ return win32_error();
+ }
+ return ret;
+ }
+ // Slow path
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ result<file_handle> ret(file_handle(native_handle_type(), _devid, _inode, caching_, _flags));
+ native_handle_type &nativeh = ret.value()._v;
+ nativeh.behaviour |= native_handle_type::disposition::file;
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, mode_, _flags));
+ OUTCOME_TRYV(attributes_from_handle_caching_and_flags(nativeh, caching_, _flags));
+ OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, caching_, _flags));
+ ntflags |= 0x040 /*FILE_NON_DIRECTORY_FILE*/; // do not open a directory
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ // It is entirely undocumented that this is how you clone a file handle with new privs
+ UNICODE_STRING _path{};
+ memset(&_path, 0, sizeof(_path));
+ oa.ObjectName = &_path;
+ oa.RootDirectory = _v.h;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtOpenFile(&nativeh.h, access, &oa, &isb, fileshare, ntflags);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return ret;
+}
+
+result<file_handle::extent_type> file_handle::maximum_extent() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+#if 0
+ char buffer[1];
+ DWORD read = 0;
+ OVERLAPPED ol;
+ memset(&ol, 0, sizeof(ol));
+ ol.OffsetHigh = ol.Offset = 0xffffffff;
+ WriteFile(_v.h, buffer, 0, &read, &ol);
+#endif
+ FILE_STANDARD_INFO fsi{};
+ if(GetFileInformationByHandleEx(_v.h, FileStandardInfo, &fsi, sizeof(fsi)) == 0)
+ {
+ return win32_error();
+ }
+ return fsi.EndOfFile.QuadPart;
+}
+
+result<file_handle::extent_type> file_handle::truncate(file_handle::extent_type newsize) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ FILE_END_OF_FILE_INFO feofi{};
+ feofi.EndOfFile.QuadPart = newsize;
+ if(SetFileInformationByHandle(_v.h, FileEndOfFileInfo, &feofi, sizeof(feofi)) == 0)
+ {
+ return win32_error();
+ }
+ if(are_safety_fsyncs_issued())
+ {
+ FlushFileBuffers(_v.h);
+ }
+ return newsize;
+}
+
+result<std::vector<std::pair<file_handle::extent_type, file_handle::extent_type>>> file_handle::extents() const noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ try
+ {
+ static_assert(sizeof(std::pair<file_handle::extent_type, file_handle::extent_type>) == sizeof(FILE_ALLOCATED_RANGE_BUFFER), "FILE_ALLOCATED_RANGE_BUFFER is not equivalent to pair<extent_type, extent_type>!");
+ std::vector<std::pair<file_handle::extent_type, file_handle::extent_type>> ret;
+#ifdef NDEBUG
+ ret.resize(64);
+#else
+ ret.resize(1);
+#endif
+ FILE_ALLOCATED_RANGE_BUFFER farb{};
+ farb.FileOffset.QuadPart = 0;
+ farb.Length.QuadPart = (static_cast<extent_type>(1) << 63) - 1; // Microsoft claims this is 1<<64-1024 for NTFS, but I get bad parameter error with anything higher than 1<<63-1.
+ DWORD bytesout = 0;
+ OVERLAPPED ol{};
+ memset(&ol, 0, sizeof(ol));
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ while(DeviceIoControl(_v.h, FSCTL_QUERY_ALLOCATED_RANGES, &farb, sizeof(farb), ret.data(), static_cast<DWORD>(ret.size() * sizeof(FILE_ALLOCATED_RANGE_BUFFER)), &bytesout, &ol) == 0)
+ {
+ if(ERROR_INSUFFICIENT_BUFFER == GetLastError() || ERROR_MORE_DATA == GetLastError())
+ {
+ ret.resize(ret.size() * 2);
+ continue;
+ }
+ if(ERROR_SUCCESS != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ return ret;
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+result<file_handle::extent_type> file_handle::zero(file_handle::extent_type offset, file_handle::extent_type bytes, deadline /*unused*/) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(offset + bytes < offset)
+ {
+ return errc::value_too_large;
+ }
+ FILE_ZERO_DATA_INFORMATION fzdi{};
+ fzdi.FileOffset.QuadPart = offset;
+ fzdi.BeyondFinalZero.QuadPart = offset + bytes;
+ DWORD bytesout = 0;
+ OVERLAPPED ol{};
+ memset(&ol, 0, sizeof(ol));
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ if(DeviceIoControl(_v.h, FSCTL_SET_ZERO_DATA, &fzdi, sizeof(fzdi), nullptr, 0, &bytesout, &ol) == 0)
+ {
+ if(ERROR_IO_PENDING == GetLastError())
+ {
+ NTSTATUS ntstat = ntwait(_v.h, ol, deadline());
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(ERROR_SUCCESS != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ return success();
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp
new file mode 100644
index 00000000..c3b47873
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp
@@ -0,0 +1,290 @@
+/* A filing system handle
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../fs_handle.hpp"
+#include "../../../stat.hpp"
+#include "../../../utils.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<void> fs_handle::_fetch_inode() const noexcept
+{
+ stat_t s;
+ OUTCOME_TRYV(s.fill(_get_handle(), stat_t::want::dev | stat_t::want::ino));
+ _devid = s.st_dev;
+ _inode = s.st_ino;
+ return success();
+}
+
+result<path_handle> fs_handle::parent_path_handle(deadline d) const noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ auto &h = _get_handle();
+ AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
+ if(_devid == 0 && _inode == 0)
+ {
+ OUTCOME_TRY(_fetch_inode());
+ }
+ try
+ {
+ for(;;)
+ {
+ // Get current path for handle and open its containing dir
+ OUTCOME_TRY(currentpath, h.current_path());
+ // If current path is empty, it's been deleted
+ if(currentpath.empty())
+ {
+ return errc::no_such_file_or_directory;
+ }
+ // Split the path into root and leafname
+ filesystem::path filename = currentpath.filename();
+ currentpath.remove_filename();
+ /* We have to be super careful here because \Device\HarddiskVolume4 != \Device\HarddiskVolume4\!
+ The former opens the device, the latter the root directory of the device.
+ */
+ const_cast<filesystem::path::string_type &>(currentpath.native()).push_back('\\');
+ auto currentdirh_ = path_handle::path(currentpath);
+ if(!currentdirh_)
+ {
+ continue;
+ }
+ path_handle currentdirh = std::move(currentdirh_.value());
+ if(h.flags() & handle::flag::disable_safety_unlinks)
+ {
+ return success(std::move(currentdirh));
+ }
+
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ path_view::c_str zpath(filename, true);
+ UNICODE_STRING _path{};
+ _path.Buffer = const_cast<wchar_t *>(zpath.buffer);
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t);
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.RootDirectory = currentdirh.native_handle().h;
+ LARGE_INTEGER AllocationSize{};
+ memset(&AllocationSize, 0, sizeof(AllocationSize));
+ HANDLE nh = nullptr;
+ NTSTATUS ntstat = NtCreateFile(&nh, SYNCHRONIZE, &oa, &isb, &AllocationSize, 0, fileshare, 0x00000001 /*FILE_OPEN*/, 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/, nullptr, 0);
+ if(STATUS_SUCCESS != ntstat)
+ {
+ if(static_cast<NTSTATUS>(0xC000000F) /*STATUS_NO_SUCH_FILE*/ == ntstat || static_cast<NTSTATUS>(0xC0000034) /*STATUS_OBJECT_NAME_NOT_FOUND*/ == ntstat)
+ {
+ continue;
+ }
+ return ntkernel_error(ntstat);
+ }
+ auto unnh = undoer([nh] { CloseHandle(nh); });
+ (void) unnh;
+ isb.Status = -1;
+ FILE_INTERNAL_INFORMATION fii{};
+ ntstat = NtQueryInformationFile(nh, &isb, &fii, sizeof(fii), FileInternalInformation);
+ if(STATUS_SUCCESS != ntstat)
+ {
+ continue;
+ }
+ // If the same, we know for a fact that this is the correct containing dir for now at least
+ // FIXME: We are not comparing device number, that's faked as the volume number in stat_t
+ if(static_cast<ino_t>(fii.IndexNumber.QuadPart) == _inode)
+ {
+ return success(std::move(currentdirh));
+ }
+ AFIO_WIN_DEADLINE_TO_TIMEOUT(d);
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+result<void> fs_handle::relink(const path_handle &base, path_view_type path, bool atomic_replace, deadline d) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ auto &h = _get_handle();
+
+ // If the target is a win32 path, we need to convert to NT path and call ourselves
+ if(!base.is_valid() && !path.is_ntpath())
+ {
+ path_view::c_str zpath(path, false);
+ UNICODE_STRING NtPath{};
+ if(RtlDosPathNameToNtPathName_U(zpath.buffer, &NtPath, nullptr, nullptr) == 0u)
+ {
+ return win32_error(ERROR_FILE_NOT_FOUND);
+ }
+ auto unntpath = undoer([&NtPath] {
+ if(HeapFree(GetProcessHeap(), 0, NtPath.Buffer) == 0)
+ {
+ abort();
+ }
+ });
+ // RtlDosPathNameToNtPathName_U outputs \??\path, so path.is_ntpath() will be false.
+ return relink(base, wstring_view(NtPath.Buffer, NtPath.Length / sizeof(wchar_t)));
+ }
+
+ path_view::c_str zpath(path, true);
+ UNICODE_STRING _path{};
+ _path.Buffer = const_cast<wchar_t *>(zpath.buffer);
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t);
+ if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\')
+ {
+ _path.Buffer += 3;
+ _path.Length -= 3 * sizeof(wchar_t);
+ _path.MaximumLength -= 3 * sizeof(wchar_t);
+ }
+ IO_STATUS_BLOCK isb = make_iostatus();
+ alignas(8) char buffer[sizeof(FILE_RENAME_INFORMATION) + 65536];
+ auto *fni = reinterpret_cast<FILE_RENAME_INFORMATION *>(buffer);
+ fni->Flags = atomic_replace ? 0x1 /*FILE_RENAME_REPLACE_IF_EXISTS*/ : 0;
+ fni->Flags |= 0x2 /*FILE_RENAME_POSIX_SEMANTICS*/;
+ fni->RootDirectory = base.is_valid() ? base.native_handle().h : nullptr;
+ fni->FileNameLength = _path.Length;
+ memcpy(fni->FileName, _path.Buffer, fni->FileNameLength);
+ NTSTATUS ntstat = NtSetInformationFile(h.native_handle().h, &isb, fni, sizeof(FILE_RENAME_INFORMATION) + fni->FileNameLength, FileRenameInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, d);
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return success();
+}
+
+result<void> fs_handle::unlink(deadline d) noexcept
+{
+ using flag = handle::flag;
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ auto &h = _get_handle();
+ HANDLE duph;
+ // Try by POSIX delete first
+ {
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ // It is entirely undocumented that this is how you clone a file handle with new privs
+ UNICODE_STRING _path{};
+ memset(&_path, 0, sizeof(_path));
+ oa.ObjectName = &_path;
+ oa.RootDirectory = h.native_handle().h;
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat = NtOpenFile(&duph, SYNCHRONIZE | DELETE, &oa, &isb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ auto unduph = undoer([&duph] { CloseHandle(duph); });
+ (void) unduph;
+ bool failed = true;
+ // Try POSIX delete first, this will fail on Windows 10 before 1709, or if not NTFS
+ {
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_DISPOSITION_INFORMATION_EX fdie{};
+ memset(&fdie, 0, sizeof(fdie));
+ fdie.Flags = 0x1 /*FILE_DISPOSITION_DELETE*/ | 0x2 /*FILE_DISPOSITION_POSIX_SEMANTICS*/;
+ NTSTATUS ntstat = NtSetInformationFile(duph, &isb, &fdie, sizeof(fdie), FileDispositionInformationEx);
+ if(ntstat >= 0)
+ {
+ failed = false;
+ }
+ }
+ if(failed)
+ {
+ if((h.is_regular() || h.is_symlink()) && !(h.flags() & flag::win_disable_unlink_emulation))
+ {
+ // Rename it to something random to emulate immediate unlinking
+ std::string randomname;
+ try
+ {
+ randomname = utils::random_string(32);
+ randomname.append(".deleted");
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ OUTCOME_TRY(dirh, parent_path_handle(d));
+ result<void> out = relink(dirh, randomname);
+ if(!out)
+ {
+ // If something else is using it, we may not be able to rename
+ // This error also annoyingly appears if the file has delete on close set on it already
+ if(out.error().value() == static_cast<int>(0xC0000043) /*STATUS_SHARING_VIOLATION*/)
+ {
+ AFIO_LOG_WARN(this, "Failed to rename entry to random name to simulate immediate unlinking due to STATUS_SHARING_VIOLATION, skipping");
+ }
+ else
+ {
+ return out.error();
+ }
+ }
+ }
+ // No point marking it for deletion if it's already been so
+ if(!(h.flags() & flag::unlink_on_first_close))
+ {
+ // Hide the item in Explorer and the command line
+ {
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_BASIC_INFORMATION fbi{};
+ memset(&fbi, 0, sizeof(fbi));
+ fbi.FileAttributes = FILE_ATTRIBUTE_HIDDEN;
+ NTSTATUS ntstat = NtSetInformationFile(h.native_handle().h, &isb, &fbi, sizeof(fbi), FileBasicInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, d);
+ }
+ (void) ntstat;
+ }
+ // Mark the item as delete on close
+ IO_STATUS_BLOCK isb = make_iostatus();
+ FILE_DISPOSITION_INFORMATION fdi{};
+ memset(&fdi, 0, sizeof(fdi));
+ fdi._DeleteFile = 1u;
+ NTSTATUS ntstat = NtSetInformationFile(duph, &isb, &fdi, sizeof(fdi), FileDispositionInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(duph, isb, d);
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ }
+ return success();
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/handle.ipp b/include/llfio/v2.0/detail/impl/windows/handle.ipp
new file mode 100644
index 00000000..72eda554
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/handle.ipp
@@ -0,0 +1,123 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+handle::~handle()
+{
+ if(_v)
+ {
+ // Call close() below
+ auto ret = handle::close();
+ if(ret.has_error())
+ {
+ AFIO_LOG_FATAL(_v.h, "handle::~handle() close failed");
+ abort();
+ }
+ }
+}
+
+result<handle::path_type> handle::current_path() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ try
+ {
+ // Most efficient, least memory copying method is direct fill of a wstring which is moved into filesystem::path
+ filesystem::path::string_type buffer;
+ buffer.resize(32769);
+ auto *_buffer = const_cast<wchar_t *>(buffer.data());
+ memcpy(_buffer, L"\\!!", 6);
+ DWORD len = GetFinalPathNameByHandle(_v.h, _buffer + 3, (DWORD)(buffer.size() - 4 * sizeof(wchar_t)), VOLUME_NAME_NT); // NOLINT
+ if(len == 0)
+ {
+ return win32_error();
+ }
+ buffer.resize(3 + len);
+ // As of Windows 10 1709, there are such things as actually unlinked files, so detect those
+ if(filesystem::path::string_type::npos != buffer.find(L"\\$Extend\\$Deleted\\"))
+ {
+ return path_type();
+ }
+ return path_type(buffer);
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+}
+
+result<void> handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_v)
+ {
+ if(are_safety_fsyncs_issued() && is_writable())
+ {
+ if(FlushFileBuffers(_v.h) == 0)
+ {
+ return win32_error();
+ }
+ }
+ if(CloseHandle(_v.h) == 0)
+ {
+ return win32_error();
+ }
+ _v = native_handle_type();
+ }
+ return success();
+}
+
+result<handle> handle::clone() const noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ result<handle> ret(handle(native_handle_type(), _caching, _flags));
+ ret.value()._v.behaviour = _v.behaviour;
+ if(DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, 0, DUPLICATE_SAME_ACCESS) == 0)
+ {
+ return win32_error();
+ }
+ return ret;
+}
+
+result<void> handle::set_append_only(bool enable) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // This works only due to special handling in OVERLAPPED later
+ if(enable)
+ {
+ // Set append_only
+ _v.behaviour |= native_handle_type::disposition::append_only;
+ }
+ else
+ {
+ // Remove append_only
+ _v.behaviour &= ~native_handle_type::disposition::append_only;
+ }
+ return success();
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/import.hpp b/include/llfio/v2.0/detail/impl/windows/import.hpp
new file mode 100644
index 00000000..dbdaaa91
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/import.hpp
@@ -0,0 +1,1478 @@
+/* Declarations for Microsoft Windows system APIs
+(C) 2015-2018 Niall Douglas <http://www.nedproductions.biz/> (14 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_WINDOWS_H
+#define AFIO_WINDOWS_H
+
+#include "../../../handle.hpp"
+#include <memory> // for unique_ptr
+#include <mutex>
+
+#ifndef _WIN32
+#error You should not include windows/import.hpp on not Windows platforms
+#endif
+
+#include <sal.h>
+
+// At some future point we will not do this, and instead import symbols manually
+// to avoid the windows.h inclusion
+#if 1
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include <windows.h>
+#include <winternl.h>
+
+#else
+#error todo
+#endif
+
+#if !AFIO_EXPERIMENTAL_STATUS_CODE
+// Bring in the custom NT kernel error code category
+#if AFIO_HEADERS_ONLY
+#define NTKERNEL_ERROR_CATEGORY_INLINE
+#define NTKERNEL_ERROR_CATEGORY_STATIC
+#endif
+#include "../../../../ntkernel-error-category/include/ntkernel_category.hpp"
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+#if AFIO_EXPERIMENTAL_STATUS_CODE
+#else
+//! Helper for constructing an error info from a DWORD
+inline error_info win32_error(DWORD c = GetLastError())
+{
+ return error_info(std::error_code(c, std::system_category()));
+}
+//! Helper for constructing an error info from a NTSTATUS
+inline error_info ntkernel_error(NTSTATUS c)
+{
+ return error_info(std::error_code(c, ntkernel_error_category::ntkernel_category()));
+}
+#endif
+
+namespace windows_nt_kernel
+{
+// Weirdly these appear to be undefined sometimes?
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS) 0x00000000L)
+#endif
+#ifndef STATUS_ALERTED
+#define STATUS_ALERTED ((NTSTATUS) 0x00000101L)
+#endif
+#ifndef STATUS_DELETE_PENDING
+#define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
+#endif
+
+ // From http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/FILE_INFORMATION_CLASS.html
+ typedef enum _FILE_INFORMATION_CLASS { // NOLINT
+ FileDirectoryInformation = 1,
+ FileFullDirectoryInformation,
+ FileBothDirectoryInformation,
+ FileBasicInformation,
+ FileStandardInformation,
+ FileInternalInformation,
+ FileEaInformation,
+ FileAccessInformation,
+ FileNameInformation,
+ FileRenameInformation,
+ FileLinkInformation,
+ FileNamesInformation,
+ FileDispositionInformation,
+ FilePositionInformation,
+ FileFullEaInformation,
+ FileModeInformation,
+ FileAlignmentInformation,
+ FileAllInformation,
+ FileAllocationInformation,
+ FileEndOfFileInformation,
+ FileAlternateNameInformation,
+ FileStreamInformation,
+ FilePipeInformation,
+ FilePipeLocalInformation,
+ FilePipeRemoteInformation,
+ FileMailslotQueryInformation,
+ FileMailslotSetInformation,
+ FileCompressionInformation,
+ FileObjectIdInformation,
+ FileCompletionInformation,
+ FileMoveClusterInformation,
+ FileQuotaInformation,
+ FileReparsePointInformation,
+ FileNetworkOpenInformation,
+ FileAttributeTagInformation,
+ FileTrackingInformation,
+ FileIdBothDirectoryInformation,
+ FileIdFullDirectoryInformation,
+ FileValidDataLengthInformation,
+ FileShortNameInformation,
+ FileIoCompletionNotificationInformation,
+ FileIoStatusBlockRangeInformation,
+ FileIoPriorityHintInformation,
+ FileSfioReserveInformation,
+ FileSfioVolumeInformation,
+ FileHardLinkInformation,
+ FileProcessIdsUsingFileInformation,
+ FileNormalizedNameInformation,
+ FileNetworkPhysicalNameInformation,
+ FileIdGlobalTxDirectoryInformation,
+ FileIsRemoteDeviceInformation,
+ FileAttributeCacheInformation,
+ FileNumaNodeInformation,
+ FileStandardLinkInformation,
+ FileRemoteProtocolInformation,
+ FileMaximumInformation,
+ FileDispositionInformationEx = 64
+ } FILE_INFORMATION_CLASS,
+ *PFILE_INFORMATION_CLASS;
+
+ typedef enum { // NOLINT
+ FileFsVolumeInformation = 1,
+ FileFsLabelInformation = 2,
+ FileFsSizeInformation = 3,
+ FileFsDeviceInformation = 4,
+ FileFsAttributeInformation = 5,
+ FileFsControlInformation = 6,
+ FileFsFullSizeInformation = 7,
+ FileFsObjectIdInformation = 8,
+ FileFsDriverPathInformation = 9,
+ FileFsVolumeFlagsInformation = 10,
+ FileFsSectorSizeInformation = 11
+ } FS_INFORMATION_CLASS;
+
+ typedef enum { ObjectBasicInformation = 0, ObjectNameInformation = 1, ObjectTypeInformation = 2 } OBJECT_INFORMATION_CLASS; // NOLINT
+
+#ifndef NTSTATUS
+#define NTSTATUS LONG
+#endif
+#ifndef STATUS_TIMEOUT
+#define STATUS_TIMEOUT ((NTSTATUS) 0x00000102)
+#endif
+#ifndef STATUS_PENDING
+#define STATUS_PENDING ((NTSTATUS) 0x00000103)
+#endif
+#ifndef STATUS_BUFFER_OVERFLOW
+#define STATUS_BUFFER_OVERFLOW ((NTSTATUS) 0x80000005)
+#endif
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff550671(v=vs.85).aspx
+ typedef struct _IO_STATUS_BLOCK // NOLINT
+ {
+ union {
+ NTSTATUS Status;
+ PVOID Pointer;
+ };
+ ULONG_PTR Information;
+ } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+ // From http://msdn.microsoft.com/en-us/library/windows/desktop/aa380518(v=vs.85).aspx
+ typedef struct _LSA_UNICODE_STRING // NOLINT
+ {
+ USHORT Length;
+ USHORT MaximumLength;
+ PWSTR Buffer;
+ } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff557749(v=vs.85).aspx
+ typedef struct _OBJECT_ATTRIBUTES // NOLINT
+ {
+ ULONG Length;
+ HANDLE RootDirectory;
+ PUNICODE_STRING ObjectName;
+ ULONG Attributes;
+ PVOID SecurityDescriptor;
+ PVOID SecurityQualityOfService;
+ } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
+
+ using PIO_APC_ROUTINE = void(NTAPI *)(IN PVOID ApcContext, IN PIO_STATUS_BLOCK IoStatusBlock, IN ULONG Reserved);
+
+ typedef struct _IMAGEHLP_LINE64 // NOLINT
+ {
+ DWORD SizeOfStruct;
+ PVOID Key;
+ DWORD LineNumber;
+ PTSTR FileName;
+ DWORD64 Address;
+ } IMAGEHLP_LINE64, *PIMAGEHLP_LINE64;
+
+ typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 } SECTION_INHERIT, *PSECTION_INHERIT; // NOLINT
+
+ typedef struct _WIN32_MEMORY_RANGE_ENTRY // NOLINT
+ {
+ PVOID VirtualAddress;
+ SIZE_T NumberOfBytes;
+ } WIN32_MEMORY_RANGE_ENTRY, *PWIN32_MEMORY_RANGE_ENTRY;
+
+ typedef enum _SECTION_INFORMATION_CLASS { SectionBasicInformation, SectionImageInformation } SECTION_INFORMATION_CLASS, *PSECTION_INFORMATION_CLASS; // NOLINT
+
+ typedef struct _SECTION_BASIC_INFORMATION // NOLINT
+ {
+ PVOID BaseAddress;
+ ULONG AllocationAttributes;
+ LARGE_INTEGER MaximumSize;
+ } SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION;
+
+ // From https://msdn.microsoft.com/en-us/library/bb432383%28v=vs.85%29.aspx
+ using NtQueryObject_t = NTSTATUS(NTAPI *)(_In_opt_ HANDLE Handle, _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, _Out_opt_ PVOID ObjectInformation, _In_ ULONG ObjectInformationLength, _Out_opt_ PULONG ReturnLength);
+
+ // From http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/NtQueryInformationFile.html
+ // and http://msdn.microsoft.com/en-us/library/windows/hardware/ff567052(v=vs.85).aspx
+ using NtQueryInformationFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID FileInformation, _In_ ULONG Length, _In_ FILE_INFORMATION_CLASS FileInformationClass);
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff567070(v=vs.85).aspx
+ using NtQueryVolumeInformationFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID FsInformation, _In_ ULONG Length, _In_ FS_INFORMATION_CLASS FsInformationClass);
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff566492(v=vs.85).aspx
+ using NtOpenDirectoryObject_t = NTSTATUS(NTAPI *)(_Out_ PHANDLE DirectoryHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes);
+
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff567011(v=vs.85).aspx
+ using NtOpenFile_t = NTSTATUS(NTAPI *)(_Out_ PHANDLE FileHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ ULONG ShareAccess, _In_ ULONG OpenOptions);
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff566424(v=vs.85).aspx
+ using NtCreateFile_t = NTSTATUS(NTAPI *)(_Out_ PHANDLE FileHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_opt_ PLARGE_INTEGER AllocationSize, _In_ ULONG FileAttributes, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition,
+ _In_ ULONG CreateOptions, _In_opt_ PVOID EaBuffer, _In_ ULONG EaLength);
+
+ using NtDeleteFile_t = NTSTATUS(NTAPI *)(_In_ POBJECT_ATTRIBUTES ObjectAttributes);
+
+ using NtClose_t = NTSTATUS(NTAPI *)(_Out_ HANDLE FileHandle);
+
+ // From http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/NtQueryDirectoryFile.html
+ // and http://msdn.microsoft.com/en-us/library/windows/hardware/ff567047(v=vs.85).aspx
+ using NtQueryDirectoryFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID FileInformation, _In_ ULONG Length, _In_ FILE_INFORMATION_CLASS FileInformationClass,
+ _In_ BOOLEAN ReturnSingleEntry, _In_opt_ PUNICODE_STRING FileName, _In_ BOOLEAN RestartScan);
+
+ // From http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/NtSetInformationFile.html
+ // and http://msdn.microsoft.com/en-us/library/windows/hardware/ff567096(v=vs.85).aspx
+ using NtSetInformationFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ PVOID FileInformation, _In_ ULONG Length, _In_ FILE_INFORMATION_CLASS FileInformationClass);
+
+ // From http://msdn.microsoft.com/en-us/library/ms648412(v=vs.85).aspx
+ using NtWaitForSingleObject_t = NTSTATUS(NTAPI *)(_In_ HANDLE Handle, _In_ BOOLEAN Alertable, _In_opt_ PLARGE_INTEGER Timeout);
+
+ typedef enum _OBJECT_WAIT_TYPE { WaitAllObject, WaitAnyObject } OBJECT_WAIT_TYPE, *POBJECT_WAIT_TYPE; // NOLINT
+
+ using NtWaitForMultipleObjects_t = NTSTATUS(NTAPI *)(_In_ ULONG Count, _In_ HANDLE Object[], _In_ OBJECT_WAIT_TYPE WaitType, _In_ BOOLEAN Alertable, _In_opt_ PLARGE_INTEGER Time);
+
+ using NtDelayExecution_t = NTSTATUS(NTAPI *)(_In_ BOOLEAN Alertable, _In_opt_ LARGE_INTEGER *Interval);
+
+ // From https://msdn.microsoft.com/en-us/library/windows/hardware/ff566474(v=vs.85).aspx
+ using NtLockFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ PLARGE_INTEGER ByteOffset, _In_ PLARGE_INTEGER Length, _In_ ULONG Key, _In_ BOOLEAN FailImmediately,
+ _In_ BOOLEAN ExclusiveLock);
+
+ // From https://msdn.microsoft.com/en-us/library/windows/hardware/ff567118(v=vs.85).aspx
+ using NtUnlockFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ PLARGE_INTEGER ByteOffset, _In_ PLARGE_INTEGER Length, _In_ ULONG Key);
+
+ using NtCreateSection_t = NTSTATUS(NTAPI *)(_Out_ PHANDLE SectionHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PLARGE_INTEGER MaximumSize, _In_ ULONG SectionPageProtection, _In_ ULONG AllocationAttributes, _In_opt_ HANDLE FileHandle);
+
+ using NtQuerySection_t = NTSTATUS(NTAPI *)(_In_ HANDLE SectionHandle, _In_ SECTION_INFORMATION_CLASS InformationClass, _Out_ PVOID InformationBuffer, _In_ ULONG InformationBufferSize, _Out_opt_ PULONG ResultLength);
+
+ using NtExtendSection_t = NTSTATUS(NTAPI *)(_In_ HANDLE SectionHandle, _In_opt_ PLARGE_INTEGER MaximumSize);
+
+ using NtMapViewOfSection_t = NTSTATUS(NTAPI *)(_In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, _In_ SECTION_INHERIT InheritDisposition, _In_ ULONG AllocationType,
+ _In_ ULONG Win32Protect);
+
+ using NtUnmapViewOfSection_t = NTSTATUS(NTAPI *)(_In_ HANDLE ProcessHandle, _In_opt_ PVOID BaseAddress);
+
+ using NtFlushBuffersFileEx_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_ ULONG Flags, _In_reads_bytes_(ParametersSize) PVOID Parameters, _In_ ULONG ParametersSize, _Out_ PIO_STATUS_BLOCK IoStatusBlock);
+
+ using NtSetSystemInformation_t = NTSTATUS(NTAPI *)(_In_ INT SystemInformationClass, _In_ PVOID SystemInformation, _In_ ULONG SystemInformationLength);
+
+ using NtAllocateVirtualMemory_t = NTSTATUS(NTAPI *)(_In_ HANDLE ProcessHandle, _Inout_ PVOID *BaseAddress, _In_ ULONG_PTR ZeroBits, _Inout_ PSIZE_T RegionSize, _In_ ULONG AllocationType, _In_ ULONG Protect);
+
+ using NtFreeVirtualMemory_t = NTSTATUS(NTAPI *)(_In_ HANDLE ProcessHandle, _Inout_ PVOID *BaseAddress, _Inout_ PSIZE_T RegionSize, _In_ ULONG FreeType);
+
+
+ using RtlGenRandom_t = BOOLEAN(NTAPI *)(_Out_ PVOID RandomBuffer, _In_ ULONG RandomBufferLength);
+
+ using OpenProcessToken_t = BOOL(NTAPI *)(_In_ HANDLE ProcessHandle, _In_ DWORD DesiredAccess, _Out_ PHANDLE TokenHandle);
+
+ using LookupPrivilegeValue_t = BOOL(NTAPI *)(_In_opt_ LPCTSTR lpSystemName, _In_ LPCTSTR lpName, _Out_ PLUID lpLuid);
+
+ using AdjustTokenPrivileges_t = BOOL(NTAPI *)(_In_ HANDLE TokenHandle, _In_ BOOL DisableAllPrivileges, _In_opt_ PTOKEN_PRIVILEGES NewState, _In_ DWORD BufferLength, _Out_opt_ PTOKEN_PRIVILEGES PreviousState, _Out_opt_ PDWORD ReturnLength);
+
+ using PrefetchVirtualMemory_t = BOOL(NTAPI *)(_In_ HANDLE hProcess, _In_ ULONG_PTR NumberOfEntries, _In_ PWIN32_MEMORY_RANGE_ENTRY VirtualAddresses, _In_ ULONG Flags);
+
+ using DiscardVirtualMemory_t = BOOL(NTAPI *)(_In_ PVOID VirtualAddress, _In_ SIZE_T Size);
+
+ using RtlCaptureStackBackTrace_t = USHORT(NTAPI *)(_In_ ULONG FramesToSkip, _In_ ULONG FramesToCapture, _Out_ PVOID *BackTrace, _Out_opt_ PULONG BackTraceHash);
+
+ using SymInitialize_t = BOOL(NTAPI *)(_In_ HANDLE hProcess, _In_opt_ PCTSTR UserSearchPath, _In_ BOOL fInvadeProcess);
+
+ using SymGetLineFromAddr64_t = BOOL(NTAPI *)(_In_ HANDLE hProcess, _In_ DWORD64 dwAddr, _Out_ PDWORD pdwDisplacement, _Out_ PIMAGEHLP_LINE64 Line);
+
+ using RtlDosPathNameToNtPathName_U_t = BOOLEAN(NTAPI *)(__in PCWSTR DosFileName, __out PUNICODE_STRING NtFileName, __out_opt PWSTR *FilePart, __out_opt PVOID RelativeName);
+
+ using RtlUTF8ToUnicodeN_t = NTSTATUS(NTAPI *)(_Out_opt_ PWSTR UnicodeStringDestination, _In_ ULONG UnicodeStringMaxByteCount, _Out_ PULONG UnicodeStringActualByteCount, _In_ PCCH UTF8StringSource, _In_ ULONG UTF8StringByteCount);
+
+ typedef struct _FILE_BASIC_INFORMATION // NOLINT
+ {
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER LastAccessTime;
+ LARGE_INTEGER LastWriteTime;
+ LARGE_INTEGER ChangeTime;
+ ULONG FileAttributes;
+ } FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
+
+ typedef struct _FILE_STANDARD_INFORMATION // NOLINT
+ {
+ LARGE_INTEGER AllocationSize;
+ LARGE_INTEGER EndOfFile;
+ ULONG NumberOfLinks;
+ BOOLEAN DeletePending;
+ BOOLEAN Directory;
+ } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
+
+ typedef struct _FILE_INTERNAL_INFORMATION // NOLINT
+ {
+ LARGE_INTEGER IndexNumber;
+ } FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION;
+
+ typedef struct _FILE_EA_INFORMATION // NOLINT
+ {
+ union {
+ ULONG EaSize;
+ ULONG ReparsePointTag;
+ };
+ } FILE_EA_INFORMATION, *PFILE_EA_INFORMATION;
+
+ typedef struct _FILE_ACCESS_INFORMATION // NOLINT
+ {
+ ACCESS_MASK AccessFlags;
+ } FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION;
+
+ typedef struct _FILE_POSITION_INFORMATION // NOLINT
+ {
+ LARGE_INTEGER CurrentByteOffset;
+ } FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION;
+
+ typedef struct _FILE_MODE_INFORMATION // NOLINT
+ {
+ ULONG Mode;
+ } FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION;
+
+ typedef struct _FILE_ALIGNMENT_INFORMATION // NOLINT
+ {
+ ULONG AlignmentRequirement;
+ } FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION;
+
+ typedef struct _FILE_NAME_INFORMATION // NOLINT
+ {
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+ } FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;
+
+ typedef struct _FILE_RENAME_INFORMATION // NOLINT
+ {
+ ULONG Flags;
+ HANDLE RootDirectory;
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+ } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
+
+ typedef struct _FILE_LINK_INFORMATION // NOLINT
+ {
+ BOOLEAN ReplaceIfExists;
+ HANDLE RootDirectory;
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+ } FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION;
+
+ typedef struct _FILE_DISPOSITION_INFORMATION // NOLINT
+ {
+ BOOLEAN _DeleteFile;
+ } FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION;
+
+ typedef struct _FILE_DISPOSITION_INFORMATION_EX // NOLINT
+ {
+ ULONG Flags;
+ } FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;
+
+ typedef struct _FILE_ALL_INFORMATION // NOLINT
+ {
+ FILE_BASIC_INFORMATION BasicInformation;
+ FILE_STANDARD_INFORMATION StandardInformation;
+ FILE_INTERNAL_INFORMATION InternalInformation;
+ FILE_EA_INFORMATION EaInformation;
+ FILE_ACCESS_INFORMATION AccessInformation;
+ FILE_POSITION_INFORMATION PositionInformation;
+ FILE_MODE_INFORMATION ModeInformation;
+ FILE_ALIGNMENT_INFORMATION AlignmentInformation;
+ FILE_NAME_INFORMATION NameInformation;
+ } FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION;
+
+ typedef struct _FILE_FS_ATTRIBUTE_INFORMATION // NOLINT
+ {
+ ULONG FileSystemAttributes;
+ LONG MaximumComponentNameLength;
+ ULONG FileSystemNameLength;
+ WCHAR FileSystemName[1];
+ } FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION;
+
+ typedef struct _FILE_FS_FULL_SIZE_INFORMATION // NOLINT
+ {
+ LARGE_INTEGER TotalAllocationUnits;
+ LARGE_INTEGER CallerAvailableAllocationUnits;
+ LARGE_INTEGER ActualAvailableAllocationUnits;
+ ULONG SectorsPerAllocationUnit;
+ ULONG BytesPerSector;
+ } FILE_FS_FULL_SIZE_INFORMATION, *PFILE_FS_FULL_SIZE_INFORMATION;
+
+ typedef struct _FILE_FS_OBJECTID_INFORMATION // NOLINT
+ {
+ UCHAR ObjectId[16];
+ UCHAR ExtendedInfo[48];
+ } FILE_FS_OBJECTID_INFORMATION, *PFILE_FS_OBJECTID_INFORMATION;
+
+ typedef struct _FILE_FS_SECTOR_SIZE_INFORMATION // NOLINT
+ {
+ ULONG LogicalBytesPerSector;
+ ULONG PhysicalBytesPerSectorForAtomicity;
+ ULONG PhysicalBytesPerSectorForPerformance;
+ ULONG FileSystemEffectivePhysicalBytesPerSectorForAtomicity;
+ ULONG Flags;
+ ULONG ByteOffsetForSectorAlignment;
+ ULONG ByteOffsetForPartitionAlignment;
+ } FILE_FS_SECTOR_SIZE_INFORMATION, *PFILE_FS_SECTOR_SIZE_INFORMATION;
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff540310(v=vs.85).aspx
+ typedef struct _FILE_ID_FULL_DIR_INFORMATION // NOLINT
+ {
+ ULONG NextEntryOffset;
+ ULONG FileIndex;
+ LARGE_INTEGER CreationTime;
+ LARGE_INTEGER LastAccessTime;
+ LARGE_INTEGER LastWriteTime;
+ LARGE_INTEGER ChangeTime;
+ LARGE_INTEGER EndOfFile;
+ LARGE_INTEGER AllocationSize;
+ ULONG FileAttributes;
+ ULONG FileNameLength;
+ union {
+ ULONG EaSize;
+ ULONG ReparsePointTag;
+ };
+ LARGE_INTEGER FileId;
+ WCHAR FileName[1];
+ } FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION;
+
+ // From https://msdn.microsoft.com/en-us/library/windows/hardware/ff540354(v=vs.85).aspx
+ typedef struct _FILE_REPARSE_POINT_INFORMATION // NOLINT
+ {
+ LARGE_INTEGER FileReference;
+ ULONG Tag;
+ } FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION;
+
+ // From http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
+ typedef struct _REPARSE_DATA_BUFFER // NOLINT
+ {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct
+ {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct
+ {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct
+ {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+ } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+ static NtQueryObject_t NtQueryObject;
+ static NtQueryInformationFile_t NtQueryInformationFile;
+ static NtQueryVolumeInformationFile_t NtQueryVolumeInformationFile;
+ static NtOpenDirectoryObject_t NtOpenDirectoryObject;
+ static NtOpenFile_t NtOpenFile;
+ static NtCreateFile_t NtCreateFile;
+ static NtDeleteFile_t NtDeleteFile;
+ static NtClose_t NtClose;
+ static NtQueryDirectoryFile_t NtQueryDirectoryFile;
+ static NtSetInformationFile_t NtSetInformationFile;
+ static NtWaitForSingleObject_t NtWaitForSingleObject;
+ static NtWaitForMultipleObjects_t NtWaitForMultipleObjects;
+ static NtDelayExecution_t NtDelayExecution;
+ static NtLockFile_t NtLockFile;
+ static NtUnlockFile_t NtUnlockFile;
+ static NtCreateSection_t NtCreateSection;
+ static NtQuerySection_t NtQuerySection;
+ static NtExtendSection_t NtExtendSection;
+ static NtMapViewOfSection_t NtMapViewOfSection;
+ static NtUnmapViewOfSection_t NtUnmapViewOfSection;
+ static NtFlushBuffersFileEx_t NtFlushBuffersFileEx;
+ static NtSetSystemInformation_t NtSetSystemInformation;
+ static NtAllocateVirtualMemory_t NtAllocateVirtualMemory;
+ static NtFreeVirtualMemory_t NtFreeVirtualMemory;
+ static RtlGenRandom_t RtlGenRandom;
+ static OpenProcessToken_t OpenProcessToken;
+ static LookupPrivilegeValue_t LookupPrivilegeValue;
+ static AdjustTokenPrivileges_t AdjustTokenPrivileges;
+ static PrefetchVirtualMemory_t PrefetchVirtualMemory_;
+ static DiscardVirtualMemory_t DiscardVirtualMemory_;
+ static SymInitialize_t SymInitialize;
+ static SymGetLineFromAddr64_t SymGetLineFromAddr64;
+ static RtlCaptureStackBackTrace_t RtlCaptureStackBackTrace;
+ static RtlDosPathNameToNtPathName_U_t RtlDosPathNameToNtPathName_U;
+ static RtlUTF8ToUnicodeN_t RtlUTF8ToUnicodeN;
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4706) // assignment within conditional
+#pragma warning(disable : 6387) // MSVC sanitiser warns that GetModuleHandleA() might fail (hah!)
+#endif
+ inline void doinit()
+ {
+ if(RtlUTF8ToUnicodeN != nullptr)
+ {
+ return;
+ }
+ static std::mutex lock;
+ std::lock_guard<decltype(lock)> g(lock);
+ static HMODULE ntdllh = GetModuleHandleA("NTDLL.DLL");
+ static HMODULE kernel32 = GetModuleHandleA("KERNEL32.DLL");
+ if(NtQueryObject == nullptr)
+ {
+ if((NtQueryObject = reinterpret_cast<NtQueryObject_t>(GetProcAddress(ntdllh, "NtQueryObject"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtQueryInformationFile == nullptr)
+ {
+ if((NtQueryInformationFile = reinterpret_cast<NtQueryInformationFile_t>(GetProcAddress(ntdllh, "NtQueryInformationFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtQueryVolumeInformationFile == nullptr)
+ {
+ if((NtQueryVolumeInformationFile = reinterpret_cast<NtQueryVolumeInformationFile_t>(GetProcAddress(ntdllh, "NtQueryVolumeInformationFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtOpenDirectoryObject == nullptr)
+ {
+ if((NtOpenDirectoryObject = reinterpret_cast<NtOpenDirectoryObject_t>(GetProcAddress(ntdllh, "NtOpenDirectoryObject"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtOpenFile == nullptr)
+ {
+ if((NtOpenFile = reinterpret_cast<NtOpenFile_t>(GetProcAddress(ntdllh, "NtOpenFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtCreateFile == nullptr)
+ {
+ if((NtCreateFile = reinterpret_cast<NtCreateFile_t>(GetProcAddress(ntdllh, "NtCreateFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtDeleteFile == nullptr)
+ {
+ if((NtDeleteFile = reinterpret_cast<NtDeleteFile_t>(GetProcAddress(ntdllh, "NtDeleteFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtClose == nullptr)
+ {
+ if((NtClose = reinterpret_cast<NtClose_t>(GetProcAddress(ntdllh, "NtClose"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtQueryDirectoryFile == nullptr)
+ {
+ if((NtQueryDirectoryFile = reinterpret_cast<NtQueryDirectoryFile_t>(GetProcAddress(ntdllh, "NtQueryDirectoryFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtSetInformationFile == nullptr)
+ {
+ if((NtSetInformationFile = reinterpret_cast<NtSetInformationFile_t>(GetProcAddress(ntdllh, "NtSetInformationFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtWaitForSingleObject == nullptr)
+ {
+ if((NtWaitForSingleObject = reinterpret_cast<NtWaitForSingleObject_t>(GetProcAddress(ntdllh, "NtWaitForSingleObject"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtWaitForMultipleObjects == nullptr)
+ {
+ if((NtWaitForMultipleObjects = reinterpret_cast<NtWaitForMultipleObjects_t>(GetProcAddress(ntdllh, "NtWaitForMultipleObjects"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtDelayExecution == nullptr)
+ {
+ if((NtDelayExecution = reinterpret_cast<NtDelayExecution_t>(GetProcAddress(ntdllh, "NtDelayExecution"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtLockFile == nullptr)
+ {
+ if((NtLockFile = reinterpret_cast<NtLockFile_t>(GetProcAddress(ntdllh, "NtLockFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtUnlockFile == nullptr)
+ {
+ if((NtUnlockFile = reinterpret_cast<NtUnlockFile_t>(GetProcAddress(ntdllh, "NtUnlockFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtCreateSection == nullptr)
+ {
+ if((NtCreateSection = reinterpret_cast<NtCreateSection_t>(GetProcAddress(ntdllh, "NtCreateSection"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtQuerySection == nullptr)
+ {
+ if((NtQuerySection = reinterpret_cast<NtQuerySection_t>(GetProcAddress(ntdllh, "NtQuerySection"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtExtendSection == nullptr)
+ {
+ if((NtExtendSection = reinterpret_cast<NtExtendSection_t>(GetProcAddress(ntdllh, "NtExtendSection"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtMapViewOfSection == nullptr)
+ {
+ if((NtMapViewOfSection = reinterpret_cast<NtMapViewOfSection_t>(GetProcAddress(ntdllh, "NtMapViewOfSection"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtUnmapViewOfSection == nullptr)
+ {
+ if((NtUnmapViewOfSection = reinterpret_cast<NtUnmapViewOfSection_t>(GetProcAddress(ntdllh, "NtUnmapViewOfSection"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtFlushBuffersFileEx == nullptr)
+ {
+ if((NtFlushBuffersFileEx = reinterpret_cast<NtFlushBuffersFileEx_t>(GetProcAddress(ntdllh, "NtFlushBuffersFileEx"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtSetSystemInformation == nullptr)
+ {
+ if((NtSetSystemInformation = reinterpret_cast<NtSetSystemInformation_t>(GetProcAddress(ntdllh, "NtSetSystemInformation"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtAllocateVirtualMemory == nullptr)
+ {
+ if((NtAllocateVirtualMemory = reinterpret_cast<NtAllocateVirtualMemory_t>(GetProcAddress(ntdllh, "NtAllocateVirtualMemory"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtFreeVirtualMemory == nullptr)
+ {
+ if((NtFreeVirtualMemory = reinterpret_cast<NtFreeVirtualMemory_t>(GetProcAddress(ntdllh, "NtFreeVirtualMemory"))) == nullptr)
+ {
+ abort();
+ }
+ }
+
+ HMODULE advapi32 = LoadLibraryA("ADVAPI32.DLL");
+ if(RtlGenRandom == nullptr)
+ {
+ if((RtlGenRandom = reinterpret_cast<RtlGenRandom_t>(GetProcAddress(advapi32, "SystemFunction036"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(OpenProcessToken == nullptr)
+ {
+ if((OpenProcessToken = reinterpret_cast<OpenProcessToken_t>(GetProcAddress(advapi32, "OpenProcessToken"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(!LookupPrivilegeValue)
+ {
+ if((LookupPrivilegeValue = reinterpret_cast<LookupPrivilegeValue_t>(GetProcAddress(advapi32, "LookupPrivilegeValueW"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(AdjustTokenPrivileges == nullptr)
+ {
+ if((AdjustTokenPrivileges = reinterpret_cast<AdjustTokenPrivileges_t>(GetProcAddress(advapi32, "AdjustTokenPrivileges"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ // Only provided on Windows 8 and above
+ if(PrefetchVirtualMemory_ == nullptr)
+ {
+ PrefetchVirtualMemory_ = reinterpret_cast<PrefetchVirtualMemory_t>(GetProcAddress(kernel32, "PrefetchVirtualMemory"));
+ }
+ if(DiscardVirtualMemory_ == nullptr)
+ {
+ DiscardVirtualMemory_ = reinterpret_cast<DiscardVirtualMemory_t>(GetProcAddress(kernel32, "DiscardVirtualMemory"));
+ }
+#ifdef AFIO_OP_STACKBACKTRACEDEPTH
+ if(dbghelp)
+ {
+ HMODULE dbghelp = LoadLibraryA("DBGHELP.DLL");
+ if(!(SymInitialize = (SymInitialize_t) GetProcAddress(dbghelp, "SymInitializeW")))
+ abort();
+ if(!SymInitialize(GetCurrentProcess(), nullptr, true))
+ abort();
+ if(!(SymGetLineFromAddr64 = (SymGetLineFromAddr64_t) GetProcAddress(dbghelp, "SymGetLineFromAddrW64")))
+ abort();
+ }
+#endif
+ if(RtlCaptureStackBackTrace == nullptr)
+ {
+ if((RtlCaptureStackBackTrace = reinterpret_cast<RtlCaptureStackBackTrace_t>(GetProcAddress(ntdllh, "RtlCaptureStackBackTrace"))) == nullptr)
+ {
+ abort();
+ }
+ }
+
+ if(RtlDosPathNameToNtPathName_U == nullptr)
+ {
+ if((RtlDosPathNameToNtPathName_U = reinterpret_cast<RtlDosPathNameToNtPathName_U_t>(GetProcAddress(ntdllh, "RtlDosPathNameToNtPathName_U"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(RtlUTF8ToUnicodeN == nullptr)
+ {
+ if((RtlUTF8ToUnicodeN = reinterpret_cast<RtlUTF8ToUnicodeN_t>(GetProcAddress(ntdllh, "RtlUTF8ToUnicodeN"))) == nullptr)
+ {
+ abort();
+ }
+ }
+
+ // MAKE SURE you update the early exit check at the top to whatever the last of these is!
+ }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ inline void init()
+ {
+ static bool initialised = false;
+ if(!initialised)
+ {
+ doinit();
+ initialised = true;
+ }
+ }
+
+ inline filesystem::file_type to_st_type(ULONG FileAttributes, ULONG ReparsePointTag)
+ {
+#ifdef AFIO_USE_LEGACY_FILESYSTEM_SEMANTICS
+ if(FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && (ReparsePointTag == IO_REPARSE_TAG_MOUNT_POINT || ReparsePointTag == IO_REPARSE_TAG_SYMLINK))
+ return filesystem::file_type::symlink_file;
+ // return filesystem::file_type::reparse_file;
+ else if(FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ return filesystem::file_type::directory_file;
+ else
+ return filesystem::file_type::regular_file;
+#else
+ if(((FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u) && (ReparsePointTag == IO_REPARSE_TAG_MOUNT_POINT || ReparsePointTag == IO_REPARSE_TAG_SYMLINK))
+ {
+ return filesystem::file_type::symlink;
+ // return filesystem::file_type::reparse_file;
+ }
+ if((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0u)
+ {
+ return filesystem::file_type::directory;
+ }
+
+
+ return filesystem::file_type::regular;
+
+#endif
+ }
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 6326) // comparison of constants
+#endif
+ inline std::chrono::system_clock::time_point to_timepoint(LARGE_INTEGER time)
+ {
+ // For speed we make the big assumption that the STL's system_clock is based on the time_t epoch 1st Jan 1970.
+ static constexpr unsigned long long FILETIME_OFFSET_TO_1970 = ((27111902ULL << 32U) + 3577643008ULL);
+ // Need to have this self-adapt to the STL being used
+ static constexpr unsigned long long STL_TICKS_PER_SEC = static_cast<unsigned long long>(std::chrono::system_clock::period::den) / std::chrono::system_clock::period::num;
+ static constexpr unsigned long long multiplier = STL_TICKS_PER_SEC >= 10000000ULL ? STL_TICKS_PER_SEC / 10000000ULL : 1;
+ static constexpr unsigned long long divider = STL_TICKS_PER_SEC >= 10000000ULL ? 1 : 10000000ULL / STL_TICKS_PER_SEC;
+
+ unsigned long long ticks_since_1970 = (time.QuadPart - FILETIME_OFFSET_TO_1970); // In 100ns increments
+ std::chrono::system_clock::duration duration(ticks_since_1970 * multiplier / divider);
+ return std::chrono::system_clock::time_point(duration);
+ }
+ inline LARGE_INTEGER from_timepoint(std::chrono::system_clock::time_point time)
+ {
+ // For speed we make the big assumption that the STL's system_clock is based on the time_t epoch 1st Jan 1970.
+ static constexpr unsigned long long FILETIME_OFFSET_TO_1970 = ((27111902ULL << 32U) + 3577643008ULL);
+ static const std::chrono::system_clock::time_point time_point_1970 = std::chrono::system_clock::from_time_t(0);
+
+ LARGE_INTEGER ret{};
+ ret.QuadPart = FILETIME_OFFSET_TO_1970 + std::chrono::duration_cast<std::chrono::nanoseconds>(time - time_point_1970).count() / 100;
+ return ret;
+ }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// Adapted from http://www.cprogramming.com/snippets/source-code/convert-ntstatus-win32-error
+// Could use RtlNtStatusToDosError() instead
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 6387) // MSVC sanitiser warns on misuse of GetOverlappedResult
+#endif
+ inline DWORD win32_error_from_nt_status(NTSTATUS ntstatus)
+ {
+ DWORD br;
+ OVERLAPPED o{};
+
+ SetLastError(0);
+ o.Internal = ntstatus;
+ o.InternalHigh = 0;
+ o.Offset = 0;
+ o.OffsetHigh = 0;
+ o.hEvent = nullptr;
+ GetOverlappedResult(nullptr, &o, &br, FALSE);
+ return GetLastError();
+ }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+} // namespace windows_nt_kernel
+
+#if 0
+inline void fill_stat_t(stat_t &stat, AFIO_POSIX_STAT_STRUCT s, metadata_flags wanted)
+{
+#ifndef _WIN32
+ if (!!(wanted&metadata_flags::dev)) { stat.st_dev = s.st_dev; }
+#endif
+ if (!!(wanted&metadata_flags::ino)) { stat.st_ino = s.st_ino; }
+ if (!!(wanted&metadata_flags::type)) { stat.st_type = to_st_type(s.st_mode); }
+#ifndef _WIN32
+ if (!!(wanted&metadata_flags::perms)) { stat.st_mode = s.st_perms; }
+#endif
+ if (!!(wanted&metadata_flags::nlink)) { stat.st_nlink = s.st_nlink; }
+#ifndef _WIN32
+ if (!!(wanted&metadata_flags::uid)) { stat.st_uid = s.st_uid; }
+ if (!!(wanted&metadata_flags::gid)) { stat.st_gid = s.st_gid; }
+ if (!!(wanted&metadata_flags::rdev)) { stat.st_rdev = s.st_rdev; }
+#endif
+ if (!!(wanted&metadata_flags::atim)) { stat.st_atim = chrono::system_clock::from_time_t(s.st_atime); }
+ if (!!(wanted&metadata_flags::mtim)) { stat.st_mtim = chrono::system_clock::from_time_t(s.st_mtime); }
+ if (!!(wanted&metadata_flags::birthtim)) { stat.st_birthtim = chrono::system_clock::from_time_t(s.st_ctime); }
+ if (!!(wanted&metadata_flags::size)) { stat.st_size = s.st_size; }
+}
+#endif
+
+// Utility routines for implementing deadline sleeps on Windows which only provides interval sleeps
+#if 0 // This is the win32 edition. The NT kernel edition is much cleaner and lower overhead.
+struct win_handle_deleter
+{
+ void operator()(HANDLE h) { CloseHandle(h); }
+};
+inline std::unique_ptr<void, win_handle_deleter> create_waitable_timer()
+{
+ HANDLE ret = CreateWaitableTimer(nullptr, true, nullptr);
+ if(INVALID_HANDLE_VALUE == ret)
+ throw std::system_error(GetLastError(), std::system_category());
+ return std::unique_ptr<void, win_handle_deleter>(ret);
+}
+inline HANDLE get_thread_local_waitable_timer()
+{
+ static thread_local auto self = create_waitable_timer();
+ return self.get();
+}
+
+/*! Defines a number of variables into its scope:
+- began_steady: Set to the steady clock at the beginning of a sleep
+- end_utc: Set to the system clock when the sleep must end
+- sleep_interval: Set to the number of steady milliseconds until the sleep must end
+- sleep_object: Set to a primed deadline timer HANDLE which will signal when the system clock reaches the deadline
+*/
+#define AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d) \
+ std::chrono::steady_clock::time_point began_steady; \
+ \
+std::chrono::system_clock::time_point end_utc; \
+ \
+if(d) \
+ \
+{ \
+ if((d).steady) \
+ began_steady = std::chrono::steady_clock::now(); \
+ else \
+ end_utc = (d).to_time_point(); \
+ \
+} \
+ \
+DWORD sleep_interval = INFINITE; \
+ \
+HANDLE sleep_object = nullptr;
+
+#define AFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d) \
+ \
+if(d) \
+ \
+{ \
+ if((d).steady) \
+ { \
+ std::chrono::milliseconds ms; \
+ ms = std::chrono::duration_cast<std::chrono::milliseconds>((began_steady + std::chrono::nanoseconds(d.nsecs)) - std::chrono::steady_clock::now()); \
+ if(ms.count() < 0) \
+ sleep_interval = 0; \
+ else \
+ sleep_interval = (DWORD) ms.count(); \
+ } \
+ else \
+ { \
+ sleep_object = get_thread_local_waitable_timer(); \
+ LARGE_INTEGER due_time = windows_nt_kernel::from_timepoint(end_utc); \
+ if(!SetWaitableTimer(sleep_object, &due_time, 0, nullptr, nullptr, false)) \
+ throw std::system_error(GetLastError(), std::system_category()); \
+ } \
+ \
+}
+
+#define AFIO_WIN_DEADLINE_TO_TIMEOUT(type, d) \
+ \
+if(d) \
+ \
+{ \
+ if((d).steady) \
+ { \
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs))) \
+ return make_errored_result<type>(errc::timed_out); \
+ } \
+ else \
+ { \
+ if(std::chrono::system_clock::now() >= end_utc) \
+ return make_errored_result<type>(errc::timed_out); \
+ } \
+ \
+}
+#else
+/*! Defines a number of variables into its scope:
+- began_steady: Set to the steady clock at the beginning of a sleep
+- end_utc: Set to the system clock when the sleep must end
+- sleep_interval: Set to the number of steady milliseconds until the sleep must end
+- sleep_object: Set to a primed deadline timer HANDLE which will signal when the system clock reaches the deadline
+*/
+#define AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d) \
+ std::chrono::steady_clock::time_point began_steady; \
+ \
+std::chrono::system_clock::time_point end_utc; \
+ \
+alignas(8) LARGE_INTEGER _timeout{}; \
+ \
+memset(&_timeout, 0, sizeof(_timeout)); \
+ \
+LARGE_INTEGER *timeout = nullptr; \
+ \
+if(d) \
+ \
+{ \
+ if((d).steady) \
+ began_steady = std::chrono::steady_clock::now(); \
+ else \
+ { \
+ end_utc = (d).to_time_point(); \
+ _timeout = windows_nt_kernel::from_timepoint(end_utc); \
+ } \
+ timeout = &_timeout; \
+ \
+}
+
+#define AFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d) \
+ if((d) && (d).steady) \
+ { \
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now()); \
+ if(ns.count() < 0) \
+ _timeout.QuadPart = 0; \
+ else \
+ _timeout.QuadPart = ns.count() / -100; \
+ }
+
+#define AFIO_WIN_DEADLINE_TO_PARTIAL_DEADLINE(nd, d) \
+ if(d) \
+ { \
+ if((d).steady) \
+ { \
+ std::chrono::nanoseconds ns = std::chrono::duration_cast<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds((d).nsecs)) - std::chrono::steady_clock::now()); \
+ if(ns.count() < 0) \
+ (nd).nsecs = 0; \
+ else \
+ (nd).nsecs = ns.count(); \
+ } \
+ else \
+ (nd) = (d); \
+ }
+
+#define AFIO_WIN_DEADLINE_TO_TIMEOUT(d) \
+ \
+if(d) \
+ \
+{ \
+ if((d).steady) \
+ { \
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs))) \
+ return errc::timed_out; \
+ } \
+ else \
+ { \
+ if(std::chrono::system_clock::now() >= end_utc) \
+ return errc::timed_out; \
+ } \
+ \
+}
+#endif
+
+// Initialise an IO_STATUS_BLOCK for later wait operations
+inline windows_nt_kernel::IO_STATUS_BLOCK make_iostatus() noexcept
+{
+ windows_nt_kernel::IO_STATUS_BLOCK isb{};
+ memset(&isb, 0, sizeof(isb));
+ isb.Status = -1;
+ return isb;
+}
+
+// Wait for an overlapped handle to complete a specific operation
+inline NTSTATUS ntwait(HANDLE h, windows_nt_kernel::IO_STATUS_BLOCK &isb, const deadline &d) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
+ do // needs to be a do, not while in order to flip auto reset event objects etc.
+ {
+ AFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d);
+ // Pump alerts and APCs
+ NTSTATUS ntstat = NtWaitForSingleObject(h, 1u, timeout);
+ if(STATUS_TIMEOUT == ntstat)
+ {
+ auto expected = static_cast<DWORD>(-1);
+ // Have to be very careful here, atomically swap timed out for the -1 only
+ InterlockedCompareExchange(&isb.Status, ntstat, expected);
+ // If it's no longer -1 or the i/o completes, that's fine.
+ return isb.Status;
+ }
+ } while(isb.Status == -1);
+ return isb.Status;
+}
+inline NTSTATUS ntwait(HANDLE h, OVERLAPPED &ol, const deadline &d) noexcept
+{
+ return ntwait(h, reinterpret_cast<windows_nt_kernel::IO_STATUS_BLOCK &>(ol), d);
+}
+
+// Sleep the thread until some deadline
+inline bool ntsleep(const deadline &d, bool return_on_alert = false) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
+ alignas(8) LARGE_INTEGER infinity{};
+ infinity.QuadPart = INT64_MIN;
+ for(;;)
+ {
+ AFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d);
+ // Pump alerts and APCs
+ NTSTATUS ntstat = NtDelayExecution(1u, timeout != nullptr ? timeout : &infinity);
+ (void) ntstat;
+ if((d).steady)
+ {
+ if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds((d).nsecs)))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if(std::chrono::system_clock::now() >= end_utc)
+ {
+ return false;
+ }
+ }
+ if(return_on_alert)
+ {
+ return true;
+ }
+ }
+}
+
+
+// Utility routine for building an ACCESS_MASK from a handle::mode
+inline result<ACCESS_MASK> access_mask_from_handle_mode(native_handle_type &nativeh, handle::mode _mode, handle::flag flags)
+{
+ ACCESS_MASK access = SYNCHRONIZE; // appears that this is the bare minimum for the call to succeed
+ switch(_mode)
+ {
+ case handle::mode::unchanged:
+ return errc::invalid_argument;
+ case handle::mode::none:
+ break;
+ case handle::mode::attr_read:
+ access |= FILE_READ_ATTRIBUTES | STANDARD_RIGHTS_READ;
+ break;
+ case handle::mode::attr_write:
+ access |= DELETE | FILE_READ_ATTRIBUTES | STANDARD_RIGHTS_READ | FILE_WRITE_ATTRIBUTES | STANDARD_RIGHTS_WRITE;
+ break;
+ case handle::mode::read:
+ access |= GENERIC_READ;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable;
+ break;
+ case handle::mode::write:
+ access |= DELETE | GENERIC_WRITE | GENERIC_READ;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
+ break;
+ case handle::mode::append:
+ access |= DELETE | GENERIC_READ | FILE_WRITE_ATTRIBUTES | STANDARD_RIGHTS_WRITE | FILE_APPEND_DATA;
+ nativeh.behaviour |= native_handle_type::disposition::writable | native_handle_type::disposition::append_only;
+ break;
+ }
+ // Should we allow unlink on close if opening read only? I guess if Windows allows it, so should we.
+ if(flags & handle::flag::unlink_on_first_close)
+ {
+ access |= DELETE;
+ }
+ return access;
+}
+inline result<DWORD> attributes_from_handle_caching_and_flags(native_handle_type &nativeh, handle::caching _caching, handle::flag flags)
+{
+ DWORD attribs = 0;
+ if(flags & handle::flag::overlapped)
+ {
+ attribs |= FILE_FLAG_OVERLAPPED;
+ nativeh.behaviour |= native_handle_type::disposition::overlapped;
+ }
+ switch(_caching)
+ {
+ case handle::caching::unchanged:
+ return errc::invalid_argument;
+ case handle::caching::none:
+ attribs |= FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH;
+ nativeh.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case handle::caching::only_metadata:
+ attribs |= FILE_FLAG_NO_BUFFERING;
+ nativeh.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case handle::caching::reads:
+ case handle::caching::reads_and_metadata:
+ attribs |= FILE_FLAG_WRITE_THROUGH;
+ break;
+ case handle::caching::all:
+ case handle::caching::safety_fsyncs:
+ break;
+ case handle::caching::temporary:
+ attribs |= FILE_ATTRIBUTE_TEMPORARY;
+ break;
+ }
+ if(flags & handle::flag::unlink_on_first_close)
+ {
+ attribs |= FILE_FLAG_DELETE_ON_CLOSE;
+ }
+ if(flags & handle::flag::disable_prefetching)
+ {
+ attribs |= FILE_FLAG_RANDOM_ACCESS;
+ }
+ if(flags & handle::flag::maximum_prefetching)
+ {
+ attribs |= FILE_FLAG_SEQUENTIAL_SCAN;
+ }
+ return attribs;
+}
+inline result<DWORD> ntflags_from_handle_caching_and_flags(native_handle_type &nativeh, handle::caching _caching, handle::flag flags)
+{
+ DWORD ntflags = 0;
+ if(flags & handle::flag::overlapped)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::overlapped;
+ }
+ else
+ {
+ ntflags |= 0x20 /*FILE_SYNCHRONOUS_IO_NONALERT*/;
+ }
+ switch(_caching)
+ {
+ case handle::caching::unchanged:
+ return errc::invalid_argument;
+ case handle::caching::none:
+ ntflags |= 0x00000008 /*FILE_NO_INTERMEDIATE_BUFFERING*/ | 0x00000002 /*FILE_WRITE_THROUGH*/;
+ nativeh.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case handle::caching::only_metadata:
+ ntflags |= 0x00000008 /*FILE_NO_INTERMEDIATE_BUFFERING*/;
+ nativeh.behaviour |= native_handle_type::disposition::aligned_io;
+ break;
+ case handle::caching::reads:
+ case handle::caching::reads_and_metadata:
+ ntflags |= 0x00000002 /*FILE_WRITE_THROUGH*/;
+ break;
+ case handle::caching::all:
+ case handle::caching::safety_fsyncs:
+ break;
+ case handle::caching::temporary:
+ // should be handled by attributes_from_handle_caching_and_flags
+ break;
+ }
+ if(flags & handle::flag::unlink_on_first_close)
+ {
+ ntflags |= 0x00001000 /*FILE_DELETE_ON_CLOSE*/;
+ }
+ if(flags & handle::flag::disable_prefetching)
+ {
+ ntflags |= 0x00000800 /*FILE_RANDOM_ACCESS*/;
+ }
+ if(flags & handle::flag::maximum_prefetching)
+ {
+ ntflags |= 0x00000004 /*FILE_SEQUENTIAL_ONLY*/;
+ }
+ return ntflags;
+}
+
+/* Our own custom CreateFileW() implementation.
+
+The Win32 CreateFileW() implementation is unfortunately slow. It also, very annoyingly,
+maps STATUS_DELETE_PENDING onto ERROR_ACCESS_DENIED instead of to ERROR_DELETE_PENDING
+which means our file open routines return EACCES, which is useless to us for detecting
+when we are trying to open files being deleted. We therefore reimplement CreateFileW()
+with a non-broken version.
+
+This edition does pretty much the same as the Win32 edition, minus support for file
+templates and lpFileName being anything but a file path.
+*/
+inline HANDLE CreateFileW_(_In_ LPCTSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile, bool forcedir = false)
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ if((lpFileName == nullptr) || (lpFileName[0] == 0u))
+ {
+ SetLastError(ERROR_PATH_NOT_FOUND);
+ return INVALID_HANDLE_VALUE; // NOLINT
+ }
+ if((hTemplateFile != nullptr) || (lstrcmpW(lpFileName, L"CONIN$") == 0) || (lstrcmpW(lpFileName, L"CONOUT$") == 0))
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return INVALID_HANDLE_VALUE; // NOLINT
+ }
+ switch(dwCreationDisposition)
+ {
+ case CREATE_NEW:
+ dwCreationDisposition = FILE_CREATE;
+ break;
+ case CREATE_ALWAYS:
+ dwCreationDisposition = FILE_OVERWRITE_IF;
+ break;
+ case OPEN_EXISTING:
+ dwCreationDisposition = FILE_OPEN;
+ break;
+ case OPEN_ALWAYS:
+ dwCreationDisposition = FILE_OPEN_IF;
+ break;
+ case TRUNCATE_EXISTING:
+ dwCreationDisposition = FILE_OVERWRITE;
+ break;
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return INVALID_HANDLE_VALUE; // NOLINT
+ }
+ ULONG flags = 0;
+ if((dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED) == 0u)
+ {
+ flags |= FILE_SYNCHRONOUS_IO_NONALERT;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_WRITE_THROUGH) != 0u)
+ {
+ flags |= FILE_WRITE_THROUGH;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_NO_BUFFERING) != 0u)
+ {
+ flags |= FILE_NO_INTERMEDIATE_BUFFERING;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_RANDOM_ACCESS) != 0u)
+ {
+ flags |= FILE_RANDOM_ACCESS;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_SEQUENTIAL_SCAN) != 0u)
+ {
+ flags |= FILE_SEQUENTIAL_ONLY;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE) != 0u)
+ {
+ flags |= FILE_DELETE_ON_CLOSE;
+ dwDesiredAccess |= DELETE;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS) != 0u)
+ {
+ if((dwDesiredAccess & GENERIC_ALL) != 0u)
+ {
+ flags |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REMOTE_INSTANCE;
+ }
+ else
+ {
+ if((dwDesiredAccess & GENERIC_READ) != 0u)
+ {
+ flags |= FILE_OPEN_FOR_BACKUP_INTENT;
+ }
+ if((dwDesiredAccess & GENERIC_WRITE) != 0u)
+ {
+ flags |= FILE_OPEN_REMOTE_INSTANCE;
+ }
+ }
+ if(forcedir)
+ {
+ flags |= FILE_DIRECTORY_FILE;
+ }
+ }
+ else
+ {
+ flags |= FILE_NON_DIRECTORY_FILE;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_OPEN_REPARSE_POINT) != 0u)
+ {
+ flags |= FILE_OPEN_REPARSE_POINT;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_OPEN_NO_RECALL) != 0u)
+ {
+ flags |= FILE_OPEN_NO_RECALL;
+ }
+
+ UNICODE_STRING NtPath{};
+ if(RtlDosPathNameToNtPathName_U(lpFileName, &NtPath, nullptr, nullptr) == 0u)
+ {
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return INVALID_HANDLE_VALUE; // NOLINT
+ }
+ auto unntpath = undoer([&NtPath] {
+ if(HeapFree(GetProcessHeap(), 0, NtPath.Buffer) == 0)
+ {
+ abort();
+ }
+ });
+
+ OBJECT_ATTRIBUTES ObjectAttributes{};
+ InitializeObjectAttributes(&ObjectAttributes, &NtPath, 0, nullptr, nullptr);
+ if(lpSecurityAttributes != nullptr)
+ {
+ if(lpSecurityAttributes->bInheritHandle != 0)
+ {
+ ObjectAttributes.Attributes |= OBJ_INHERIT;
+ }
+ ObjectAttributes.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor;
+ }
+ if((dwFlagsAndAttributes & FILE_FLAG_POSIX_SEMANTICS) == 0u)
+ {
+ ObjectAttributes.Attributes |= OBJ_CASE_INSENSITIVE;
+ }
+
+ HANDLE ret = INVALID_HANDLE_VALUE; // NOLINT
+ IO_STATUS_BLOCK isb = make_iostatus();
+ dwFlagsAndAttributes &= ~0xfff80000;
+ NTSTATUS ntstat = NtCreateFile(&ret, dwDesiredAccess, &ObjectAttributes, &isb, nullptr, dwFlagsAndAttributes, dwShareMode, dwCreationDisposition, flags, nullptr, 0);
+ if(STATUS_SUCCESS == ntstat)
+ {
+ return ret;
+ }
+
+ win32_error_from_nt_status(ntstat);
+ if(STATUS_DELETE_PENDING == ntstat)
+ {
+ SetLastError(ERROR_DELETE_PENDING);
+ }
+ return INVALID_HANDLE_VALUE; // NOLINT
+}
+
+// Detect if we are running under SUID or SGID
+inline bool running_under_suid_gid()
+{
+ HANDLE processtoken;
+ if(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &processtoken) == 0)
+ {
+ abort();
+ }
+ auto unprocesstoken = undoer([&processtoken] { CloseHandle(processtoken); });
+ (void) unprocesstoken;
+ DWORD written;
+ char buffer1[1024], buffer2[1024];
+ if(GetTokenInformation(processtoken, TokenUser, buffer1, sizeof(buffer1), &written) == 0)
+ {
+ abort();
+ }
+ if(GetTokenInformation(processtoken, TokenOwner, buffer2, sizeof(buffer2), &written) == 0)
+ {
+ abort();
+ }
+ auto *tu = reinterpret_cast<TOKEN_USER *>(buffer1);
+ auto *to = reinterpret_cast<TOKEN_OWNER *>(buffer2);
+ return EqualSid(tu->User.Sid, to->Owner) == 0;
+}
+
+AFIO_V2_NAMESPACE_END
+
+#endif
diff --git a/include/llfio/v2.0/detail/impl/windows/io_handle.ipp b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp
new file mode 100644
index 00000000..71f51ccb
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp
@@ -0,0 +1,220 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../io_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+size_t io_handle::max_buffers() const noexcept
+{
+ return 1; // async_file_handle may override this virtual function
+}
+
+template <class BuffersType, class Syscall> inline io_handle::io_result<BuffersType> do_read_write(const native_handle_type &nativeh, Syscall &&syscall, io_handle::io_request<BuffersType> reqs, deadline d) noexcept
+{
+ if(d && !nativeh.is_overlapped())
+ {
+ return errc::not_supported;
+ }
+ if(reqs.buffers.size() > 64)
+ {
+ return errc::argument_list_too_long;
+ }
+
+ AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
+ std::array<OVERLAPPED, 64> _ols{};
+ memset(_ols.data(), 0, reqs.buffers.size() * sizeof(OVERLAPPED));
+ span<OVERLAPPED> ols(_ols.data(), reqs.buffers.size());
+ auto ol_it = ols.begin();
+ DWORD transferred = 0;
+ auto cancel_io = undoer([&] {
+ if(nativeh.is_overlapped())
+ {
+ for(auto &ol : ols)
+ {
+ CancelIoEx(nativeh.h, &ol);
+ }
+ for(auto &ol : ols)
+ {
+ ntwait(nativeh.h, ol, deadline());
+ }
+ }
+ });
+ for(auto &req : reqs.buffers)
+ {
+ OVERLAPPED &ol = *ol_it++;
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ if(nativeh.is_append_only())
+ {
+ ol.OffsetHigh = ol.Offset = 0xffffffff;
+ }
+ else
+ {
+#ifndef NDEBUG
+ if(nativeh.requires_aligned_io())
+ {
+ assert((reqs.offset & 511) == 0);
+ }
+#endif
+ ol.OffsetHigh = (reqs.offset >> 32) & 0xffffffff;
+ ol.Offset = reqs.offset & 0xffffffff;
+ }
+#ifndef NDEBUG
+ if(nativeh.requires_aligned_io())
+ {
+ assert(((uintptr_t) req.data & 511) == 0);
+ assert((req.len & 511) == 0);
+ }
+#endif
+ if(!syscall(nativeh.h, req.data, static_cast<DWORD>(req.len), &transferred, &ol) && ERROR_IO_PENDING != GetLastError())
+ {
+ return win32_error();
+ }
+ reqs.offset += req.len;
+ }
+ // If handle is overlapped, wait for completion of each i/o.
+ if(nativeh.is_overlapped())
+ {
+ for(auto &ol : ols)
+ {
+ deadline nd = d;
+ AFIO_WIN_DEADLINE_TO_PARTIAL_DEADLINE(nd, d);
+ if(STATUS_TIMEOUT == ntwait(nativeh.h, ol, nd))
+ {
+ AFIO_WIN_DEADLINE_TO_TIMEOUT(d);
+ }
+ }
+ }
+ cancel_io.dismiss();
+ for(size_t n = 0; n < reqs.buffers.size(); n++)
+ {
+ // It seems the NT kernel is guilty of casting bugs sometimes
+ ols[n].Internal = ols[n].Internal & 0xffffffff;
+ if(ols[n].Internal != 0)
+ {
+ return ntkernel_error(static_cast<NTSTATUS>(ols[n].Internal));
+ }
+ reqs.buffers[n].len = ols[n].InternalHigh;
+ }
+ return io_handle::io_result<BuffersType>(std::move(reqs.buffers));
+}
+
+io_handle::io_result<io_handle::buffers_type> io_handle::read(io_handle::io_request<io_handle::buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ return do_read_write(_v, &ReadFile, reqs, d);
+}
+
+io_handle::io_result<io_handle::const_buffers_type> io_handle::write(io_handle::io_request<io_handle::const_buffers_type> reqs, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ return do_read_write(_v, &WriteFile, reqs, d);
+}
+
+result<io_handle::extent_guard> io_handle::lock(io_handle::extent_type offset, io_handle::extent_type bytes, bool exclusive, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(_v.h);
+ if(d && d.nsecs > 0 && !_v.is_overlapped())
+ {
+ return errc::not_supported;
+ }
+ DWORD flags = exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0;
+ if(d && (d.nsecs == 0u))
+ {
+ flags |= LOCKFILE_FAIL_IMMEDIATELY;
+ }
+ AFIO_WIN_DEADLINE_TO_SLEEP_INIT(d);
+ OVERLAPPED ol{};
+ memset(&ol, 0, sizeof(ol));
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ ol.OffsetHigh = (offset >> 32) & 0xffffffff;
+ ol.Offset = offset & 0xffffffff;
+ DWORD bytes_high = bytes == 0u ? MAXDWORD : static_cast<DWORD>((bytes >> 32) & 0xffffffff);
+ DWORD bytes_low = bytes == 0u ? MAXDWORD : static_cast<DWORD>(bytes & 0xffffffff);
+ if(LockFileEx(_v.h, flags, 0, bytes_low, bytes_high, &ol) == 0)
+ {
+ if(ERROR_LOCK_VIOLATION == GetLastError() && d && (d.nsecs == 0u))
+ {
+ return errc::timed_out;
+ }
+ if(ERROR_IO_PENDING != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ // If handle is overlapped, wait for completion of each i/o.
+ if(_v.is_overlapped())
+ {
+ if(STATUS_TIMEOUT == ntwait(_v.h, ol, d))
+ {
+ AFIO_WIN_DEADLINE_TO_TIMEOUT(d);
+ }
+ // It seems the NT kernel is guilty of casting bugs sometimes
+ ol.Internal = ol.Internal & 0xffffffff;
+ if(ol.Internal != 0)
+ {
+ return ntkernel_error(static_cast<NTSTATUS>(ol.Internal));
+ }
+ }
+ return extent_guard(this, offset, bytes, exclusive);
+}
+
+void io_handle::unlock(io_handle::extent_type offset, io_handle::extent_type bytes) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ OVERLAPPED ol{};
+ memset(&ol, 0, sizeof(ol));
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ ol.OffsetHigh = (offset >> 32) & 0xffffffff;
+ ol.Offset = offset & 0xffffffff;
+ DWORD bytes_high = bytes == 0u ? MAXDWORD : static_cast<DWORD>((bytes >> 32) & 0xffffffff);
+ DWORD bytes_low = bytes == 0u ? MAXDWORD : static_cast<DWORD>(bytes & 0xffffffff);
+ if(UnlockFileEx(_v.h, 0, bytes_low, bytes_high, &ol) == 0)
+ {
+ if(ERROR_IO_PENDING != GetLastError())
+ {
+ auto ret = win32_error();
+ (void) ret;
+ AFIO_LOG_FATAL(_v.h, "io_handle::unlock() failed");
+ std::terminate();
+ }
+ }
+ // If handle is overlapped, wait for completion of each i/o.
+ if(_v.is_overlapped())
+ {
+ ntwait(_v.h, ol, deadline());
+ if(ol.Internal != 0)
+ {
+ // It seems the NT kernel is guilty of casting bugs sometimes
+ ol.Internal = ol.Internal & 0xffffffff;
+ auto ret = ntkernel_error(static_cast<NTSTATUS>(ol.Internal));
+ (void) ret;
+ AFIO_LOG_FATAL(_v.h, "io_handle::unlock() failed");
+ std::terminate();
+ }
+ }
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/io_service.ipp b/include/llfio/v2.0/detail/impl/windows/io_service.ipp
new file mode 100644
index 00000000..4003e1bb
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/io_service.ipp
@@ -0,0 +1,102 @@
+/* Multiplex file i/o
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (4 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../io_service.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+io_service::io_service()
+ : _work_queued(0)
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &_threadh, 0, 0, DUPLICATE_SAME_ACCESS) == 0)
+ {
+ throw std::runtime_error("Failed to create creating thread handle");
+ }
+ _threadid = GetCurrentThreadId();
+}
+
+io_service::~io_service()
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_work_queued != 0u)
+ {
+ fprintf(stderr, "WARNING: ~io_service() sees work still queued, blocking until no work queued\n");
+ while(_work_queued != 0u)
+ {
+ std::this_thread::yield();
+ }
+ }
+ CloseHandle(_threadh);
+}
+
+result<bool> io_service::run_until(deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_work_queued == 0u)
+ {
+ return false;
+ }
+ if(GetCurrentThreadId() != _threadid)
+ {
+ return errc::operation_not_supported;
+ }
+ ntsleep(d, true);
+ return _work_queued != 0;
+}
+
+void io_service::_post(detail::function_ptr<void(io_service *)> &&f)
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ void *data = nullptr;
+ {
+ post_info pi(this, std::move(f));
+ std::lock_guard<decltype(_posts_lock)> g(_posts_lock);
+ _posts.push_back(std::move(pi));
+ data = static_cast<void *>(&_posts.back());
+ }
+ // lambdas can't be __stdcall on winclang, so ...
+ struct lambda
+ {
+ static void __stdcall _(ULONG_PTR data)
+ {
+ auto *pi = reinterpret_cast<post_info *>(data);
+ pi->f(pi->service);
+ pi->service->_post_done(pi);
+ }
+ };
+ PAPCFUNC apcf = lambda::_;
+ if(QueueUserAPC(apcf, _threadh, reinterpret_cast<ULONG_PTR>(data)) != 0u)
+ {
+ _work_enqueued();
+ }
+ else
+ {
+ auto *pi = static_cast<post_info *>(data);
+ pi->service->_post_done(pi);
+ }
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/map_handle.ipp b/include/llfio/v2.0/detail/impl/windows/map_handle.ipp
new file mode 100644
index 00000000..ef7b7a56
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/map_handle.ipp
@@ -0,0 +1,864 @@
+/* A handle to a source of mapped memory
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (17 commits)
+File Created: August 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../map_handle.hpp"
+#include "../../../utils.hpp"
+#include "import.hpp"
+
+#ifdef __has_include
+#if __has_include("../../../quickcpplib/include/signal_guard.hpp")
+#include "../../../quickcpplib/include/algorithm/hash.hpp"
+#include "../../../quickcpplib/include/signal_guard.hpp"
+#else
+#include "quickcpplib/include/algorithm/hash.hpp"
+#include "quickcpplib/include/signal_guard.hpp"
+#endif
+#else
+#include "quickcpplib/include/algorithm/hash.hpp"
+#include "quickcpplib/include/signal_guard.hpp"
+#endif
+
+
+AFIO_V2_NAMESPACE_BEGIN
+
+section_handle::~section_handle()
+{
+ if(_v)
+ {
+ auto ret = section_handle::close();
+ if(ret.has_error())
+ {
+ AFIO_LOG_FATAL(_v.h, "section_handle::~section_handle() close failed");
+ abort();
+ }
+ }
+}
+result<void> section_handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_v)
+ {
+ OUTCOME_TRYV(handle::close());
+ OUTCOME_TRYV(_anonymous.close());
+ _flag = flag::none;
+ }
+ return success();
+}
+
+result<section_handle> section_handle::section(file_handle &backing, extent_type maximum_size, flag _flag) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ result<section_handle> ret(section_handle(native_handle_type(), &backing, file_handle(), _flag));
+ native_handle_type &nativeh = ret.value()._v;
+ ULONG prot = 0, attribs = 0;
+ if(_flag & flag::read)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::readable;
+ }
+ if(_flag & flag::write)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::writable;
+ }
+ if(_flag & flag::execute)
+ {
+ }
+ if(!!(_flag & flag::cow) && !!(_flag & flag::execute))
+ {
+ prot = PAGE_EXECUTE_WRITECOPY;
+ }
+ else if(_flag & flag::execute)
+ {
+ prot = PAGE_EXECUTE;
+ }
+ else if(_flag & flag::cow)
+ {
+ prot = PAGE_WRITECOPY;
+ }
+ else if(_flag & flag::write)
+ {
+ prot = PAGE_READWRITE;
+ }
+ else
+ {
+ prot = PAGE_READONLY; // PAGE_NOACCESS is refused, so this is the next best
+ }
+ attribs = SEC_COMMIT;
+ if(_flag & flag::executable)
+ {
+ attribs = SEC_IMAGE;
+ }
+ if(_flag & flag::prefault)
+ {
+ // Handled during view mapping below
+ }
+ nativeh.behaviour |= native_handle_type::disposition::section;
+ OBJECT_ATTRIBUTES oa{}, *poa = nullptr;
+ UNICODE_STRING _path{};
+ static wchar_t *buffer = []() -> wchar_t * {
+ static wchar_t buffer_[96] = L"\\Sessions\\0\\BaseNamedObjects\\";
+ DWORD sessionid = 0;
+ if(ProcessIdToSessionId(GetCurrentProcessId(), &sessionid) != 0)
+ {
+ wsprintf(buffer_, L"\\Sessions\\%u\\BaseNamedObjects\\", sessionid);
+ }
+ return buffer_;
+ }();
+ static wchar_t *bufferid = wcschr(buffer, 0);
+ if(_flag & flag::singleton)
+ {
+ OUTCOME_TRY(currentpath, backing.current_path());
+ auto hash = QUICKCPPLIB_NAMESPACE::algorithm::hash::fast_hash::hash(reinterpret_cast<const char *>(currentpath.native().data()), currentpath.native().size() * sizeof(wchar_t));
+ auto *_buffer = reinterpret_cast<char *>(bufferid);
+ QUICKCPPLIB_NAMESPACE::algorithm::string::to_hex_string(_buffer, 96 * sizeof(wchar_t), reinterpret_cast<const char *>(hash.as_bytes), sizeof(hash.as_bytes));
+ for(size_t n = 31; n <= 31; n--)
+ {
+ bufferid[n] = _buffer[n];
+ }
+ bufferid[32] = 0;
+ _path.Buffer = buffer;
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>((32 + bufferid - buffer) * sizeof(wchar_t))) + sizeof(wchar_t);
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.Attributes = 0x80 /*OBJ_OPENIF*/;
+ poa = &oa;
+ }
+ LARGE_INTEGER _maximum_size{}, *pmaximum_size = &_maximum_size;
+ if(maximum_size > 0)
+ {
+ _maximum_size.QuadPart = maximum_size;
+ }
+ else
+ {
+ pmaximum_size = nullptr;
+ }
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ HANDLE h;
+ NTSTATUS ntstat = NtCreateSection(&h, SECTION_ALL_ACCESS, poa, pmaximum_size, prot, attribs, backing.native_handle().h);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ nativeh.h = h;
+ return ret;
+}
+
+result<section_handle> section_handle::section(extent_type bytes, const path_handle &dirh, flag _flag) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ OUTCOME_TRY(_anonh, file_handle::temp_inode(dirh));
+ OUTCOME_TRYV(_anonh.truncate(bytes));
+ result<section_handle> ret(section_handle(native_handle_type(), nullptr, std::move(_anonh), _flag));
+ native_handle_type &nativeh = ret.value()._v;
+ file_handle &anonh = ret.value()._anonymous;
+ ULONG prot = 0, attribs = 0;
+ if(_flag & flag::read)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::readable;
+ }
+ if(_flag & flag::write)
+ {
+ nativeh.behaviour |= native_handle_type::disposition::writable;
+ }
+ if(_flag & flag::execute)
+ {
+ }
+ if(!!(_flag & flag::cow) && !!(_flag & flag::execute))
+ {
+ prot = PAGE_EXECUTE_WRITECOPY;
+ }
+ else if(_flag & flag::execute)
+ {
+ prot = PAGE_EXECUTE;
+ }
+ else if(_flag & flag::cow)
+ {
+ prot = PAGE_WRITECOPY;
+ }
+ else if(_flag & flag::write)
+ {
+ prot = PAGE_READWRITE;
+ }
+ else
+ {
+ prot = PAGE_READONLY; // PAGE_NOACCESS is refused, so this is the next best
+ }
+ attribs = SEC_COMMIT;
+ // On Windows, asking for inaccessible memory from the swap file almost certainly
+ // means the user is intending to change permissions later, so reserve read/write
+ // memory of the size requested
+ if(!_flag)
+ {
+ attribs = SEC_RESERVE;
+ prot = PAGE_READWRITE;
+ }
+ if(_flag & flag::executable)
+ {
+ attribs = SEC_IMAGE;
+ }
+ if(_flag & flag::prefault)
+ {
+ // Handled during view mapping below
+ }
+ nativeh.behaviour |= native_handle_type::disposition::section;
+ LARGE_INTEGER _maximum_size{}, *pmaximum_size = &_maximum_size;
+ _maximum_size.QuadPart = bytes;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ HANDLE h;
+ NTSTATUS ntstat = NtCreateSection(&h, SECTION_ALL_ACCESS, nullptr, pmaximum_size, prot, attribs, anonh.native_handle().h);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ nativeh.h = h;
+ return ret;
+}
+
+result<section_handle::extent_type> section_handle::length() const noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ SECTION_BASIC_INFORMATION sbi{};
+ NTSTATUS ntstat = NtQuerySection(_v.h, SectionBasicInformation, &sbi, sizeof(sbi), nullptr);
+ if(STATUS_SUCCESS != ntstat)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return sbi.MaximumSize.QuadPart;
+}
+
+result<section_handle::extent_type> section_handle::truncate(extent_type newsize) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(newsize == 0u)
+ {
+ if(_backing != nullptr)
+ {
+ OUTCOME_TRY(length, _backing->maximum_extent());
+ newsize = length;
+ }
+ else
+ {
+ return errc::invalid_argument;
+ }
+ }
+ LARGE_INTEGER _maximum_size{};
+ _maximum_size.QuadPart = newsize;
+ NTSTATUS ntstat = NtExtendSection(_v.h, &_maximum_size);
+ if(STATUS_SUCCESS != ntstat)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return newsize;
+}
+
+
+/******************************************* map_handle *********************************************/
+
+template <class T> static inline T win32_round_up_to_allocation_size(T i) noexcept
+{
+ // Should we fetch the allocation granularity from Windows? I very much doubt it'll ever change from 64Kb
+ i = (T)((AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<uintptr_t>(i) + 65535) & ~(65535)); // NOLINT
+ return i;
+}
+static inline void win32_map_flags(native_handle_type &nativeh, DWORD &allocation, DWORD &prot, size_t &commitsize, bool enable_reservation, section_handle::flag _flag)
+{
+ prot = PAGE_NOACCESS;
+ if(enable_reservation && ((_flag & section_handle::flag::nocommit) || (_flag == section_handle::flag::none)))
+ {
+ allocation = MEM_RESERVE;
+ prot = PAGE_NOACCESS;
+ commitsize = 0;
+ }
+ if(_flag & section_handle::flag::cow)
+ {
+ prot = PAGE_WRITECOPY;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
+ }
+ else if(_flag & section_handle::flag::write)
+ {
+ prot = PAGE_READWRITE;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
+ }
+ else if(_flag & section_handle::flag::read)
+ {
+ prot = PAGE_READONLY;
+ nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable;
+ }
+ if(!!(_flag & section_handle::flag::cow) && !!(_flag & section_handle::flag::execute))
+ {
+ prot = PAGE_EXECUTE_WRITECOPY;
+ }
+ else if(_flag & section_handle::flag::execute)
+ {
+ prot = PAGE_EXECUTE;
+ }
+}
+// Used to apply an operation to all maps within a region
+template <class F> static inline result<void> win32_maps_apply(byte *addr, size_t bytes, F &&f)
+{
+ while(bytes > 0)
+ {
+ MEMORY_BASIC_INFORMATION mbi;
+ byte *thisregion = addr;
+ // Need to iterate until AllocationBase changes
+ for(;;)
+ {
+ if(!VirtualQuery(addr, &mbi, sizeof(mbi)) || mbi.AllocationBase != thisregion)
+ {
+ break;
+ }
+ addr += mbi.RegionSize;
+ if(mbi.RegionSize < bytes)
+ {
+ bytes -= mbi.RegionSize;
+ }
+ else
+ {
+ bytes = 0;
+ }
+ }
+ // Address passed in originally must match an allocation
+ if(addr == thisregion)
+ {
+ return errc::invalid_argument;
+ }
+ OUTCOME_TRYV(f(thisregion, addr - thisregion));
+ }
+ return success();
+}
+// Only for memory allocated with VirtualAlloc. We can special case decommitting or releasing
+// memory because NtFreeVirtualMemory() tells us how much it freed.
+static inline result<void> win32_release_allocations(byte *addr, size_t bytes, ULONG op)
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ while(bytes > 0)
+ {
+ VOID *regionbase = addr;
+ SIZE_T regionsize = (op == MEM_RELEASE) ? 0 : bytes;
+ NTSTATUS ntstat = NtFreeVirtualMemory(GetCurrentProcess(), &regionbase, &regionsize, op);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ addr += regionsize;
+ assert(regionsize <= bytes);
+ bytes -= regionsize;
+ }
+ return success();
+}
+
+map_handle::~map_handle()
+{
+ if(_v)
+ {
+ // Unmap the view
+ auto ret = map_handle::close();
+ if(ret.has_error())
+ {
+ AFIO_LOG_FATAL(_v.h, "map_handle::~map_handle() close failed");
+ abort();
+ }
+ }
+}
+
+result<void> map_handle::close() noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_addr != nullptr)
+ {
+ if(_section != nullptr)
+ {
+ if(is_writable() && (_flag & section_handle::flag::barrier_on_close))
+ {
+ OUTCOME_TRYV(barrier({}, true, false));
+ }
+ OUTCOME_TRYV(win32_maps_apply(_addr, _reservation, [](byte *addr, size_t /* unused */) -> result<void> {
+ NTSTATUS ntstat = NtUnmapViewOfSection(GetCurrentProcess(), addr);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return success();
+ }));
+ }
+ else
+ {
+ OUTCOME_TRYV(win32_release_allocations(_addr, _reservation, MEM_RELEASE));
+ }
+ }
+ // We don't want ~handle() to close our borrowed handle
+ _v = native_handle_type();
+ _addr = nullptr;
+ _length = 0;
+ return success();
+}
+
+native_handle_type map_handle::release() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // We don't want ~handle() to close our borrowed handle
+ _v = native_handle_type();
+ _addr = nullptr;
+ _length = 0;
+ return {};
+}
+
+map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_handle::io_request<map_handle::const_buffers_type> reqs, bool wait_for_device, bool and_metadata, deadline d) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ byte *addr = _addr + reqs.offset;
+ extent_type bytes = 0;
+ // Check for overflow
+ for(const auto &req : reqs.buffers)
+ {
+ if(bytes + req.len < bytes)
+ {
+ return errc::value_too_large;
+ }
+ bytes += req.len;
+ }
+ // bytes = 0 means flush entire mapping
+ if(bytes == 0)
+ {
+ bytes = _reservation - reqs.offset;
+ }
+ // If nvram and not syncing metadata, use lightweight barrier
+ if(!and_metadata && is_nvram())
+ {
+ auto synced = barrier({addr, bytes});
+ if(synced.len >= bytes)
+ {
+ return {reqs.buffers};
+ }
+ }
+ OUTCOME_TRYV(win32_maps_apply(addr, bytes, [](byte *addr, size_t bytes) -> result<void> {
+ if(FlushViewOfFile(addr, static_cast<SIZE_T>(bytes)) == 0)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ if((_section != nullptr) && (_section->backing() != nullptr) && (wait_for_device || and_metadata))
+ {
+ reqs.offset += _offset;
+ return _section->backing()->barrier(reqs, wait_for_device, and_metadata, d);
+ }
+ return {reqs.buffers};
+}
+
+
+result<map_handle> map_handle::map(size_type bytes, section_handle::flag _flag) noexcept
+{
+ bytes = win32_round_up_to_allocation_size(bytes);
+ result<map_handle> ret(map_handle(nullptr));
+ native_handle_type &nativeh = ret.value()._v;
+ DWORD allocation = MEM_RESERVE | MEM_COMMIT, prot;
+ PVOID addr = nullptr;
+ {
+ size_t commitsize;
+ win32_map_flags(nativeh, allocation, prot, commitsize, true, _flag);
+ }
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ addr = VirtualAlloc(nullptr, bytes, allocation, prot);
+ if(addr == nullptr)
+ {
+ return win32_error();
+ }
+ ret.value()._addr = static_cast<byte *>(addr);
+ ret.value()._reservation = bytes;
+ ret.value()._length = bytes;
+
+ // Windows has no way of getting the kernel to prefault maps on creation, so ...
+ if(_flag & section_handle::flag::prefault)
+ {
+ using namespace windows_nt_kernel;
+ // Start an asynchronous prefetch
+ buffer_type b{static_cast<byte *>(addr), bytes};
+ (void) prefetch(span<buffer_type>(&b, 1));
+ // If this kernel doesn't support that API, manually poke every page in the new map
+ if(PrefetchVirtualMemory_ == nullptr)
+ {
+ size_t pagesize = utils::page_size();
+ volatile auto *a = static_cast<volatile char *>(addr);
+ for(size_t n = 0; n < bytes; n += pagesize)
+ {
+ a[n];
+ }
+ }
+ }
+ return ret;
+}
+
+result<map_handle> map_handle::map(section_handle &section, size_type bytes, extent_type offset, section_handle::flag _flag) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ result<map_handle> ret{map_handle(&section)};
+ native_handle_type &nativeh = ret.value()._v;
+ ULONG allocation = 0, prot;
+ PVOID addr = nullptr;
+ size_t commitsize = bytes;
+ LARGE_INTEGER _offset{};
+ _offset.QuadPart = offset;
+ SIZE_T _bytes = utils::round_up_to_page_size(bytes);
+ win32_map_flags(nativeh, allocation, prot, commitsize, section.backing() != nullptr, _flag);
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ NTSTATUS ntstat = NtMapViewOfSection(section.native_handle().h, GetCurrentProcess(), &addr, 0, commitsize, &_offset, &_bytes, ViewUnmap, allocation, prot);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ ret.value()._addr = static_cast<byte *>(addr);
+ ret.value()._offset = offset;
+ ret.value()._reservation = _bytes;
+ ret.value()._length = section.length().value() - offset;
+ // Make my handle borrow the native handle of my backing storage
+ ret.value()._v.h = section.backing_native_handle().h;
+
+ // Windows has no way of getting the kernel to prefault maps on creation, so ...
+ if(_flag & section_handle::flag::prefault)
+ {
+ // Start an asynchronous prefetch
+ buffer_type b{static_cast<byte *>(addr), _bytes};
+ (void) prefetch(span<buffer_type>(&b, 1));
+ // If this kernel doesn't support that API, manually poke every page in the new map
+ if(PrefetchVirtualMemory_ == nullptr)
+ {
+ size_t pagesize = utils::page_size();
+ volatile auto *a = static_cast<volatile char *>(addr);
+ for(size_t n = 0; n < _bytes; n += pagesize)
+ {
+ a[n];
+ }
+ }
+ }
+ return ret;
+}
+
+result<map_handle::size_type> map_handle::truncate(size_type newsize, bool /* unused */) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ newsize = win32_round_up_to_allocation_size(newsize);
+ if(newsize == _reservation)
+ {
+ return success();
+ }
+ // Is this VirtualAlloc() allocated memory?
+ if(_section == nullptr)
+ {
+ if(newsize == 0)
+ {
+ OUTCOME_TRYV(win32_release_allocations(_addr, _reservation, MEM_RELEASE));
+ _addr = nullptr;
+ _reservation = 0;
+ _length = 0;
+ return success();
+ }
+ if(newsize < _reservation)
+ {
+ // VirtualAlloc doesn't let us shrink regions, only free the whole thing. So if he tries
+ // to free any part of a region, it'll fail.
+ OUTCOME_TRYV(win32_release_allocations(_addr + newsize, _reservation - newsize, MEM_RELEASE));
+ _reservation = _length = newsize;
+ return _reservation;
+ }
+ // Try to allocate another region directly after this one
+ native_handle_type nativeh;
+ DWORD allocation = MEM_RESERVE | MEM_COMMIT, prot;
+ size_t commitsize;
+ win32_map_flags(nativeh, allocation, prot, commitsize, true, _flag);
+ if(!VirtualAlloc(_addr + _reservation, newsize - _reservation, allocation, prot))
+ {
+ return win32_error();
+ }
+ _reservation = _length = newsize;
+ return _reservation;
+ }
+
+ // So this must be file backed memory. Totally different APIs for that :)
+ OUTCOME_TRY(length, _section->length()); // length of the backing file
+ if(newsize < _reservation)
+ {
+ // If newsize isn't exactly a previous extension, this will fail, same as for the VirtualAlloc case
+ OUTCOME_TRYV(win32_maps_apply(_addr + newsize, _reservation - newsize, [](byte *addr, size_t /* unused */) -> result<void> {
+ NTSTATUS ntstat = NtUnmapViewOfSection(GetCurrentProcess(), addr);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return success();
+ }));
+ _reservation = newsize;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize; // length of backing, not reservation
+ return _reservation;
+ }
+ // Try to map an additional part of the section directly after this map
+ ULONG allocation = MEM_RESERVE, prot;
+ PVOID addr = _addr + _reservation;
+ size_t commitsize = newsize - _reservation;
+ LARGE_INTEGER offset{};
+ offset.QuadPart = _offset + _reservation;
+ SIZE_T _bytes = newsize - _reservation;
+ native_handle_type nativeh;
+ win32_map_flags(nativeh, allocation, prot, commitsize, _section->backing() != nullptr, _flag);
+ NTSTATUS ntstat = NtMapViewOfSection(_section->native_handle().h, GetCurrentProcess(), &addr, 0, commitsize, &offset, &_bytes, ViewUnmap, allocation, prot);
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ _reservation += _bytes;
+ _length = (length - _offset < newsize) ? (length - _offset) : newsize; // length of backing, not reservation
+ return _reservation;
+}
+
+result<map_handle::buffer_type> map_handle::commit(buffer_type region, section_handle::flag flag) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+ DWORD prot = 0;
+ if(flag == section_handle::flag::none)
+ {
+ OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> {
+ DWORD _ = 0;
+ if(VirtualProtect(addr, bytes, PAGE_NOACCESS, &_) == 0)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ return region;
+ }
+ if(flag & section_handle::flag::cow)
+ {
+ prot = PAGE_WRITECOPY;
+ }
+ else if(flag & section_handle::flag::write)
+ {
+ prot = PAGE_READWRITE;
+ }
+ else if(flag & section_handle::flag::read)
+ {
+ prot = PAGE_READONLY;
+ }
+ if(flag & section_handle::flag::execute)
+ {
+ prot = PAGE_EXECUTE;
+ }
+ region = utils::round_to_page_size(region);
+ OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [prot](byte *addr, size_t bytes) -> result<void> {
+ if(VirtualAlloc(addr, bytes, MEM_COMMIT, prot) == nullptr)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ return region;
+}
+
+result<map_handle::buffer_type> map_handle::decommit(buffer_type region) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+ region = utils::round_to_page_size(region);
+ OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> {
+ if(VirtualFree(addr, bytes, MEM_DECOMMIT) == 0)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ return region;
+}
+
+result<void> map_handle::zero_memory(buffer_type region) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+ // Alas, zero() will not work on mapped views on Windows :(, so memset to zero and call discard if available
+ memset(region.data, 0, region.len);
+ if((DiscardVirtualMemory_ != nullptr) && region.len >= utils::page_size())
+ {
+ region = utils::round_to_page_size(region);
+ if(region.len > 0)
+ {
+ OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> {
+ if(DiscardVirtualMemory_(addr, bytes) == 0)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ }
+ }
+ return success();
+}
+
+result<span<map_handle::buffer_type>> map_handle::prefetch(span<buffer_type> regions) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(0);
+ if(PrefetchVirtualMemory_ == nullptr)
+ {
+ return span<map_handle::buffer_type>();
+ }
+ auto wmre = reinterpret_cast<PWIN32_MEMORY_RANGE_ENTRY>(regions.data());
+ if(PrefetchVirtualMemory_(GetCurrentProcess(), regions.size(), wmre, 0) == 0)
+ {
+ return win32_error();
+ }
+ return regions;
+}
+
+result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ AFIO_LOG_FUNCTION_CALL(0);
+ region = utils::round_to_page_size(region);
+ if(region.data == nullptr)
+ {
+ return errc::invalid_argument;
+ }
+ // Windows does not support throwing away dirty pages on file backed maps
+ if((_section == nullptr) || (_section->backing() == nullptr))
+ {
+ // Win8's DiscardVirtualMemory is much faster if it's available
+ if(DiscardVirtualMemory_ != nullptr)
+ {
+ OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> {
+ if(DiscardVirtualMemory_(addr, bytes) == 0)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ return region;
+ }
+ // Else MEM_RESET will do
+ OUTCOME_TRYV(win32_maps_apply(region.data, region.len, [](byte *addr, size_t bytes) -> result<void> {
+ if(VirtualAlloc(addr, bytes, MEM_RESET, 0) == nullptr)
+ {
+ return win32_error();
+ }
+ return success();
+ }));
+ return region;
+ }
+ // We did nothing
+ region.len = 0;
+ return region;
+}
+
+map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buffers_type> reqs, deadline /*d*/) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ byte *addr = _addr + reqs.offset;
+ size_type togo = reqs.offset < _length ? static_cast<size_type>(_length - reqs.offset) : 0;
+ for(buffer_type &req : reqs.buffers)
+ {
+ if(togo != 0u)
+ {
+ req.data = addr;
+ if(req.len > togo)
+ {
+ req.len = togo;
+ }
+ addr += req.len;
+ togo -= req.len;
+ }
+ else
+ {
+ req.len = 0;
+ }
+ }
+ return reqs.buffers;
+}
+
+map_handle::io_result<map_handle::const_buffers_type> map_handle::write(io_request<const_buffers_type> reqs, deadline /*d*/) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ byte *addr = _addr + reqs.offset;
+ size_type togo = reqs.offset < _length ? static_cast<size_type>(_length - reqs.offset) : 0;
+ if(QUICKCPPLIB_NAMESPACE::signal_guard::signal_guard(QUICKCPPLIB_NAMESPACE::signal_guard::signalc::undefined_memory_access,
+ [&] {
+ for(const_buffer_type &req : reqs.buffers)
+ {
+ if(togo != 0u)
+ {
+ if(req.len > togo)
+ {
+ req.len = togo;
+ }
+ memcpy(addr, req.data, req.len);
+ req.data = addr;
+ addr += req.len;
+ togo -= req.len;
+ }
+ else
+ {
+ req.len = 0;
+ }
+ }
+ return false;
+ },
+ [&](const QUICKCPPLIB_NAMESPACE::signal_guard::raised_signal_info &_info) {
+ auto &info = const_cast<QUICKCPPLIB_NAMESPACE::signal_guard::raised_signal_info &>(_info);
+ auto *causingaddr = (byte *) info.address();
+ if(causingaddr < _addr || causingaddr >= (_addr + _length))
+ {
+ // Not caused by this map
+ QUICKCPPLIB_NAMESPACE::signal_guard::thread_local_raise_signal(info.signal(), info.raw_info(), info.raw_context());
+ }
+ return true;
+ }))
+ {
+ return errc::no_space_on_device;
+ }
+ return reqs.buffers;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/mapped_file_handle.ipp b/include/llfio/v2.0/detail/impl/windows/mapped_file_handle.ipp
new file mode 100644
index 00000000..5f1d815d
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/mapped_file_handle.ipp
@@ -0,0 +1,181 @@
+/* An mapped handle to a file
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../mapped_file_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<mapped_file_handle::size_type> mapped_file_handle::reserve(size_type reservation) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(reservation == 0)
+ {
+ OUTCOME_TRY(length, underlying_file_maximum_extent());
+ reservation = length;
+ }
+ reservation = utils::round_up_to_page_size(reservation);
+ if(!_sh.is_valid())
+ {
+ // Section must match read/write of file, as otherwise map reservation doesn't work on Windows
+ section_handle::flag sectionflags = section_handle::flag::singleton;
+ if(this->is_readable())
+ sectionflags |= section_handle::flag::read;
+ if(this->is_writable())
+ sectionflags |= section_handle::flag::write;
+ OUTCOME_TRY(sh, section_handle::section(*this, 0, sectionflags));
+ _sh = std::move(sh);
+ }
+ if(_mh.is_valid() && reservation == _mh.length())
+ {
+ return reservation;
+ }
+ section_handle::flag mapflags = section_handle::flag::read;
+ // Reserve the full reservation in address space
+ if(this->is_writable())
+ {
+ mapflags |= section_handle::flag::nocommit | section_handle::flag::write;
+ }
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRY(mh, map_handle::map(_sh, reservation, 0, mapflags));
+ _mh = std::move(mh);
+ _reservation = reservation;
+ return reservation;
+}
+
+result<void> mapped_file_handle::close() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_mh.is_valid())
+ {
+ OUTCOME_TRYV(_mh.close());
+ }
+ if(_sh.is_valid())
+ {
+ OUTCOME_TRYV(_sh.close());
+ }
+ return file_handle::close();
+}
+native_handle_type mapped_file_handle::release() noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_mh.is_valid())
+ {
+ (void) _mh.close();
+ }
+ if(_sh.is_valid())
+ {
+ (void) _sh.close();
+ }
+ return file_handle::release();
+}
+
+result<mapped_file_handle::extent_type> mapped_file_handle::truncate(extent_type newsize) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(this);
+ // Release all maps and sections and truncate the backing file to zero
+ if(newsize == 0)
+ {
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRYV(_sh.close());
+ return file_handle::truncate(newsize);
+ }
+ if(!_sh.is_valid())
+ {
+ OUTCOME_TRY(ret, file_handle::truncate(newsize));
+ // Reserve now we have resized, it'll create a new section for the new size
+ OUTCOME_TRYV(reserve(_reservation));
+ return ret;
+ }
+ // Ask the section what size it is. If multiple AFIO processes are using the same file,
+ // because the section is a singleton based on the canonical path of the file, another
+ // AFIO in another process may have already resized the section for us in which case
+ // we can skip doing work now.
+ OUTCOME_TRY(size, _sh.length());
+ if(size != newsize)
+ {
+ // If we are making this smaller, we must destroy the map and section first
+ if(newsize < size)
+ {
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRYV(_sh.close());
+ // This will fail on Windows if any other processes are holding a section on this file
+ OUTCOME_TRY(ret, file_handle::truncate(newsize));
+ // Put the reservation and map back
+ OUTCOME_TRYV(reserve(_reservation));
+ return ret;
+ }
+ // Otherwise resize the file upwards, then the section.
+ OUTCOME_TRYV(file_handle::truncate(newsize));
+ // On Windows, resizing the section upwards maps the added extents into memory in all
+ // processes using this singleton section
+ OUTCOME_TRYV(_sh.truncate(newsize));
+ // Have we exceeded the reservation? If so, reserve a new reservation which will recreate the map.
+ if(newsize > _reservation)
+ {
+ OUTCOME_TRY(ret, reserve(newsize));
+ return ret;
+ }
+ size = newsize;
+ }
+ // Adjust the map to reflect the new size of the section
+ _mh._length = size;
+ return newsize;
+}
+
+result<mapped_file_handle::extent_type> mapped_file_handle::update_map() noexcept
+{
+ OUTCOME_TRY(length, underlying_file_maximum_extent());
+ if(length > _reservation)
+ {
+ // This API never exceeds the reservation
+ length = _reservation;
+ }
+ if(length == 0)
+ {
+ OUTCOME_TRYV(_mh.close());
+ OUTCOME_TRYV(_sh.close());
+ return length;
+ }
+ if(!_sh.is_valid())
+ {
+ OUTCOME_TRYV(reserve(_reservation));
+ return length;
+ }
+ OUTCOME_TRY(size, _sh.length());
+ // Section may have become bigger than our reservation ...
+ if(size >= length)
+ {
+ // Section is already the same size as the file, or is as big as it can go
+ _mh._length = length;
+ return length;
+ }
+ // Nobody appears to have extended the section to match the file yet
+ OUTCOME_TRYV(_sh.truncate(length));
+ // Adjust the map to reflect the new size of the section
+ _mh._length = length;
+ return length;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/path_discovery.ipp b/include/llfio/v2.0/detail/impl/windows/path_discovery.ipp
new file mode 100644
index 00000000..992992ed
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/path_discovery.ipp
@@ -0,0 +1,126 @@
+/* Discovery of various useful filesystem paths
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_PATH_DISCOVERY_INCLUDING
+#error Must be included by ../path_discovery.ipp only
+#endif
+
+#include "import.hpp"
+
+#include <ShlObj.h>
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace path_discovery
+{
+ std::vector<std::pair<discovered_path::source_type, _store::_discovered_path>> _all_temporary_directories()
+ {
+ std::vector<std::pair<discovered_path::source_type, _store::_discovered_path>> ret;
+ filesystem::path::string_type buffer;
+ buffer.resize(32768);
+ // Only observe environment variables if not a SUID or SGID situation
+ if(!running_under_suid_gid())
+ {
+ // We don't use it as it's nearly useless, but GetTempPath() returns one of (in order):
+ // %TMP%, %TEMP%, %USERPROFILE%, GetWindowsDirectory()\Temp
+ static const wchar_t *variables[] = {L"TMP", L"TEMP", L"LOCALAPPDATA", L"USERPROFILE"};
+ for(auto &variable : variables)
+ {
+ buffer.resize(32768);
+ DWORD len = GetEnvironmentVariable(variable, const_cast<LPWSTR>(buffer.data()), static_cast<DWORD>(buffer.size()));
+ if((len != 0u) && len < buffer.size())
+ {
+ buffer.resize(len);
+ if(variable[0] == 'L')
+ {
+ buffer.append(L"\\Temp");
+ ret.emplace_back(discovered_path::source_type::environment, buffer);
+ buffer.resize(len);
+ }
+ else if(variable[0] == 'U')
+ {
+ buffer.append(L"\\AppData\\Local\\Temp");
+ ret.emplace_back(discovered_path::source_type::environment, buffer);
+ buffer.resize(len);
+ buffer.append(L"\\Local Settings\\Temp");
+ ret.emplace_back(discovered_path::source_type::environment, buffer);
+ buffer.resize(len);
+ }
+ else
+ {
+ ret.emplace_back(discovered_path::source_type::environment, buffer);
+ }
+ }
+ }
+ }
+
+ // Ask the shell
+ {
+ PWSTR out;
+ if(S_OK == SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &out))
+ {
+ buffer = out;
+ CoTaskMemFree(out);
+ buffer.append(L"\\Temp");
+ ret.emplace_back(discovered_path::source_type::system, buffer);
+ }
+ if(S_OK == SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &out))
+ {
+ buffer = out;
+ CoTaskMemFree(out);
+ auto len = buffer.size();
+ buffer.append(L"\\AppData\\Local\\Temp");
+ ret.emplace_back(discovered_path::source_type::system, buffer);
+ buffer.resize(len);
+ buffer.append(L"\\Local Settings\\Temp");
+ ret.emplace_back(discovered_path::source_type::system, buffer);
+ }
+ }
+
+ // Finally if everything earlier failed e.g. if our environment block is zeroed,
+ // fall back to Win3.1 era "the Windows directory" which definitely won't be
+ // C:\Windows nowadays
+ buffer.resize(32768);
+ DWORD len = GetWindowsDirectory(const_cast<LPWSTR>(buffer.data()), static_cast<UINT>(buffer.size()));
+ if((len != 0u) && len < buffer.size())
+ {
+ buffer.resize(len);
+ buffer.append(L"\\Temp");
+ ret.emplace_back(discovered_path::source_type::system, buffer);
+ }
+ // And if even that fails, try good old %SYSTEMDRIVE%\Temp
+ buffer.resize(32768);
+ len = GetSystemWindowsDirectory(const_cast<LPWSTR>(buffer.data()), static_cast<UINT>(buffer.size()));
+ if((len != 0u) && len < buffer.size())
+ {
+ buffer.resize(len);
+ buffer.resize(buffer.find_last_of(L'\\'));
+ buffer.append(L"\\Temp");
+ ret.emplace_back(discovered_path::source_type::hardcoded, buffer);
+ }
+ return ret;
+ }
+} // namespace path_discovery
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/path_handle.ipp b/include/llfio/v2.0/detail/impl/windows/path_handle.ipp
new file mode 100644
index 00000000..218339ca
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/path_handle.ipp
@@ -0,0 +1,101 @@
+/* A handle to a filesystem location
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Jul 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../path_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+result<path_handle> path_handle::path(const path_handle &base, path_handle::path_view_type path) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ result<path_handle> ret{path_handle(native_handle_type())};
+ native_handle_type &nativeh = ret.value()._v;
+ AFIO_LOG_FUNCTION_CALL(&ret);
+ nativeh.behaviour |= native_handle_type::disposition::directory;
+ DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ // Open directory with no access requested, this is much faster than asking for access
+ OUTCOME_TRY(access, access_mask_from_handle_mode(nativeh, mode::none, flag::none));
+ OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(nativeh, caching::all, flag::none));
+ /* It is super important that we remove the DELETE permission for directories as otherwise relative renames
+ will always fail due to an unfortunate design choice by Microsoft.
+ */
+ access &= ~DELETE;
+ if(base.is_valid() || path.is_ntpath())
+ {
+ DWORD creatdisp = 0x00000001 /*FILE_OPEN*/;
+ attribs &= 0x00ffffff; // the real attributes only, not the win32 flags
+ OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(nativeh, caching::all, flag::none));
+ ntflags |= 0x01 /*FILE_DIRECTORY_FILE*/; // required to open a directory
+ IO_STATUS_BLOCK isb = make_iostatus();
+
+ path_view::c_str zpath(path, true);
+ UNICODE_STRING _path{};
+ _path.Buffer = const_cast<wchar_t *>(zpath.buffer);
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t);
+ if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\')
+ {
+ _path.Buffer += 3;
+ _path.Length -= 3 * sizeof(wchar_t);
+ _path.MaximumLength -= 3 * sizeof(wchar_t);
+ }
+
+ OBJECT_ATTRIBUTES oa{};
+ memset(&oa, 0, sizeof(oa));
+ oa.Length = sizeof(OBJECT_ATTRIBUTES);
+ oa.ObjectName = &_path;
+ oa.RootDirectory = base.is_valid() ? base.native_handle().h : nullptr;
+ oa.Attributes = 0x40 /*OBJ_CASE_INSENSITIVE*/;
+ // if(!!(flags & file_flags::int_opening_link))
+ // oa.Attributes|=0x100/*OBJ_OPENLINK*/;
+
+ LARGE_INTEGER AllocationSize{};
+ memset(&AllocationSize, 0, sizeof(AllocationSize));
+ NTSTATUS ntstat = NtCreateFile(&nativeh.h, access, &oa, &isb, &AllocationSize, attribs, fileshare, creatdisp, ntflags, nullptr, 0);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(nativeh.h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ else
+ {
+ DWORD creation = OPEN_EXISTING;
+ attribs |= FILE_FLAG_BACKUP_SEMANTICS; // required to open a directory
+ path_view::c_str zpath(path, false);
+ if(INVALID_HANDLE_VALUE == (nativeh.h = CreateFileW_(zpath.buffer, access, fileshare, nullptr, creation, attribs, nullptr))) // NOLINT
+ {
+ DWORD errcode = GetLastError();
+ // assert(false);
+ return win32_error(errcode);
+ }
+ }
+ return ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/path_view.ipp b/include/llfio/v2.0/detail/impl/windows/path_view.ipp
new file mode 100644
index 00000000..7439a020
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/path_view.ipp
@@ -0,0 +1,55 @@
+/* A view of a path to something
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Jul 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../path_view.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC void path_view::c_str::_from_utf8(const path_view &view) noexcept
+{
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ ULONG written = 0;
+ NTSTATUS ntstat = RtlUTF8ToUnicodeN(_buffer, static_cast<ULONG>(sizeof(_buffer) - sizeof(wchar_t)), &written, view._state._utf8.data(), static_cast<ULONG>(view._state._utf8.size()));
+ if(ntstat < 0)
+ {
+ AFIO_LOG_FATAL(ntstat, ntkernel_error(ntstat).message().c_str());
+ abort();
+ }
+ length = static_cast<uint16_t>(written / sizeof(wchar_t));
+ _buffer[length] = 0;
+ wchar_t *p = _buffer;
+ do
+ {
+ p = wcschr(p, '/');
+ if(p != nullptr)
+ {
+ *p = '\\';
+ }
+ } while(p != nullptr);
+ buffer = _buffer;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/stat.ipp b/include/llfio/v2.0/detail/impl/windows/stat.ipp
new file mode 100644
index 00000000..aa337be5
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/stat.ipp
@@ -0,0 +1,224 @@
+/* Information about a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (4 commits)
+File Created: Apr 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "../../../stat.hpp"
+#include "import.hpp"
+
+#include <winioctl.h> // for DeviceIoControl codes
+
+AFIO_V2_NAMESPACE_BEGIN
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> stat_t::fill(const handle &h, stat_t::want wanted) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(&h);
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ alignas(8) wchar_t buffer[32769];
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat;
+ size_t ret = 0;
+
+ FILE_ALL_INFORMATION &fai = *reinterpret_cast<FILE_ALL_INFORMATION *>(buffer);
+ FILE_FS_SECTOR_SIZE_INFORMATION ffssi{};
+ memset(&ffssi, 0, sizeof(ffssi));
+ bool needInternal = !!(wanted & want::ino);
+ bool needBasic = (!!(wanted & want::type) || !!(wanted & want::atim) || !!(wanted & want::mtim) || !!(wanted & want::ctim) || !!(wanted & want::birthtim) || !!(wanted & want::sparse) || !!(wanted & want::compressed) || !!(wanted & want::reparse_point));
+ bool needStandard = (!!(wanted & want::nlink) || !!(wanted & want::size) || !!(wanted & want::allocated) || !!(wanted & want::blocks));
+ // It's not widely known that the NT kernel supplies a stat() equivalent i.e. get me everything in a single syscall
+ // However fetching FileAlignmentInformation which comes with FILE_ALL_INFORMATION is slow as it touches the device driver,
+ // so only use if we need more than one item
+ if((static_cast<int>(needInternal) + static_cast<int>(needBasic) + static_cast<int>(needStandard)) >= 2)
+ {
+ ntstat = NtQueryInformationFile(h.native_handle().h, &isb, &fai, sizeof(buffer), FileAllInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ else
+ {
+ if(needInternal)
+ {
+ ntstat = NtQueryInformationFile(h.native_handle().h, &isb, &fai.InternalInformation, sizeof(fai.InternalInformation), FileInternalInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(needBasic)
+ {
+ isb.Status = -1;
+ ntstat = NtQueryInformationFile(h.native_handle().h, &isb, &fai.BasicInformation, sizeof(fai.BasicInformation), FileBasicInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(needStandard)
+ {
+ isb.Status = -1;
+ ntstat = NtQueryInformationFile(h.native_handle().h, &isb, &fai.StandardInformation, sizeof(fai.StandardInformation), FileStandardInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ }
+ if((wanted & want::blocks) || (wanted & want::blksize))
+ {
+ isb.Status = -1;
+ ntstat = NtQueryVolumeInformationFile(h.native_handle().h, &isb, &ffssi, sizeof(ffssi), FileFsSectorSizeInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat < 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+
+ if(wanted & want::dev)
+ {
+ // This is a bit hacky, but we just need a unique device number
+ alignas(8) wchar_t buffer_[32769];
+ DWORD len = GetFinalPathNameByHandle(h.native_handle().h, buffer_, sizeof(buffer_) / sizeof(*buffer_), VOLUME_NAME_NT);
+ if((len == 0u) || len >= sizeof(buffer_) / sizeof(*buffer_))
+ {
+ return win32_error();
+ }
+ buffer_[len] = 0;
+ if(memcmp(buffer_, L"\\Device\\HarddiskVolume", 44) != 0)
+ {
+ return errc::illegal_byte_sequence;
+ }
+ // buffer_ should look like \Device\HarddiskVolumeX, so our number is from +22 onwards
+ st_dev = _wtoi(buffer_ + 22);
+ ++ret;
+ }
+ if(wanted & want::ino)
+ {
+ st_ino = fai.InternalInformation.IndexNumber.QuadPart;
+ ++ret;
+ }
+ if(wanted & want::type)
+ {
+ ULONG ReparsePointTag = fai.EaInformation.ReparsePointTag;
+ // We need to get its reparse tag to see if it's a symlink
+ if(((fai.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u) && (ReparsePointTag == 0u))
+ {
+ alignas(8) char buffer_[sizeof(REPARSE_DATA_BUFFER) + 32769];
+ DWORD written = 0;
+ auto *rpd = reinterpret_cast<REPARSE_DATA_BUFFER *>(buffer_);
+ memset(rpd, 0, sizeof(*rpd));
+ if(DeviceIoControl(h.native_handle().h, FSCTL_GET_REPARSE_POINT, nullptr, 0, rpd, sizeof(buffer_), &written, nullptr) == 0)
+ {
+ return win32_error();
+ }
+ ReparsePointTag = rpd->ReparseTag;
+ }
+ st_type = windows_nt_kernel::to_st_type(fai.BasicInformation.FileAttributes, ReparsePointTag);
+ ++ret;
+ }
+ if(wanted & want::nlink)
+ {
+ st_nlink = static_cast<int16_t>(fai.StandardInformation.NumberOfLinks);
+ ++ret;
+ }
+ if(wanted & want::atim)
+ {
+ st_atim = to_timepoint(fai.BasicInformation.LastAccessTime);
+ ++ret;
+ }
+ if(wanted & want::mtim)
+ {
+ st_mtim = to_timepoint(fai.BasicInformation.LastWriteTime);
+ ++ret;
+ }
+ if(wanted & want::ctim)
+ {
+ st_ctim = to_timepoint(fai.BasicInformation.ChangeTime);
+ ++ret;
+ }
+ if(wanted & want::size)
+ {
+ st_size = fai.StandardInformation.EndOfFile.QuadPart;
+ ++ret;
+ }
+ if(wanted & want::allocated)
+ {
+ st_allocated = fai.StandardInformation.AllocationSize.QuadPart;
+ ++ret;
+ }
+ if(wanted & want::blocks)
+ {
+ st_blocks = fai.StandardInformation.AllocationSize.QuadPart / ffssi.PhysicalBytesPerSectorForPerformance;
+ ++ret;
+ }
+ if(wanted & want::blksize)
+ {
+ st_blksize = static_cast<uint16_t>(ffssi.PhysicalBytesPerSectorForPerformance);
+ ++ret;
+ }
+ if(wanted & want::birthtim)
+ {
+ st_birthtim = to_timepoint(fai.BasicInformation.CreationTime);
+ ++ret;
+ }
+ if(wanted & want::sparse)
+ {
+ st_sparse = static_cast<unsigned int>((fai.BasicInformation.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0u);
+ ++ret;
+ }
+ if(wanted & want::compressed)
+ {
+ st_compressed = static_cast<unsigned int>((fai.BasicInformation.FileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0u);
+ ++ret;
+ }
+ if(wanted & want::reparse_point)
+ {
+ st_reparse_point = static_cast<unsigned int>((fai.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0u);
+ ++ret;
+ }
+ return ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/statfs.ipp b/include/llfio/v2.0/detail/impl/windows/statfs.ipp
new file mode 100644
index 00000000..1bb85e26
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/statfs.ipp
@@ -0,0 +1,205 @@
+/* Information about the volume storing a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (6 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "../../../statfs.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> statfs_t::fill(const handle &h, statfs_t::want wanted) noexcept
+{
+ AFIO_LOG_FUNCTION_CALL(&h);
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ alignas(8) wchar_t buffer[32769];
+ IO_STATUS_BLOCK isb = make_iostatus();
+ NTSTATUS ntstat;
+ size_t ret = 0;
+ if((wanted & want::flags) || (wanted & want::namemax) || (wanted & want::fstypename))
+ {
+ auto *ffai = reinterpret_cast<FILE_FS_ATTRIBUTE_INFORMATION *>(buffer);
+ isb.Status = -1;
+ ntstat = NtQueryVolumeInformationFile(h.native_handle().h, &isb, ffai, sizeof(buffer), FileFsAttributeInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ if(wanted & want::flags)
+ {
+ f_flags.rdonly = static_cast<uint32_t>((ffai->FileSystemAttributes & FILE_READ_ONLY_VOLUME) != 0u);
+ f_flags.acls = static_cast<uint32_t>((ffai->FileSystemAttributes & FILE_PERSISTENT_ACLS) != 0u);
+ f_flags.xattr = static_cast<uint32_t>((ffai->FileSystemAttributes & FILE_NAMED_STREAMS) != 0u);
+ f_flags.compression = static_cast<uint32_t>((ffai->FileSystemAttributes & FILE_VOLUME_IS_COMPRESSED) != 0u);
+ f_flags.extents = static_cast<uint32_t>((ffai->FileSystemAttributes & FILE_SUPPORTS_SPARSE_FILES) != 0u);
+ f_flags.filecompression = static_cast<uint32_t>((ffai->FileSystemAttributes & FILE_FILE_COMPRESSION) != 0u);
+ ++ret;
+ }
+ if(wanted & want::namemax)
+ {
+ f_namemax = ffai->MaximumComponentNameLength;
+ ++ret;
+ }
+ if(wanted & want::fstypename)
+ {
+ f_fstypename.resize(ffai->FileSystemNameLength / sizeof(wchar_t));
+ for(size_t n = 0; n < ffai->FileSystemNameLength / sizeof(wchar_t); n++)
+ {
+ f_fstypename[n] = static_cast<char>(ffai->FileSystemName[n]);
+ }
+ ++ret;
+ }
+ }
+ if((wanted & want::bsize) || (wanted & want::blocks) || (wanted & want::bfree) || (wanted & want::bavail))
+ {
+ auto *fffsi = reinterpret_cast<FILE_FS_FULL_SIZE_INFORMATION *>(buffer);
+ isb.Status = -1;
+ ntstat = NtQueryVolumeInformationFile(h.native_handle().h, &isb, fffsi, sizeof(buffer), FileFsFullSizeInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ if(wanted & want::bsize)
+ {
+ f_bsize = fffsi->BytesPerSector * fffsi->SectorsPerAllocationUnit;
+ ++ret;
+ }
+ if(wanted & want::blocks)
+ {
+ f_blocks = fffsi->TotalAllocationUnits.QuadPart;
+ ++ret;
+ }
+ if(wanted & want::bfree)
+ {
+ f_bfree = fffsi->ActualAvailableAllocationUnits.QuadPart;
+ ++ret;
+ }
+ if(wanted & want::bavail)
+ {
+ f_bavail = fffsi->CallerAvailableAllocationUnits.QuadPart;
+ ++ret;
+ }
+ }
+ if(wanted & want::fsid)
+ {
+ auto *ffoi = reinterpret_cast<FILE_FS_OBJECTID_INFORMATION *>(buffer);
+ isb.Status = -1;
+ ntstat = NtQueryVolumeInformationFile(h.native_handle().h, &isb, ffoi, sizeof(buffer), FileFsObjectIdInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(0 /*STATUS_SUCCESS*/ == ntstat)
+ {
+ // FAT32 doesn't support filing system id, so sink error
+ memcpy(&f_fsid, ffoi->ObjectId, sizeof(f_fsid));
+ ++ret;
+ }
+ }
+ if(wanted & want::iosize)
+ {
+ auto *ffssi = reinterpret_cast<FILE_FS_SECTOR_SIZE_INFORMATION *>(buffer);
+ isb.Status = -1;
+ ntstat = NtQueryVolumeInformationFile(h.native_handle().h, &isb, ffssi, sizeof(buffer), FileFsSectorSizeInformation);
+ if(STATUS_PENDING == ntstat)
+ {
+ ntstat = ntwait(h.native_handle().h, isb, deadline());
+ }
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ f_iosize = ffssi->PhysicalBytesPerSectorForPerformance;
+ ++ret;
+ }
+ if((wanted & want::mntfromname) || (wanted & want::mntonname))
+ {
+ // Irrespective we need the path before figuring out the mounted device
+ alignas(8) wchar_t buffer2[32769];
+ for(;;)
+ {
+ DWORD pathlen = GetFinalPathNameByHandle(h.native_handle().h, buffer2, sizeof(buffer2) / sizeof(*buffer2), FILE_NAME_OPENED | VOLUME_NAME_NONE);
+ if((pathlen == 0u) || pathlen >= sizeof(buffer2) / sizeof(*buffer2))
+ {
+ return win32_error();
+ }
+ buffer2[pathlen] = 0;
+ if(wanted & want::mntfromname)
+ {
+ DWORD len = GetFinalPathNameByHandle(h.native_handle().h, buffer, sizeof(buffer) / sizeof(*buffer), FILE_NAME_OPENED | VOLUME_NAME_NT);
+ if((len == 0u) || len >= sizeof(buffer) / sizeof(*buffer))
+ {
+ return win32_error();
+ }
+ buffer[len] = 0;
+ if(memcmp(buffer2, buffer + len - pathlen, pathlen) != 0)
+ {
+ continue; // path changed
+ }
+ len -= pathlen;
+ // buffer should look like \Device\HarddiskVolumeX
+ if(memcmp(buffer, L"\\Device\\HarddiskVolume", 44) != 0)
+ {
+ return errc::illegal_byte_sequence;
+ }
+ f_mntfromname.reserve(len + 3);
+ f_mntfromname.assign("\\!!"); // escape prefix for NT kernel path
+ for(size_t n = 0; n < len; n++)
+ {
+ f_mntfromname.push_back(static_cast<char>(buffer[n]));
+ }
+ ++ret;
+ wanted &= ~want::mntfromname;
+ }
+ if(wanted & want::mntonname)
+ {
+ DWORD len = GetFinalPathNameByHandle(h.native_handle().h, buffer, sizeof(buffer) / sizeof(*buffer), FILE_NAME_OPENED | VOLUME_NAME_DOS);
+ if((len == 0u) || len >= sizeof(buffer) / sizeof(*buffer))
+ {
+ return win32_error();
+ }
+ buffer[len] = 0;
+ if(memcmp(buffer2, buffer + len - pathlen, pathlen) != 0)
+ {
+ continue; // path changed
+ }
+ len -= pathlen;
+ f_mntonname = decltype(f_mntonname)::string_type(buffer, len);
+ ++ret;
+ }
+ break;
+ }
+ }
+ return ret;
+}
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/storage_profile.ipp b/include/llfio/v2.0/detail/impl/windows/storage_profile.ipp
new file mode 100644
index 00000000..96b58814
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/storage_profile.ipp
@@ -0,0 +1,440 @@
+/* A profile of an OS and filing system
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../handle.hpp"
+#include "../../../storage_profile.hpp"
+#include "import.hpp"
+
+#include <winioctl.h>
+#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+#include <intrin.h> // for __cpuid
+#endif
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace storage_profile
+{
+ namespace system
+ {
+// OS name, version
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 6387) // MSVC sanitiser warns that GetModuleHandleA() might fail (hah!)
+#endif
+ outcome<void> os(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ static std::string os_name, os_ver;
+ if(!os_name.empty())
+ {
+ sp.os_name.value = os_name;
+ sp.os_ver.value = os_ver;
+ }
+ else
+ {
+ try
+ {
+ using std::to_string;
+ RTL_OSVERSIONINFOW ovi{};
+ memset(&ovi, 0, sizeof(ovi));
+ ovi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW);
+ // GetVersionEx() is no longer useful since Win8.1
+ using RtlGetVersion_t = LONG (*)(PRTL_OSVERSIONINFOW);
+ static RtlGetVersion_t RtlGetVersion;
+ if(RtlGetVersion == nullptr)
+ {
+ RtlGetVersion = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "RtlGetVersion"));
+ }
+ if(RtlGetVersion == nullptr)
+ {
+ return win32_error();
+ }
+ RtlGetVersion(&ovi);
+ sp.os_name.value = "Microsoft Windows ";
+ sp.os_name.value.append(ovi.dwPlatformId == VER_PLATFORM_WIN32_NT ? "NT" : "Unknown");
+ sp.os_ver.value.append(to_string(ovi.dwMajorVersion) + "." + to_string(ovi.dwMinorVersion) + "." + to_string(ovi.dwBuildNumber));
+ os_name = sp.os_name.value;
+ os_ver = sp.os_ver.value;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ }
+ return success();
+ }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ // CPU name, architecture, physical cores
+ outcome<void> cpu(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ static std::string cpu_name, cpu_architecture;
+ static unsigned cpu_physical_cores;
+ if(!cpu_name.empty())
+ {
+ sp.cpu_name.value = cpu_name;
+ sp.cpu_architecture.value = cpu_architecture;
+ sp.cpu_physical_cores.value = cpu_physical_cores;
+ }
+ else
+ {
+ try
+ {
+ SYSTEM_INFO si{};
+ memset(&si, 0, sizeof(si));
+ GetNativeSystemInfo(&si);
+ switch(si.wProcessorArchitecture)
+ {
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ sp.cpu_name.value = sp.cpu_architecture.value = "x64";
+ break;
+ case PROCESSOR_ARCHITECTURE_ARM:
+ sp.cpu_name.value = sp.cpu_architecture.value = "ARM";
+ break;
+ case PROCESSOR_ARCHITECTURE_IA64:
+ sp.cpu_name.value = sp.cpu_architecture.value = "IA64";
+ break;
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ sp.cpu_name.value = sp.cpu_architecture.value = "x86";
+ break;
+ default:
+ sp.cpu_name.value = sp.cpu_architecture.value = "unknown";
+ break;
+ }
+ {
+ DWORD size = 0;
+
+ GetLogicalProcessorInformation(nullptr, &size);
+ if(ERROR_INSUFFICIENT_BUFFER != GetLastError())
+ {
+ return win32_error();
+ }
+
+ std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer(size);
+ if(GetLogicalProcessorInformation(&buffer.front(), &size) == FALSE)
+ {
+ return win32_error();
+ }
+
+ const size_t Elements = size / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
+
+ sp.cpu_physical_cores.value = 0;
+ for(size_t i = 0; i < Elements; ++i)
+ {
+ if(buffer[i].Relationship == RelationProcessorCore)
+ {
+ ++sp.cpu_physical_cores.value;
+ }
+ }
+ }
+#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+// We can do a much better CPU name on x86/x64
+#if defined(__clang__)
+ auto __cpuid = [](const int *cpuInfo, int func) { __asm__ __volatile__("cpuid\n\t" : "=a"(cpuInfo[0]), "=b"(cpuInfo[1]), "=c"(cpuInfo[2]), "=d"(cpuInfo[3]) : "0"(func)); };
+#endif
+ sp.cpu_name.value.clear();
+ {
+ char buffer[62];
+ memset(buffer, 32, 62);
+ int nBuff[4];
+ __cpuid(nBuff, 0);
+ *reinterpret_cast<int *>(&buffer[0]) = nBuff[1];
+ *reinterpret_cast<int *>(&buffer[4]) = nBuff[3];
+ *reinterpret_cast<int *>(&buffer[8]) = nBuff[2];
+
+ // Do we have a brand string?
+ __cpuid(nBuff, 0x80000000);
+ if(static_cast<unsigned>(nBuff[0]) >= 0x80000004)
+ {
+ __cpuid(reinterpret_cast<int *>(&buffer[14]), 0x80000002);
+ __cpuid(reinterpret_cast<int *>(&buffer[30]), 0x80000003);
+ __cpuid(reinterpret_cast<int *>(&buffer[46]), 0x80000004);
+ }
+ else
+ {
+ memcpy(&buffer[14], "unbranded", 10);
+ }
+
+ // Trim string
+ for(size_t n = 0; n < 62; n++)
+ {
+ if((n == 0u) || buffer[n] != 32 || buffer[n - 1] != 32)
+ {
+ if(buffer[n] != 0)
+ {
+ sp.cpu_name.value.push_back(buffer[n]);
+ }
+ }
+ }
+ }
+#endif
+ cpu_name = sp.cpu_name.value;
+ cpu_architecture = sp.cpu_architecture.value;
+ cpu_physical_cores = sp.cpu_physical_cores.value;
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ }
+ return success();
+ }
+ namespace windows
+ {
+ outcome<void> _mem(storage_profile &sp, file_handle & /*unused*/) noexcept
+ {
+ MEMORYSTATUSEX ms{};
+ memset(&ms, 0, sizeof(ms));
+ ms.dwLength = sizeof(MEMORYSTATUSEX);
+ GlobalMemoryStatusEx(&ms);
+ sp.mem_quantity.value = static_cast<unsigned long long>(ms.ullTotalPhys);
+ sp.mem_in_use.value = static_cast<float>(ms.ullTotalPhys - ms.ullAvailPhys) / ms.ullTotalPhys;
+ return success();
+ }
+ } // namespace windows
+ } // namespace system
+ namespace storage
+ {
+ namespace windows
+ {
+ // Controller type, max transfer, max buffers. Device name, size
+ outcome<void> _device(storage_profile &sp, file_handle & /*unused*/, const std::string &_mntfromname, const std::string & /*fstypename*/) noexcept
+ {
+ try
+ {
+ alignas(8) wchar_t buffer[32769];
+ // Firstly open a handle to the volume
+ OUTCOME_TRY(volumeh, file_handle::file({}, _mntfromname, handle::mode::none, handle::creation::open_existing, handle::caching::only_metadata));
+ STORAGE_PROPERTY_QUERY spq{};
+ memset(&spq, 0, sizeof(spq));
+ spq.PropertyId = StorageAdapterProperty;
+ spq.QueryType = PropertyStandardQuery;
+ auto *sad = reinterpret_cast<STORAGE_ADAPTER_DESCRIPTOR *>(buffer);
+ OVERLAPPED ol{};
+ memset(&ol, 0, sizeof(ol));
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ if(DeviceIoControl(volumeh.native_handle().h, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(spq), sad, sizeof(buffer), nullptr, &ol) == 0)
+ {
+ if(ERROR_IO_PENDING == GetLastError())
+ {
+ NTSTATUS ntstat = ntwait(volumeh.native_handle().h, ol, deadline());
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(ERROR_SUCCESS != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ switch(sad->BusType)
+ {
+ case BusTypeScsi:
+ sp.controller_type.value = "SCSI";
+ break;
+ case BusTypeAtapi:
+ sp.controller_type.value = "ATAPI";
+ break;
+ case BusTypeAta:
+ sp.controller_type.value = "ATA";
+ break;
+ case BusType1394:
+ sp.controller_type.value = "1394";
+ break;
+ case BusTypeSsa:
+ sp.controller_type.value = "SSA";
+ break;
+ case BusTypeFibre:
+ sp.controller_type.value = "Fibre";
+ break;
+ case BusTypeUsb:
+ sp.controller_type.value = "USB";
+ break;
+ case BusTypeRAID:
+ sp.controller_type.value = "RAID";
+ break;
+ case BusTypeiScsi:
+ sp.controller_type.value = "iSCSI";
+ break;
+ case BusTypeSas:
+ sp.controller_type.value = "SAS";
+ break;
+ case BusTypeSata:
+ sp.controller_type.value = "SATA";
+ break;
+ case BusTypeSd:
+ sp.controller_type.value = "SD";
+ break;
+ case BusTypeMmc:
+ sp.controller_type.value = "MMC";
+ break;
+ case BusTypeVirtual:
+ sp.controller_type.value = "Virtual";
+ break;
+ case BusTypeFileBackedVirtual:
+ sp.controller_type.value = "File Backed Virtual";
+ break;
+ case BusTypeSpaces:
+ sp.controller_type.value = "Storage Spaces";
+ break;
+ case BusTypeNvme:
+ sp.controller_type.value = "NVMe";
+ break;
+ case BusTypeSCM:
+ sp.controller_type.value = "Storage Class Memory";
+ break;
+ default:
+ sp.controller_type.value = "unknown";
+ break;
+ }
+ sp.controller_max_transfer.value = sad->MaximumTransferLength;
+ sp.controller_max_buffers.value = sad->MaximumPhysicalPages;
+
+ // Now ask the volume what physical disks it spans
+ auto *vde = reinterpret_cast<VOLUME_DISK_EXTENTS *>(buffer);
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ if(DeviceIoControl(volumeh.native_handle().h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, nullptr, 0, vde, sizeof(buffer), nullptr, &ol) == 0)
+ {
+ if(ERROR_IO_PENDING == GetLastError())
+ {
+ NTSTATUS ntstat = ntwait(volumeh.native_handle().h, ol, deadline());
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(ERROR_SUCCESS != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ DWORD disk_extents = vde->NumberOfDiskExtents;
+ sp.device_name.value.clear();
+ if(disk_extents > 0)
+ {
+ // For now we only care about the first physical device
+ alignas(8) wchar_t physicaldrivename[32769] = L"\\\\.\\PhysicalDrive", *e;
+ for(e = physicaldrivename; *e != 0u; e++)
+ {
+ ;
+ }
+ if(vde->Extents[0].DiskNumber >= 100)
+ {
+ *e++ = '0' + ((vde->Extents[0].DiskNumber / 100) % 10);
+ }
+ if(vde->Extents[0].DiskNumber >= 10)
+ {
+ *e++ = '0' + ((vde->Extents[0].DiskNumber / 10) % 10);
+ }
+ *e++ = '0' + (vde->Extents[0].DiskNumber % 10);
+ *e = 0;
+ OUTCOME_TRY(diskh, file_handle::file({}, wstring_view(physicaldrivename, e - physicaldrivename), handle::mode::none, handle::creation::open_existing, handle::caching::only_metadata));
+ memset(&spq, 0, sizeof(spq));
+ spq.PropertyId = StorageDeviceProperty;
+ spq.QueryType = PropertyStandardQuery;
+ auto *sdd = reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR *>(buffer);
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ if(DeviceIoControl(diskh.native_handle().h, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(spq), sdd, sizeof(buffer), nullptr, &ol) == 0)
+ {
+ if(ERROR_IO_PENDING == GetLastError())
+ {
+ NTSTATUS ntstat = ntwait(volumeh.native_handle().h, ol, deadline());
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(ERROR_SUCCESS != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ if(sdd->VendorIdOffset > 0 && sdd->VendorIdOffset < sizeof(buffer))
+ {
+ for(auto n = sdd->VendorIdOffset; (reinterpret_cast<const char *>(buffer))[n] != 0; n++)
+ {
+ sp.device_name.value.push_back((reinterpret_cast<const char *>(buffer))[n]);
+ }
+ sp.device_name.value.push_back(',');
+ }
+ if(sdd->ProductIdOffset > 0 && sdd->ProductIdOffset < sizeof(buffer))
+ {
+ for(auto n = sdd->ProductIdOffset; (reinterpret_cast<const char *>(buffer))[n] != 0; n++)
+ {
+ sp.device_name.value.push_back((reinterpret_cast<const char *>(buffer))[n]);
+ }
+ sp.device_name.value.push_back(',');
+ }
+ if(sdd->ProductRevisionOffset > 0 && sdd->ProductRevisionOffset < sizeof(buffer))
+ {
+ for(auto n = sdd->ProductRevisionOffset; (reinterpret_cast<const char *>(buffer))[n] != 0; n++)
+ {
+ sp.device_name.value.push_back((reinterpret_cast<const char *>(buffer))[n]);
+ }
+ sp.device_name.value.push_back(',');
+ }
+ if(!sp.device_name.value.empty())
+ {
+ sp.device_name.value.resize(sp.device_name.value.size() - 1);
+ }
+ if(disk_extents > 1)
+ {
+ sp.device_name.value.append(" (NOTE: plus additional devices)");
+ }
+
+ // Get device size
+ // IOCTL_STORAGE_READ_CAPACITY needs GENERIC_READ privs which requires admin privs
+ // so simply fetch the geometry
+ auto *dg = reinterpret_cast<DISK_GEOMETRY_EX *>(buffer);
+ ol.Internal = static_cast<ULONG_PTR>(-1);
+ if(DeviceIoControl(diskh.native_handle().h, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, dg, sizeof(buffer), nullptr, &ol) == 0)
+ {
+ if(ERROR_IO_PENDING == GetLastError())
+ {
+ NTSTATUS ntstat = ntwait(volumeh.native_handle().h, ol, deadline());
+ if(ntstat != 0)
+ {
+ return ntkernel_error(ntstat);
+ }
+ }
+ if(ERROR_SUCCESS != GetLastError())
+ {
+ return win32_error();
+ }
+ }
+ sp.device_size.value = dg->DiskSize.QuadPart;
+ }
+ }
+ catch(...)
+ {
+ return std::current_exception();
+ }
+ return success();
+ }
+ } // namespace windows
+ } // namespace storage
+} // namespace storage_profile
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/detail/impl/windows/utils.ipp b/include/llfio/v2.0/detail/impl/windows/utils.ipp
new file mode 100644
index 00000000..c6792acd
--- /dev/null
+++ b/include/llfio/v2.0/detail/impl/windows/utils.ipp
@@ -0,0 +1,238 @@
+/* Misc utilities
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (7 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "../../../utils.hpp"
+
+#include "../../../quickcpplib/include/spinlock.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace utils
+{
+ // Stupid MSVC ...
+ namespace detail
+ {
+ using namespace AFIO_V2_NAMESPACE::detail;
+ }
+ size_t page_size() noexcept
+ {
+ static size_t ret;
+ if(ret == 0u)
+ {
+ SYSTEM_INFO si{};
+ memset(&si, 0, sizeof(si));
+ GetSystemInfo(&si);
+ ret = si.dwPageSize;
+ }
+ return ret;
+ }
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 6387) // MSVC sanitiser warns that GetModuleHandleA() might fail (hah!)
+#endif
+ std::vector<size_t> page_sizes(bool only_actually_available)
+ {
+ static QUICKCPPLIB_NAMESPACE::configurable_spinlock::spinlock<bool> lock;
+ static std::vector<size_t> pagesizes, pagesizes_available;
+ std::lock_guard<decltype(lock)> g(lock);
+ if(pagesizes.empty())
+ {
+ using GetLargePageMinimum_t = size_t(WINAPI *)(void);
+ SYSTEM_INFO si{};
+ memset(&si, 0, sizeof(si));
+ GetSystemInfo(&si);
+ pagesizes.push_back(si.dwPageSize);
+ pagesizes_available.push_back(si.dwPageSize);
+ auto GetLargePageMinimum_ = reinterpret_cast<GetLargePageMinimum_t>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetLargePageMinimum"));
+ if(GetLargePageMinimum_ != nullptr)
+ {
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ pagesizes.push_back(GetLargePageMinimum_());
+ /* Attempt to enable SeLockMemoryPrivilege */
+ HANDLE token;
+ if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token) != 0)
+ {
+ auto untoken = undoer([&token] { CloseHandle(token); });
+ TOKEN_PRIVILEGES privs{};
+ memset(&privs, 0, sizeof(privs));
+ privs.PrivilegeCount = 1;
+ if(LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &privs.Privileges[0].Luid))
+ {
+ privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ if((AdjustTokenPrivileges(token, FALSE, &privs, 0, nullptr, nullptr) != 0) && GetLastError() == S_OK)
+ {
+ pagesizes_available.push_back(GetLargePageMinimum_());
+ }
+ }
+ }
+ }
+ }
+ return only_actually_available ? pagesizes_available : pagesizes;
+ }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+ void random_fill(char *buffer, size_t bytes) noexcept
+ {
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ if(RtlGenRandom(buffer, static_cast<ULONG>(bytes)) == 0u)
+ {
+ AFIO_LOG_FATAL(0, "afio: Kernel crypto function failed");
+ std::terminate();
+ }
+ }
+
+ result<void> flush_modified_data() noexcept
+ {
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ static bool prived;
+ if(!prived)
+ {
+ HANDLE processToken;
+ if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken) == FALSE)
+ {
+ return win32_error();
+ }
+ auto unprocessToken = undoer([&processToken] { CloseHandle(processToken); });
+ {
+ LUID luid{};
+ if(!LookupPrivilegeValue(nullptr, L"SeProfileSingleProcessPrivilege", &luid))
+ {
+ return win32_error();
+ }
+ TOKEN_PRIVILEGES tp{};
+ tp.PrivilegeCount = 1;
+ tp.Privileges[0].Luid = luid;
+ tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ if(AdjustTokenPrivileges(processToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), static_cast<PTOKEN_PRIVILEGES>(nullptr), static_cast<PDWORD>(nullptr)) == 0)
+ {
+ return win32_error();
+ }
+ if(GetLastError() == ERROR_NOT_ALL_ASSIGNED)
+ {
+ return win32_error();
+ }
+ }
+ prived = true;
+ }
+ typedef enum _SYSTEM_MEMORY_LIST_COMMAND { MemoryCaptureAccessedBits, MemoryCaptureAndResetAccessedBits, MemoryEmptyWorkingSets, MemoryFlushModifiedList, MemoryPurgeStandbyList, MemoryPurgeLowPriorityStandbyList, MemoryCommandMax } SYSTEM_MEMORY_LIST_COMMAND; // NOLINT
+
+ // Write all modified pages to storage
+ SYSTEM_MEMORY_LIST_COMMAND command = MemoryPurgeStandbyList;
+ NTSTATUS ntstat = NtSetSystemInformation(80 /*SystemMemoryListInformation*/, &command, sizeof(SYSTEM_MEMORY_LIST_COMMAND));
+ if(ntstat != STATUS_SUCCESS)
+ {
+ return ntkernel_error(ntstat);
+ }
+ return success();
+ }
+
+ result<void> drop_filesystem_cache() noexcept
+ {
+ windows_nt_kernel::init();
+ using namespace windows_nt_kernel;
+ static bool prived;
+ if(!prived)
+ {
+ HANDLE processToken;
+ if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &processToken) == FALSE)
+ {
+ return win32_error();
+ }
+ auto unprocessToken = undoer([&processToken] { CloseHandle(processToken); });
+ {
+ LUID luid{};
+ if(!LookupPrivilegeValue(nullptr, L"SeIncreaseQuotaPrivilege", &luid))
+ {
+ return win32_error();
+ }
+ TOKEN_PRIVILEGES tp{};
+ tp.PrivilegeCount = 1;
+ tp.Privileges[0].Luid = luid;
+ tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ if(AdjustTokenPrivileges(processToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), static_cast<PTOKEN_PRIVILEGES>(nullptr), static_cast<PDWORD>(nullptr)) == 0)
+ {
+ return win32_error();
+ }
+ if(GetLastError() == ERROR_NOT_ALL_ASSIGNED)
+ {
+ return win32_error();
+ }
+ }
+ prived = true;
+ }
+ // Flush modified data so dropping the cache drops everything
+ OUTCOME_TRYV(flush_modified_data());
+ // Drop filesystem cache
+ if(SetSystemFileCacheSize(static_cast<SIZE_T>(-1), static_cast<SIZE_T>(-1), 0) == 0)
+ {
+ return win32_error();
+ }
+ return success();
+ }
+
+ namespace detail
+ {
+ large_page_allocation allocate_large_pages(size_t bytes)
+ {
+ large_page_allocation ret(calculate_large_page_allocation(bytes));
+ DWORD type = MEM_COMMIT | MEM_RESERVE;
+ if(ret.page_size_used > 65536)
+ {
+ type |= MEM_LARGE_PAGES;
+ }
+ ret.p = VirtualAlloc(nullptr, ret.actual_size, type, PAGE_READWRITE);
+ if(ret.p == nullptr)
+ {
+ if(ERROR_NOT_ENOUGH_MEMORY == GetLastError())
+ {
+ ret.p = VirtualAlloc(nullptr, ret.actual_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+ }
+ }
+#ifndef NDEBUG
+ else if(ret.page_size_used > 65536)
+ {
+ printf("afio: Large page allocation successful\n");
+ }
+#endif
+ return ret;
+ }
+ void deallocate_large_pages(void *p, size_t bytes)
+ {
+ (void) bytes;
+ if(VirtualFree(p, 0, MEM_RELEASE) == 0)
+ {
+ AFIO_LOG_FATAL(p, "afio: Freeing large pages failed");
+ std::terminate();
+ }
+ }
+ } // namespace detail
+} // namespace utils
+
+AFIO_V2_NAMESPACE_END
diff --git a/include/llfio/v2.0/directory_handle.hpp b/include/llfio/v2.0/directory_handle.hpp
new file mode 100644
index 00000000..cbb37f5d
--- /dev/null
+++ b/include/llfio/v2.0/directory_handle.hpp
@@ -0,0 +1,388 @@
+/* A handle to a directory
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_DIRECTORY_HANDLE_H
+#define AFIO_DIRECTORY_HANDLE_H
+
+#include "path_discovery.hpp"
+#include "stat.hpp"
+
+#include <memory> // for unique_ptr
+
+//! \file directory_handle.hpp Provides a handle to a directory.
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+struct directory_entry
+{
+ //! The leafname of the directory entry
+ path_view leafname;
+ //! The metadata retrieved for the directory entry
+ stat_t stat;
+};
+#ifndef NDEBUG
+// Is trivial in all ways, except default constructibility
+static_assert(std::is_trivially_copyable<directory_entry>::value, "directory_entry is not trivially copyable!");
+static_assert(std::is_trivially_assignable<directory_entry, directory_entry>::value, "directory_entry is not trivially assignable!");
+static_assert(std::is_trivially_destructible<directory_entry>::value, "directory_entry is not trivially destructible!");
+static_assert(std::is_trivially_copy_constructible<directory_entry>::value, "directory_entry is not trivially copy constructible!");
+static_assert(std::is_trivially_move_constructible<directory_entry>::value, "directory_entry is not trivially move constructible!");
+static_assert(std::is_trivially_copy_assignable<directory_entry>::value, "directory_entry is not trivially copy assignable!");
+static_assert(std::is_trivially_move_assignable<directory_entry>::value, "directory_entry is not trivially move assignable!");
+static_assert(std::is_standard_layout<directory_entry>::value, "directory_entry is not a standard layout type!");
+#endif
+
+/*! \class directory_handle
+\brief A handle to a directory which can be enumerated.
+*/
+class AFIO_DECL directory_handle : public path_handle, public fs_handle
+{
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC const handle &_get_handle() const noexcept final { return *this; }
+
+public:
+ using path_type = path_handle::path_type;
+ using extent_type = path_handle::extent_type;
+ using size_type = path_handle::size_type;
+ using mode = path_handle::mode;
+ using creation = path_handle::creation;
+ using caching = path_handle::caching;
+ using flag = path_handle::flag;
+ using dev_t = fs_handle::dev_t;
+ using ino_t = fs_handle::ino_t;
+ using path_view_type = fs_handle::path_view_type;
+
+ //! The buffer type used by this handle, which is a `directory_entry`
+ using buffer_type = directory_entry;
+ /*! The buffers type used by this handle, which is a contiguous sequence of `directory_entry`.
+
+ \warning Unless you supply your own kernel buffer, you need to keep this around as long as you
+ use any of the directory entries, as their leafnames are views of the original buffer filled by
+ the kernel and the existence of this keeps that original buffer around.
+ */
+ struct buffers_type : public span<buffer_type>
+ {
+ using span<buffer_type>::span;
+ //! Implicit construction from a span
+ /* constexpr */ buffers_type(span<buffer_type> v) // NOLINT TODO FIXME Make this constexpr when span becomes constexpr. SAME for move constructor below
+ : span<buffer_type>(v)
+ {
+ }
+ ~buffers_type() = default;
+ //! Move constructor
+ /* constexpr */ buffers_type(buffers_type &&o) noexcept : span<buffer_type>(std::move(o)), _kernel_buffer(std::move(o._kernel_buffer)), _kernel_buffer_size(o._kernel_buffer_size)
+ {
+ static_cast<span<buffer_type> &>(o) = {};
+ o._kernel_buffer_size = 0;
+ }
+ //! No copy construction
+ buffers_type(const buffers_type &) = delete;
+ //! Move assignment
+ buffers_type &operator=(buffers_type &&o) noexcept
+ {
+ this->~buffers_type();
+ new(this) buffers_type(std::move(o));
+ return *this;
+ }
+ //! No copy assignment
+ buffers_type &operator=(const buffers_type &) = delete;
+
+ private:
+ friend class directory_handle;
+ std::unique_ptr<char[]> _kernel_buffer;
+ size_t _kernel_buffer_size{0};
+ void _resize(size_t l) { *static_cast<span<buffer_type> *>(this) = this->subspan(0, l); }
+ };
+
+ //! How to do deleted file elimination on Windows
+ enum class filter
+ {
+ none, //!< Do no filtering at all
+ fastdeleted //!< Filter out AFIO deleted files based on their filename (fast and fairly reliable)
+ };
+
+public:
+ //! Default constructor
+ constexpr directory_handle() {} // NOLINT
+ //! Construct a directory_handle from a supplied native path_handle
+ explicit constexpr directory_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::all, flag flags = flag::none)
+ : path_handle(std::move(h), caching, flags)
+ , fs_handle(devid, inode)
+ {
+ }
+ //! Implicit move construction of directory_handle permitted
+ constexpr directory_handle(directory_handle &&o) noexcept : path_handle(std::move(o)), fs_handle(std::move(o)) {}
+ //! No copy construction (use `clone()`)
+ directory_handle(const directory_handle &) = delete;
+ //! Explicit conversion from handle permitted
+ explicit constexpr directory_handle(handle &&o, dev_t devid, ino_t inode) noexcept : path_handle(std::move(o)), fs_handle(devid, inode) {}
+ //! Move assignment of directory_handle permitted
+ directory_handle &operator=(directory_handle &&o) noexcept
+ {
+ this->~directory_handle();
+ new(this) directory_handle(std::move(o));
+ return *this;
+ }
+ //! No copy assignment
+ directory_handle &operator=(const directory_handle &) = delete;
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(directory_handle &o) noexcept
+ {
+ directory_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! Create a handle opening access to a directory on path.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<directory_handle> directory(const path_handle &base, path_view_type path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept;
+ /*! Create a directory handle creating a randomly named file on a path.
+ The file is opened exclusively with `creation::only_if_not_exist` so it
+ will never collide with nor overwrite any existing entry.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<directory_handle> random_directory(const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::temporary, flag flags = flag::none) noexcept
+ {
+ try
+ {
+ for(;;)
+ {
+ auto randomname = utils::random_string(32);
+ result<directory_handle> ret = directory(dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags);
+ if(ret || (!ret && ret.error() != errc::file_exists))
+ {
+ return ret;
+ }
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ /*! Create a directory handle creating the named directory on some path which
+ the OS declares to be suitable for temporary files.
+ Note also that an empty name is equivalent to calling
+ `random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+ parameter is ignored.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<directory_handle> temp_directory(path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::all, flag flags = flag::none) noexcept
+ {
+ auto &tempdirh = path_discovery::storage_backed_temporary_files_directory();
+ return name.empty() ? random_directory(tempdirh, _mode, _caching, flags) : directory(tempdirh, name, _mode, _creation, _caching, flags);
+ }
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~directory_handle() override
+ {
+ if(_v)
+ {
+ (void) directory_handle::close();
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_flags & flag::unlink_on_first_close)
+ {
+ auto ret = unlink();
+ if(!ret)
+ {
+ // File may have already been deleted, if so ignore
+ if(ret.error() != errc::no_such_file_or_directory)
+ {
+ return ret.error();
+ }
+ }
+ }
+ return path_handle::close();
+ }
+
+ /*! Clone this handle (copy constructor is disabled to avoid accidental copying),
+ optionally race free reopening the handle with different access or caching.
+
+ Microsoft Windows provides a syscall for cloning an existing handle but with new
+ access. On POSIX, we must loop calling `current_path()`,
+ trying to open the path returned and making sure it is the same inode.
+
+ \errors Any of the values POSIX dup() or DuplicateHandle() can return.
+ \mallocs On POSIX if changing the mode, we must loop calling `current_path()` and
+ trying to open the path returned. Thus many allocations may occur.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<directory_handle> clone(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept;
+
+ /*! Return a copy of this directory handle, but as a path handle.
+
+ \errors Any of the values POSIX dup() or DuplicateHandle() can return.
+ \mallocs On POSIX, we must loop calling `current_path()` and
+ trying to open the path returned. Thus many allocations may occur.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> clone_to_path_handle() const noexcept;
+
+#ifdef _WIN32
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> relink(const path_handle &base, path_view_type newpath, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept override;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept override;
+#endif
+
+ //! Completion information for `enumerate()`
+ struct enumerate_info
+ {
+ //! The buffers filled.
+ buffers_type filled;
+ //! The list of stat metadata retrieved by `enumerate()` this call per `buffer_type`.
+ stat_t::want metadata;
+ //! Whether the directory was entirely read or not.
+ bool done;
+ };
+ /*! Fill the buffers type with as many directory entries as will fit.
+
+ \return Returns the buffers filled, what metadata was filled in and whether the entire directory
+ was read into `tofill`.
+ \param tofill The buffers to fill, returned to you on exit.
+ \param glob An optional shell glob by which to filter the items filled. Done kernel side on Windows, user side on POSIX.
+ \param filtering Whether to filter out fake-deleted files on Windows or not.
+ \param kernelbuffer A buffer to use for the kernel to fill. If left defaulted, a kernel buffer
+ is allocated internally and stored into `tofill` which needs to not be destructed until one
+ is no longer using any items within (leafnames are views onto the original kernel data).
+ \errors todo
+ \mallocs If the `kernelbuffer` parameter is set on entry, no memory allocations.
+ If unset, at least one memory allocation, possibly more is performed.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<enumerate_info> enumerate(buffers_type &&tofill, path_view_type glob = path_view_type(), filter filtering = filter::fastdeleted, span<char> kernelbuffer = span<char>()) const noexcept;
+};
+inline std::ostream &operator<<(std::ostream &s, const directory_handle::filter &v)
+{
+ static constexpr const char *values[] = {"none", "fastdeleted"};
+ if(static_cast<size_t>(v) >= sizeof(values) / sizeof(values[0]) || (values[static_cast<size_t>(v)] == nullptr))
+ {
+ return s << "afio::directory_handle::filter::<unknown>";
+ }
+ return s << "afio::directory_handle::filter::" << values[static_cast<size_t>(v)];
+}
+inline std::ostream &operator<<(std::ostream &s, const directory_handle::enumerate_info & /*unused*/)
+{
+ return s << "afio::directory_handle::enumerate_info";
+}
+
+//! \brief Constructor for `directory_handle`
+template <> struct construct<directory_handle>
+{
+ const path_handle &base;
+ directory_handle::path_view_type _path;
+ directory_handle::mode _mode = directory_handle::mode::read;
+ directory_handle::creation _creation = directory_handle::creation::open_existing;
+ directory_handle::caching _caching = directory_handle::caching::all;
+ directory_handle::flag flags = directory_handle::flag::none;
+ result<directory_handle> operator()() const noexcept { return directory_handle::directory(base, _path, _mode, _creation, _caching, flags); }
+};
+
+// BEGIN make_free_functions.py
+//! Swap with another instance
+inline void swap(directory_handle &self, directory_handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+/*! Create a handle opening access to a directory on path.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<directory_handle> directory(const path_handle &base, directory_handle::path_view_type path, directory_handle::mode _mode = directory_handle::mode::read, directory_handle::creation _creation = directory_handle::creation::open_existing, directory_handle::caching _caching = directory_handle::caching::all, directory_handle::flag flags = directory_handle::flag::none) noexcept
+{
+ return directory_handle::directory(std::forward<decltype(base)>(base), std::forward<decltype(path)>(path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create a directory handle creating a randomly named file on a path.
+The file is opened exclusively with `creation::only_if_not_exist` so it
+will never collide with nor overwrite any existing entry.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<directory_handle> random_directory(const path_handle &dirpath, directory_handle::mode _mode = directory_handle::mode::write, directory_handle::caching _caching = directory_handle::caching::temporary, directory_handle::flag flags = directory_handle::flag::none) noexcept
+{
+ return directory_handle::random_directory(std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create a directory handle creating the named directory on some path which
+the OS declares to be suitable for temporary files.
+Note also that an empty name is equivalent to calling
+`random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+parameter is ignored.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<directory_handle> temp_directory(directory_handle::path_view_type name = directory_handle::path_view_type(), directory_handle::mode _mode = directory_handle::mode::write, directory_handle::creation _creation = directory_handle::creation::if_needed, directory_handle::caching _caching = directory_handle::caching::all, directory_handle::flag flags = directory_handle::flag::none) noexcept
+{
+ return directory_handle::temp_directory(std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Fill the buffers type with as many directory entries as will fit.
+
+\return Returns the buffers filled, what metadata was filled in and whether the entire directory
+was read into `tofill`.
+\param self The object whose member function to call.
+\param tofill The buffers to fill, returned to you on exit.
+\param glob An optional shell glob by which to filter the items filled. Done kernel side on Windows, user side on POSIX.
+\param filtering Whether to filter out fake-deleted files on Windows or not.
+\param kernelbuffer A buffer to use for the kernel to fill. If left defaulted, a kernel buffer
+is allocated internally and stored into `tofill` which needs to not be destructed until one
+is no longer using any items within (leafnames are views onto the original kernel data).
+\errors todo
+\mallocs If the `kernelbuffer` parameter is set on entry, no memory allocations.
+If unset, at least one memory allocation, possibly more is performed.
+*/
+inline result<directory_handle::enumerate_info> enumerate(const directory_handle &self, directory_handle::buffers_type &&tofill, directory_handle::path_view_type glob = directory_handle::path_view_type(), directory_handle::filter filtering = directory_handle::filter::fastdeleted, span<char> kernelbuffer = span<char>()) noexcept
+{
+ return self.enumerate(std::forward<decltype(tofill)>(tofill), std::forward<decltype(glob)>(glob), std::forward<decltype(filtering)>(filtering), std::forward<decltype(kernelbuffer)>(kernelbuffer));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/directory_handle.ipp"
+#else
+#include "detail/impl/posix/directory_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/file_handle.hpp b/include/llfio/v2.0/file_handle.hpp
new file mode 100644
index 00000000..04634ed5
--- /dev/null
+++ b/include/llfio/v2.0/file_handle.hpp
@@ -0,0 +1,437 @@
+/* A handle to a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_FILE_HANDLE_H
+#define AFIO_FILE_HANDLE_H
+
+#include "io_handle.hpp"
+#include "path_discovery.hpp"
+#include "utils.hpp"
+
+//! \file file_handle.hpp Provides file_handle
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+class io_service;
+
+/*! \class file_handle
+\brief A handle to a regular file or device, kept data layout compatible with
+async_file_handle.
+
+<table>
+<tr><th></th><th>Cost of opening</th><th>Cost of i/o</th><th>Concurrency and Atomicity</th><th>Other remarks</th></tr>
+<tr><td>`file_handle`</td><td>Least</td><td>Syscall</td><td>POSIX guarantees (usually)</td><td>Least gotcha</td></tr>
+<tr><td>`async_file_handle`</td><td>More</td><td>Most (syscall + malloc/free + reactor)</td><td>POSIX guarantees (usually)</td><td>Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`</td></tr>
+<tr><td>`mapped_file_handle`</td><td>Most</td><td>Least</td><td>None</td><td>Cannot be used with uncached i/o</td></tr>
+</table>
+
+*/
+class AFIO_DECL file_handle : public io_handle, public fs_handle
+{
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC const handle &_get_handle() const noexcept final { return *this; }
+
+public:
+ using path_type = io_handle::path_type;
+ using extent_type = io_handle::extent_type;
+ using size_type = io_handle::size_type;
+ using mode = io_handle::mode;
+ using creation = io_handle::creation;
+ using caching = io_handle::caching;
+ using flag = io_handle::flag;
+ using buffer_type = io_handle::buffer_type;
+ using const_buffer_type = io_handle::const_buffer_type;
+ using buffers_type = io_handle::buffers_type;
+ using const_buffers_type = io_handle::const_buffers_type;
+ template <class T> using io_request = io_handle::io_request<T>;
+ template <class T> using io_result = io_handle::io_result<T>;
+ using dev_t = fs_handle::dev_t;
+ using ino_t = fs_handle::ino_t;
+ using path_view_type = fs_handle::path_view_type;
+
+protected:
+ io_service *_service{nullptr};
+
+public:
+ //! Default constructor
+ constexpr file_handle() {} // NOLINT
+ //! Construct a handle from a supplied native handle
+ constexpr file_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none)
+ : io_handle(std::move(h), caching, flags)
+ , fs_handle(devid, inode)
+ , _service(nullptr)
+ {
+ }
+ //! No copy construction (use clone())
+ file_handle(const file_handle &) = delete;
+ //! No copy assignment
+ file_handle &operator=(const file_handle &) = delete;
+ //! Implicit move construction of file_handle permitted
+ constexpr file_handle(file_handle &&o) noexcept : io_handle(std::move(o)), fs_handle(std::move(o)), _service(o._service) { o._service = nullptr; }
+ //! Explicit conversion from handle and io_handle permitted
+ explicit constexpr file_handle(handle &&o, dev_t devid, ino_t inode) noexcept : io_handle(std::move(o)), fs_handle(devid, inode), _service(nullptr) {}
+ //! Move assignment of file_handle permitted
+ file_handle &operator=(file_handle &&o) noexcept
+ {
+ this->~file_handle();
+ new(this) file_handle(std::move(o));
+ return *this;
+ }
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(file_handle &o) noexcept
+ {
+ file_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! Create a file handle opening access to a file on path
+ \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+ \param path The path relative to base to open.
+ \param _mode How to open the file.
+ \param _creation How to create the file.
+ \param _caching How to ask the kernel to cache the file.
+ \param flags Any additional custom behaviours.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<file_handle> file(const path_handle &base, path_view_type path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept;
+ /*! Create a file handle creating a randomly named file on a path.
+ The file is opened exclusively with `creation::only_if_not_exist` so it
+ will never collide with nor overwrite any existing file. Note also
+ that caching defaults to temporary which hints to the OS to only
+ flush changes to physical storage as lately as possible.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<file_handle> random_file(const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::temporary, flag flags = flag::none) noexcept
+ {
+ try
+ {
+ for(;;)
+ {
+ auto randomname = utils::random_string(32);
+ randomname.append(".random");
+ result<file_handle> ret = file(dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags);
+ if(ret || (!ret && ret.error() != errc::file_exists))
+ {
+ return ret;
+ }
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ /*! Create a file handle creating the named file on some path which
+ the OS declares to be suitable for temporary files. Most OSs are
+ very lazy about flushing changes made to these temporary files.
+ Note the default flags are to have the newly created file deleted
+ on first handle close.
+ Note also that an empty name is equivalent to calling
+ `random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+ parameter is ignored.
+
+ \note If the temporary file you are creating is not going to have its
+ path sent to another process for usage, this is the WRONG function
+ to use. Use `temp_inode()` instead, it is far more secure.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<file_handle> temp_file(path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::temporary, flag flags = flag::unlink_on_first_close) noexcept
+ {
+ auto &tempdirh = path_discovery::storage_backed_temporary_files_directory();
+ return name.empty() ? random_file(tempdirh, _mode, _caching, flags) : file(tempdirh, name, _mode, _creation, _caching, flags);
+ }
+ /*! \em Securely create a file handle creating a temporary anonymous inode in
+ the filesystem referred to by \em dirpath. The inode created has
+ no name nor accessible path on the filing system and ceases to
+ exist as soon as the last handle is closed, making it ideal for use as
+ a temporary file where other processes do not need to have access
+ to its contents via some path on the filing system (a classic use case
+ is for backing shared memory maps).
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<file_handle> temp_inode(const path_handle &dirh = path_discovery::storage_backed_temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept;
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~file_handle() override
+ {
+ if(_v)
+ {
+ (void) file_handle::close();
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override
+ {
+ AFIO_LOG_FUNCTION_CALL(this);
+ if(_flags & flag::unlink_on_first_close)
+ {
+ auto ret = unlink();
+ if(!ret)
+ {
+ // File may have already been deleted, if so ignore
+ if(ret.error() != errc::no_such_file_or_directory)
+ {
+ return ret.error();
+ }
+ }
+ }
+ return io_handle::close();
+ }
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override;
+
+ /*! Clone this handle (copy constructor is disabled to avoid accidental copying),
+ optionally race free reopening the handle with different access or caching.
+
+ Microsoft Windows provides a syscall for cloning an existing handle but with new
+ access. On POSIX, if not changing the mode, we change caching via `fcntl()`, if
+ changing the mode we must loop calling `current_path()`,
+ trying to open the path returned and making sure it is the same inode.
+
+ \errors Any of the values POSIX dup() or DuplicateHandle() can return.
+ \mallocs On POSIX if changing the mode, we must loop calling `current_path()` and
+ trying to open the path returned. Thus many allocations may occur.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept;
+
+ //! The i/o service this handle is attached to, if any
+ io_service *service() const noexcept { return _service; }
+
+ /*! Return the current maximum permitted extent of the file.
+
+ \errors Any of the values POSIX fstat() or GetFileInformationByHandleEx() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> maximum_extent() const noexcept;
+
+ /*! Resize the current maximum permitted extent of the file to the given extent, avoiding any
+ new allocation of physical storage where supported. Note that on extents based filing systems
+ this will succeed even if there is insufficient free space on the storage medium.
+
+ \return The bytes actually truncated to.
+ \param newsize The bytes to truncate the file to.
+ \errors Any of the values POSIX ftruncate() or SetFileInformationByHandle() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> truncate(extent_type newsize) noexcept;
+
+ /*! \brief Returns a list of currently valid extents for this open file. WARNING: racy!
+ \return A vector of pairs of extent offset + extent length representing the valid extents
+ in this file. Filing systems which do not support extents return a single extent matching
+ the length of the file rather than returning an error.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<std::vector<std::pair<extent_type, extent_type>>> extents() const noexcept;
+
+ /*! \brief Efficiently zero, and possibly deallocate, data on storage.
+
+ On most major operating systems and with recent filing systems which are "extents based", one can
+ deallocate the physical storage of a file, causing the space deallocated to appear all bits zero.
+ This call attempts to deallocate whole pages (usually 4Kb) entirely, and memset's any excess to all
+ bits zero. This call works on most Linux filing systems with a recent kernel, Microsoft Windows
+ with NTFS, and FreeBSD with ZFS. On other systems it simply writes zeros.
+
+ \return The bytes zeroed.
+ \param offset The offset to start zeroing from.
+ \param bytes The number of bytes to zero.
+ \param d An optional deadline by which the i/o must complete, else it is cancelled.
+ Note function may return significantly after this deadline if the i/o takes long to cancel.
+ \errors Any of the values POSIX write() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be
+ returned if deadline i/o is not possible with this particular handle configuration (e.g.
+ writing to regular files on POSIX or writing to a non-overlapped HANDLE on Windows).
+ \mallocs The default synchronous implementation in file_handle performs no memory allocation.
+ The asynchronous implementation in async_file_handle may perform one calloc and one free.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> zero(extent_type offset, extent_type bytes, deadline d = deadline()) noexcept;
+};
+
+//! \brief Constructor for `file_handle`
+template <> struct construct<file_handle>
+{
+ const path_handle &base;
+ file_handle::path_view_type _path;
+ file_handle::mode _mode = file_handle::mode::read;
+ file_handle::creation _creation = file_handle::creation::open_existing;
+ file_handle::caching _caching = file_handle::caching::all;
+ file_handle::flag flags = file_handle::flag::none;
+ result<file_handle> operator()() const noexcept { return file_handle::file(base, _path, _mode, _creation, _caching, flags); }
+};
+
+// BEGIN make_free_functions.py
+//! Swap with another instance
+inline void swap(file_handle &self, file_handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+/*! Create a file handle opening access to a file on path
+\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+\param path The path relative to base to open.
+\param _mode How to open the file.
+\param _creation How to create the file.
+\param _caching How to ask the kernel to cache the file.
+\param flags Any additional custom behaviours.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<file_handle> file(const path_handle &base, file_handle::path_view_type path, file_handle::mode _mode = file_handle::mode::read, file_handle::creation _creation = file_handle::creation::open_existing, file_handle::caching _caching = file_handle::caching::all,
+ file_handle::flag flags = file_handle::flag::none) noexcept
+{
+ return file_handle::file(std::forward<decltype(base)>(base), std::forward<decltype(path)>(path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create a file handle creating a randomly named file on a path.
+The file is opened exclusively with `creation::only_if_not_exist` so it
+will never collide with nor overwrite any existing file. Note also
+that caching defaults to temporary which hints to the OS to only
+flush changes to physical storage as lately as possible.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<file_handle> random_file(const path_handle &dirpath, file_handle::mode _mode = file_handle::mode::write, file_handle::caching _caching = file_handle::caching::temporary, file_handle::flag flags = file_handle::flag::none) noexcept
+{
+ return file_handle::random_file(std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create a file handle creating the named file on some path which
+the OS declares to be suitable for temporary files. Most OSs are
+very lazy about flushing changes made to these temporary files.
+Note the default flags are to have the newly created file deleted
+on first handle close.
+Note also that an empty name is equivalent to calling
+`random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+parameter is ignored.
+
+\note If the temporary file you are creating is not going to have its
+path sent to another process for usage, this is the WRONG function
+to use. Use `temp_inode()` instead, it is far more secure.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<file_handle> temp_file(file_handle::path_view_type name = file_handle::path_view_type(), file_handle::mode _mode = file_handle::mode::write, file_handle::creation _creation = file_handle::creation::if_needed, file_handle::caching _caching = file_handle::caching::temporary,
+ file_handle::flag flags = file_handle::flag::unlink_on_first_close) noexcept
+{
+ return file_handle::temp_file(std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! \em Securely create a file handle creating a temporary anonymous inode in
+the filesystem referred to by \em dirpath. The inode created has
+no name nor accessible path on the filing system and ceases to
+exist as soon as the last handle is closed, making it ideal for use as
+a temporary file where other processes do not need to have access
+to its contents via some path on the filing system (a classic use case
+is for backing shared memory maps).
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<file_handle> temp_inode(const path_handle &dirh = path_discovery::storage_backed_temporary_files_directory(), file_handle::mode _mode = file_handle::mode::write, file_handle::flag flags = file_handle::flag::none) noexcept
+{
+ return file_handle::temp_inode(std::forward<decltype(dirh)>(dirh), std::forward<decltype(_mode)>(_mode), std::forward<decltype(flags)>(flags));
+}
+/*! Return the current maximum permitted extent of the file.
+
+\errors Any of the values POSIX fstat() or GetFileInformationByHandleEx() can return.
+*/
+inline result<file_handle::extent_type> maximum_extent(const file_handle &self) noexcept
+{
+ return self.maximum_extent();
+}
+/*! Resize the current maximum permitted extent of the file to the given extent, avoiding any
+new allocation of physical storage where supported. Note that on extents based filing systems
+this will succeed even if there is insufficient free space on the storage medium.
+
+\return The bytes actually truncated to.
+\param self The object whose member function to call.
+\param newsize The bytes to truncate the file to.
+\errors Any of the values POSIX ftruncate() or SetFileInformationByHandle() can return.
+*/
+inline result<file_handle::extent_type> truncate(file_handle &self, file_handle::extent_type newsize) noexcept
+{
+ return self.truncate(std::forward<decltype(newsize)>(newsize));
+}
+/*! \brief Returns a list of currently valid extents for this open file. WARNING: racy!
+*/
+inline result<std::vector<std::pair<file_handle::extent_type, file_handle::extent_type>>> extents(const file_handle &self) noexcept
+{
+ return self.extents();
+}
+/*! \brief Efficiently zero, and possibly deallocate, data on storage.
+
+On most major operating systems and with recent filing systems which are "extents based", one can
+deallocate the physical storage of a file, causing the space deallocated to appear all bits zero.
+This call attempts to deallocate whole pages (usually 4Kb) entirely, and memset's any excess to all
+bits zero. This call works on most Linux filing systems with a recent kernel, Microsoft Windows
+with NTFS, and FreeBSD with ZFS. On other systems it simply writes zeros.
+
+\return The bytes zeroed.
+\param self The object whose member function to call.
+\param offset The offset to start zeroing from.
+\param bytes The number of bytes to zero.
+\param d An optional deadline by which the i/o must complete, else it is cancelled.
+Note function may return significantly after this deadline if the i/o takes long to cancel.
+\errors Any of the values POSIX write() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be
+returned if deadline i/o is not possible with this particular handle configuration (e.g.
+writing to regular files on POSIX or writing to a non-overlapped HANDLE on Windows).
+\mallocs The default synchronous implementation in file_handle performs no memory allocation.
+The asynchronous implementation in async_file_handle may perform one calloc and one free.
+*/
+inline result<file_handle::extent_type> zero(file_handle &self, file_handle::extent_type offset, file_handle::extent_type bytes, deadline d = deadline()) noexcept
+{
+ return self.zero(std::forward<decltype(offset)>(offset), std::forward<decltype(bytes)>(bytes), std::forward<decltype(d)>(d));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/file_handle.ipp"
+#else
+#include "detail/impl/posix/file_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+// Needs to be here as path discovery uses file_handle
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#include "detail/impl/path_discovery.ipp"
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/fs_handle.hpp b/include/llfio/v2.0/fs_handle.hpp
new file mode 100644
index 00000000..91d45e44
--- /dev/null
+++ b/include/llfio/v2.0/fs_handle.hpp
@@ -0,0 +1,272 @@
+/* A filing system handle
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Aug 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_FS_HANDLE_H
+#define AFIO_FS_HANDLE_H
+
+#include "path_handle.hpp"
+#include "path_view.hpp"
+
+#include "quickcpplib/include/uint128.hpp"
+
+//! \file fs_handle.hpp Provides fs_handle
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+/*! \class fs_handle
+\brief A handle to something with a device and inode number.
+
+\sa `algorithm::cached_parent_handle_adapter<T>`
+*/
+class AFIO_DECL fs_handle
+{
+public:
+ using dev_t = uint64_t;
+ using ino_t = uint64_t;
+ //! The path view type used by this handle
+ using path_view_type = path_view;
+ //! The unique identifier type used by this handle
+ using unique_id_type = QUICKCPPLIB_NAMESPACE::integers128::uint128;
+
+protected:
+ mutable dev_t _devid{0};
+ mutable ino_t _inode{0};
+
+ //! Fill in _devid and _inode from the handle via fstat()
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<void> _fetch_inode() const noexcept;
+
+ virtual const handle &_get_handle() const noexcept = 0;
+
+protected:
+ //! Default constructor
+ constexpr fs_handle() {} // NOLINT
+ ~fs_handle() = default;
+ //! Construct a handle
+ constexpr fs_handle(dev_t devid, ino_t inode)
+ : _devid(devid)
+ , _inode(inode)
+ {
+ }
+ //! Implicit move construction of fs_handle permitted
+ constexpr fs_handle(fs_handle &&o) noexcept : _devid(o._devid), _inode(o._inode)
+ {
+ o._devid = 0;
+ o._inode = 0;
+ }
+ //! Move assignment of fs_handle permitted
+ fs_handle &operator=(fs_handle &&o) noexcept
+ {
+ _devid = o._devid;
+ _inode = o._inode;
+ o._devid = 0;
+ o._inode = 0;
+ return *this;
+ }
+
+public:
+ //! No copy construction (use `clone()`)
+ fs_handle(const fs_handle &) = delete;
+ //! No copy assignment
+ fs_handle &operator=(const fs_handle &o) = delete;
+
+ //! Unless `flag::disable_safety_unlinks` is set, the device id of the file when opened
+ dev_t st_dev() const noexcept
+ {
+ if(_devid == 0 && _inode == 0)
+ {
+ (void) _fetch_inode();
+ }
+ return _devid;
+ }
+ //! Unless `flag::disable_safety_unlinks` is set, the inode of the file when opened. When combined with st_dev(), forms a unique identifer on this system
+ ino_t st_ino() const noexcept
+ {
+ if(_devid == 0 && _inode == 0)
+ {
+ (void) _fetch_inode();
+ }
+ return _inode;
+ }
+ //! A unique identifier for this handle across the entire system. Can be used in hash tables etc.
+ unique_id_type unique_id() const noexcept
+ {
+ if(_devid == 0 && _inode == 0)
+ {
+ (void) _fetch_inode();
+ }
+ unique_id_type ret;
+ ret.as_longlongs[0] = _devid;
+ ret.as_longlongs[1] = _inode;
+ return ret;
+ }
+
+ /*! Obtain a handle to the path **currently** containing this handle's file entry.
+
+ \warning This call is \b racy and can result in the wrong path handle being returned. Note that
+ unless `flag::disable_safety_unlinks` is set, this implementation opens a
+ `path_handle` to the source containing directory, then checks if the file entry within has the
+ same inode as the open file handle. It will retry this matching until
+ success until the deadline given.
+
+ \mallocs Calls `current_path()` and thus is both expensive and calls malloc many times.
+
+ \sa `algorithm::cached_parent_handle_adapter<T>` which overrides this with a zero cost
+ implementation, thus making unlinking and relinking very considerably quicker.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_handle> parent_path_handle(deadline d = std::chrono::seconds(30)) const noexcept;
+
+ /*! Relinks the current path of this open handle to the new path specified. If `atomic_replace` is
+ true, the relink \b atomically and silently replaces any item at the new path specified. This operation
+ is both atomic and silent matching POSIX behaviour even on Microsoft Windows where
+ no Win32 API can match POSIX semantics.
+
+ \warning Some operating systems provide a race free syscall for renaming an open handle (Windows).
+ On all other operating systems this call is \b racy and can result in the wrong file entry being
+ relinked. Note that unless `flag::disable_safety_unlinks` is set, this implementation opens a
+ `path_handle` to the source containing directory first, then checks before relinking that the item
+ about to be relinked has the same inode as the open file handle. It will retry this matching until
+ success until the deadline given. This should prevent most unmalicious accidental loss of data.
+
+ \param base Base for any relative path.
+ \param path The relative or absolute new path to relink to.
+ \param atomic_replace Atomically replace the destination if a file entry already is present there.
+ Choosing false for this will fail if a file entry is already present at the destination, and may
+ not be an atomic operation on some platforms (i.e. both the old and new names may be linked to the
+ same inode for a very short period of time). Windows and recent Linuxes are always atomic.
+ \param d The deadline by which the matching of the containing directory to the open handle's inode
+ must succeed, else `errc::timed_out` will be returned.
+ \mallocs Except on platforms with race free syscalls for renaming open handles (Windows), calls
+ `current_path()` via `parent_path_handle()` and thus is both expensive and calls malloc many times.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> relink(const path_handle &base, path_view_type path, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept;
+
+ /*! Unlinks the current path of this open handle, causing its entry to immediately disappear from the filing system.
+ On Windows before Windows 10 1709 unless `flag::win_disable_unlink_emulation` is set, this behaviour is
+ simulated by renaming the file to something random and setting its delete-on-last-close flag.
+ Note that Windows may prevent the renaming of a file in use by another process, if so it will
+ NOT be renamed.
+ After the next handle to that file closes, it will become permanently unopenable by anyone
+ else until the last handle is closed, whereupon the entry will be eventually removed by the
+ operating system.
+
+ \warning Some operating systems provide a race free syscall for unlinking an open handle (Windows).
+ On all other operating systems this call is \b racy and can result in the wrong file entry being
+ unlinked. Note that unless `flag::disable_safety_unlinks` is set, this implementation opens a
+ `path_handle` to the containing directory first, then checks that the item about to be unlinked
+ has the same inode as the open file handle. It will retry this matching until success until the
+ deadline given. This should prevent most unmalicious accidental loss of data.
+
+ \param d The deadline by which the matching of the containing directory to the open handle's inode
+ must succeed, else `errc::timed_out` will be returned.
+ \mallocs Except on platforms with race free syscalls for unlinking open handles (Windows), calls
+ `current_path()` and thus is both expensive and calls malloc many times. On Windows, also calls
+ `current_path()` if `flag::disable_safety_unlinks` is not set.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC
+ result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept;
+};
+
+// BEGIN make_free_functions.py
+/*! Relinks the current path of this open handle to the new path specified. If `atomic_replace` is
+true, the relink \b atomically and silently replaces any item at the new path specified. This operation
+is both atomic and silent matching POSIX behaviour even on Microsoft Windows where
+no Win32 API can match POSIX semantics.
+
+\warning Some operating systems provide a race free syscall for renaming an open handle (Windows).
+On all other operating systems this call is \b racy and can result in the wrong file entry being
+relinked. Note that unless `flag::disable_safety_unlinks` is set, this implementation opens a
+`path_handle` to the source containing directory first, then checks before relinking that the item
+about to be relinked has the same inode as the open file handle. It will retry this matching until
+success until the deadline given. This should prevent most unmalicious accidental loss of data.
+
+\param self The object whose member function to call.
+\param base Base for any relative path.
+\param path The relative or absolute new path to relink to.
+\param atomic_replace Atomically replace the destination if a file entry already is present there.
+Choosing false for this will fail if a file entry is already present at the destination, and may
+not be an atomic operation on some platforms (i.e. both the old and new names may be linked to the
+same inode for a very short period of time). Windows and recent Linuxes are always atomic.
+\param d The deadline by which the matching of the containing directory to the open handle's inode
+must succeed, else `errc::timed_out` will be returned.
+\mallocs Except on platforms with race free syscalls for renaming open handles (Windows), calls
+`current_path()` via `parent_path_handle()` and thus is both expensive and calls malloc many times.
+*/
+inline result<void> relink(fs_handle &self, const path_handle &base, fs_handle::path_view_type path, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept
+{
+ return self.relink(std::forward<decltype(base)>(base), std::forward<decltype(path)>(path), std::forward<decltype(atomic_replace)>(atomic_replace), std::forward<decltype(d)>(d));
+}
+/*! Unlinks the current path of this open handle, causing its entry to immediately disappear from the filing system.
+On Windows unless `flag::win_disable_unlink_emulation` is set, this behaviour is
+simulated by renaming the file to something random and setting its delete-on-last-close flag.
+Note that Windows may prevent the renaming of a file in use by another process, if so it will
+NOT be renamed.
+After the next handle to that file closes, it will become permanently unopenable by anyone
+else until the last handle is closed, whereupon the entry will be eventually removed by the
+operating system.
+
+\warning Some operating systems provide a race free syscall for unlinking an open handle (Windows).
+On all other operating systems this call is \b racy and can result in the wrong file entry being
+unlinked. Note that unless `flag::disable_safety_unlinks` is set, this implementation opens a
+`path_handle` to the containing directory first, then checks that the item about to be unlinked
+has the same inode as the open file handle. It will retry this matching until success until the
+deadline given. This should prevent most unmalicious accidental loss of data.
+
+\param self The object whose member function to call.
+\param d The deadline by which the matching of the containing directory to the open handle's inode
+must succeed, else `errc::timed_out` will be returned.
+\mallocs Except on platforms with race free syscalls for unlinking open handles (Windows), calls
+`current_path()` and thus is both expensive and calls malloc many times. On Windows, also calls
+`current_path()` if `flag::disable_safety_unlinks` is not set.
+*/
+inline result<void> unlink(fs_handle &self, deadline d = std::chrono::seconds(30)) noexcept
+{
+ return self.unlink(std::forward<decltype(d)>(d));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/fs_handle.ipp"
+#else
+#include "detail/impl/posix/fs_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/handle.hpp b/include/llfio/v2.0/handle.hpp
new file mode 100644
index 00000000..57f7618f
--- /dev/null
+++ b/include/llfio/v2.0/handle.hpp
@@ -0,0 +1,576 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_HANDLE_H
+#define AFIO_HANDLE_H
+
+#include "deadline.h"
+#include "native_handle_type.hpp"
+#include "status_code.hpp"
+
+#include <algorithm> // for std::count
+#include <cassert>
+
+//! \file handle.hpp Provides handle
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+class fs_handle;
+
+/*! \class handle
+\brief A native_handle_type which is managed by the lifetime of this object instance.
+*/
+class AFIO_DECL handle
+{
+ friend class fs_handle;
+ friend inline std::ostream &operator<<(std::ostream &s, const handle &v);
+
+public:
+ //! The path type used by this handle
+ using path_type = filesystem::path;
+ //! The file extent type used by this handle
+ using extent_type = unsigned long long;
+ //! The memory extent type used by this handle
+ using size_type = size_t;
+
+ //! The behaviour of the handle: does it read, read and write, or atomic append?
+ enum class mode : unsigned char // bit 0 set means writable
+ {
+ unchanged = 0,
+ none = 2, //!< No ability to read or write anything, but can synchronise (SYNCHRONIZE or 0)
+ attr_read = 4, //!< Ability to read attributes (FILE_READ_ATTRIBUTES|SYNCHRONIZE or O_RDONLY)
+ attr_write = 5, //!< Ability to read and write attributes (FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES|SYNCHRONIZE or O_RDONLY)
+ read = 6, //!< Ability to read (READ_CONTROL|FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA|SYNCHRONISE or O_RDONLY)
+ write = 7, //!< Ability to read and write (READ_CONTROL|FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA|FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA|FILE_APPEND_DATA|SYNCHRONISE or O_RDWR)
+ append = 9 //!< All mainstream OSs and CIFS guarantee this is atomic with respect to all other appenders (FILE_APPEND_DATA|SYNCHRONISE or O_APPEND)
+ // NOTE: IF UPDATING THIS UPDATE THE std::ostream PRINTER BELOW!!!
+ };
+ //! On opening, do we also create a new file or truncate an existing one?
+ enum class creation : unsigned char
+ {
+ open_existing = 0,
+ only_if_not_exist,
+ if_needed,
+ truncate //!< Atomically truncate on open, leaving creation date unmodified.
+ // NOTE: IF UPDATING THIS UPDATE THE std::ostream PRINTER BELOW!!!
+ };
+ //! What i/o on the handle will complete immediately due to kernel caching
+ enum class caching : unsigned char // bit 0 set means safety fsyncs enabled
+ {
+ unchanged = 0,
+ none = 1, //!< No caching whatsoever, all reads and writes come from storage (i.e. <tt>O_DIRECT|O_SYNC</tt>). Align all i/o to 4Kb boundaries for this to work. <tt>flag_disable_safety_fsyncs</tt> can be used here.
+ only_metadata = 2, //!< Cache reads and writes of metadata but avoid caching data (<tt>O_DIRECT</tt>), thus i/o here does not affect other cached data for other handles. Align all i/o to 4Kb boundaries for this to work.
+ reads = 3, //!< Cache reads only. Writes of data and metadata do not complete until reaching storage (<tt>O_SYNC</tt>). <tt>flag_disable_safety_fsyncs</tt> can be used here.
+ reads_and_metadata = 5, //!< Cache reads and writes of metadata, but writes of data do not complete until reaching storage (<tt>O_DSYNC</tt>). <tt>flag_disable_safety_fsyncs</tt> can be used here.
+ all = 4, //!< Cache reads and writes of data and metadata so they complete immediately, sending writes to storage at some point when the kernel decides (this is the default file system caching on a system).
+ safety_fsyncs = 7, //!< Cache reads and writes of data and metadata so they complete immediately, but issue safety fsyncs at certain points. See documentation for <tt>flag_disable_safety_fsyncs</tt>.
+ temporary = 6 //!< Cache reads and writes of data and metadata so they complete immediately, only sending any updates to storage on last handle close in the system or if memory becomes tight as this file is expected to be temporary (Windows and FreeBSD only).
+ // NOTE: IF UPDATING THIS UPDATE THE std::ostream PRINTER BELOW!!!
+ };
+ //! Bitwise flags which can be specified
+ QUICKCPPLIB_BITFIELD_BEGIN(flag)
+ {
+ none = 0, //!< No flags
+ /*! Unlinks the file on handle close. On POSIX, this simply unlinks whatever is pointed
+ to by `path()` upon the call of `close()` if and only if the inode matches. On Windows,
+ if you are on Windows 10 1709 or later, exactly the same thing occurs. If on previous
+ editions of Windows, the file entry does not disappears but becomes unavailable for
+ anyone else to open with an `errc::resource_unavailable_try_again` error return. Because this is confusing, unless the
+ `win_disable_unlink_emulation` flag is also specified, this POSIX behaviour is
+ somewhat emulated by AFIO on older Windows by renaming the file to a random name on `close()`
+ causing it to appear to have been unlinked immediately.
+ */
+ unlink_on_first_close = 1U << 0U,
+
+ /*! Some kernel caching modes have unhelpfully inconsistent behaviours
+ in getting your data onto storage, so by default unless this flag is
+ specified AFIO adds extra fsyncs to the following operations for the
+ caching modes specified below:
+ * truncation of file length either explicitly or during file open.
+ * closing of the handle either explicitly or in the destructor.
+
+ Additionally on Linux only to prevent loss of file metadata:
+ * On the parent directory whenever a file might have been created.
+ * On the parent directory on file close.
+
+ This only occurs for these kernel caching modes:
+ * caching::none
+ * caching::reads
+ * caching::reads_and_metadata
+ * caching::safety_fsyncs
+ */
+ disable_safety_fsyncs = 1U << 2U,
+ /*! `file_handle::unlink()` could accidentally delete the wrong file if someone has
+ renamed the open file handle since the time it was opened. To prevent this occuring,
+ where the OS doesn't provide race free unlink-by-open-handle we compare the inode of
+ the path we are about to unlink with that of the open handle before unlinking.
+ \warning This does not prevent races where in between the time of checking the inode
+ and executing the unlink a third party changes the item about to be unlinked. Only
+ operating systems with a true race-free unlink syscall are race free.
+ */
+ disable_safety_unlinks = 1U << 3U,
+ /*! Ask the OS to disable prefetching of data. This can improve random
+ i/o performance.
+ */
+ disable_prefetching = 1U << 4U,
+ /*! Ask the OS to maximise prefetching of data, possibly prefetching the entire file
+ into kernel cache. This can improve sequential i/o performance.
+ */
+ maximum_prefetching = 1U << 5U,
+
+ win_disable_unlink_emulation = 1U << 24U, //!< See the documentation for `unlink_on_first_close`
+ /*! Microsoft Windows NTFS, having been created in the late 1980s, did not originally
+ implement extents-based storage and thus could only represent sparse files via
+ efficient compression of intermediate zeros. With NTFS v3.0 (Microsoft Windows 2000),
+ a proper extents-based on-storage representation was added, thus allowing only 64Kb
+ extent chunks written to be stored irrespective of whatever the maximum file extent
+ was set to.
+
+ For various historical reasons, extents-based storage is disabled by default in newly
+ created files on NTFS, unlike in almost every other major filing system. You have to
+ explicitly "opt in" to extents-based storage.
+
+ As extents-based storage is nearly cost free on NTFS, AFIO by default opts in to
+ extents-based storage for any empty file it creates. If you don't want this, you
+ can specify this flag to prevent that happening.
+ */
+ win_disable_sparse_file_creation = 1U << 25U,
+
+ // NOTE: IF UPDATING THIS UPDATE THE std::ostream PRINTER BELOW!!!
+
+ overlapped = 1U << 28U, //!< On Windows, create any new handles with OVERLAPPED semantics
+ byte_lock_insanity = 1U << 29U, //!< Using insane POSIX byte range locks
+ anonymous_inode = 1U << 30U //!< This is an inode created with no representation on the filing system
+ }
+ QUICKCPPLIB_BITFIELD_END(flag);
+
+protected:
+ caching _caching{caching::none};
+ flag _flags{flag::none};
+ native_handle_type _v;
+
+public:
+ //! Default constructor
+ constexpr handle() {} // NOLINT
+ //! Construct a handle from a supplied native handle
+ explicit constexpr handle(native_handle_type h, caching caching = caching::none, flag flags = flag::none) noexcept : _caching(caching), _flags(flags), _v(std::move(h)) {}
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~handle();
+ //! No copy construction (use clone())
+ handle(const handle &) = delete;
+ //! No copy assignment
+ handle &operator=(const handle &o) = delete;
+ //! Move the handle.
+ constexpr handle(handle &&o) noexcept : _caching(o._caching), _flags(o._flags), _v(std::move(o._v))
+ {
+ o._caching = caching::none;
+ o._flags = flag::none;
+ o._v = native_handle_type();
+ }
+ //! Move assignment of handle
+ handle &operator=(handle &&o) noexcept
+ {
+ this->~handle();
+ new(this) handle(std::move(o));
+ return *this;
+ }
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(handle &o) noexcept
+ {
+ handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! Returns the current path of the open handle as said by the operating system. Note
+ that you are NOT guaranteed that any path refreshed bears any resemblance to the original,
+ some operating systems will return some different path which still reaches the same inode
+ via some other route e.g. hardlinks, dereferenced symbolic links, etc. Windows and
+ Linux correctly track changes to the specific path the handle was opened with,
+ not getting confused by other hard links. MacOS nearly gets it right, but under some
+ circumstances e.g. renaming may switch to a different hard link's path which is almost
+ certainly a bug.
+
+ If AFIO was not able to determine the current path for this open handle e.g. the inode
+ has been unlinked, it returns an empty path. Be aware that FreeBSD can return an empty
+ (deleted) path for file inodes no longer cached by the kernel path cache, AFIO cannot
+ detect the difference. FreeBSD will also return any path leading to the inode if it is
+ hard linked. FreeBSD does implement path retrieval for directory inodes
+ correctly however, and see `algorithm::stablized_path<T>` for a handle adapter which
+ makes use of that.
+
+ On Linux if `/proc` is not mounted, this call fails with an error. All APIs in AFIO
+ which require the use of `current_path()` can be told to not use it e.g. `flag::disable_safety_unlinks`.
+ It is up to you to detect if `current_path()` is not working, and to change how you
+ call AFIO appropriately.
+
+ \warning This call is expensive, it always asks the kernel for the current path, and no
+ checking is done to ensure what the kernel returns is accurate or even sensible.
+ Be aware that despite these precautions, paths are unstable and **can change randomly at
+ any moment**. Most code written to use absolute file systems paths is **racy**, so don't
+ do it, use `path_handle` to fix a base location on the file system and work from that anchor
+ instead!
+
+ \mallocs At least one malloc for the `path_type`, likely several more.
+ \sa `algorithm::cached_parent_handle_adapter<T>` which overrides this with an
+ implementation based on retrieving the current path of a cached handle to the parent
+ directory. On platforms with instability or failure to retrieve the correct current path
+ for regular files, the cached parent handle adapter works around the problem by
+ taking advantage of directory inodes not having the same instability problems on any
+ platform.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_type> current_path() const noexcept;
+ //! Immediately close the native handle type managed by this handle
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept;
+ /*! Clone this handle (copy constructor is disabled to avoid accidental copying)
+
+ \errors Any of the values POSIX dup() or DuplicateHandle() can return.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<handle> clone() const noexcept;
+ //! Release the native handle type managed by this handle
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept
+ {
+ native_handle_type ret(std::move(_v));
+ return ret;
+ }
+
+ //! True if the handle is valid (and usually open)
+ bool is_valid() const noexcept { return _v.is_valid(); }
+
+ //! True if the handle is readable
+ bool is_readable() const noexcept { return _v.is_readable(); }
+ //! True if the handle is writable
+ bool is_writable() const noexcept { return _v.is_writable(); }
+ //! True if the handle is append only
+ bool is_append_only() const noexcept { return _v.is_append_only(); }
+ /*! Changes whether this handle is append only or not.
+
+ \warning On Windows this is implemented as a bit of a hack to make it fast like on POSIX,
+ so make sure you open the handle for read/write originally. Note unlike on POSIX the
+ append_only disposition will be the only one toggled, seekable and readable will remain
+ turned on.
+
+ \errors Whatever POSIX fcntl() returns. On Windows nothing is changed on the handle.
+ \mallocs No memory allocation.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> set_append_only(bool enable) noexcept;
+
+ //! True if overlapped
+ bool is_overlapped() const noexcept { return _v.is_overlapped(); }
+ //! True if seekable
+ bool is_seekable() const noexcept { return _v.is_seekable(); }
+ //! True if requires aligned i/o
+ bool requires_aligned_io() const noexcept { return _v.requires_aligned_io(); }
+
+ //! True if a regular file or device
+ bool is_regular() const noexcept { return _v.is_regular(); }
+ //! True if a directory
+ bool is_directory() const noexcept { return _v.is_directory(); }
+ //! True if a symlink
+ bool is_symlink() const noexcept { return _v.is_symlink(); }
+ //! True if a multiplexer like BSD kqueues, Linux epoll or Windows IOCP
+ bool is_multiplexer() const noexcept { return _v.is_multiplexer(); }
+ //! True if a process
+ bool is_process() const noexcept { return _v.is_process(); }
+ //! True if a memory section
+ bool is_section() const noexcept { return _v.is_section(); }
+
+ //! Kernel cache strategy used by this handle
+ caching kernel_caching() const noexcept { return _caching; }
+ //! True if the handle uses the kernel page cache for reads
+ bool are_reads_from_cache() const noexcept { return _caching != caching::none && _caching != caching::only_metadata; }
+ //! True if writes are safely on storage on completion
+ bool are_writes_durable() const noexcept { return _caching == caching::none || _caching == caching::reads || _caching == caching::reads_and_metadata; }
+ //! True if issuing safety fsyncs is on
+ bool are_safety_fsyncs_issued() const noexcept { return !(_flags & flag::disable_safety_fsyncs) && !((static_cast<unsigned>(_caching) & 1U) == 0U); }
+
+ //! The flags this handle was opened with
+ flag flags() const noexcept { return _flags; }
+ //! The native handle used by this handle
+ native_handle_type native_handle() const noexcept { return _v; }
+};
+inline std::ostream &operator<<(std::ostream &s, const handle &v)
+{
+ if(v.is_valid())
+ {
+ auto _currentpath = v.current_path();
+ std::string currentpath = !_currentpath ? std::string(_currentpath.error().message().c_str()) : _currentpath.value().u8string();
+ return s << "afio::handle(" << v._v._init << ", " << currentpath << ")";
+ }
+ return s << "afio::handle(closed)";
+}
+inline std::ostream &operator<<(std::ostream &s, const handle::mode &v)
+{
+ static constexpr const char *values[] = {"unchanged", nullptr, "none", nullptr, "attr_read", "attr_write", "read", "write", nullptr, "append"};
+ if(static_cast<size_t>(v) >= sizeof(values) / sizeof(values[0]) || (values[static_cast<size_t>(v)] == nullptr)) // NOLINT
+ {
+ return s << "afio::handle::mode::<unknown>";
+ }
+ return s << "afio::handle::mode::" << values[static_cast<size_t>(v)]; // NOLINT
+}
+inline std::ostream &operator<<(std::ostream &s, const handle::creation &v)
+{
+ static constexpr const char *values[] = {"open_existing", "only_if_not_exist", "if_needed", "truncate"};
+ if(static_cast<size_t>(v) >= sizeof(values) / sizeof(values[0]) || (values[static_cast<size_t>(v)] == nullptr)) // NOLINT
+ {
+ return s << "afio::handle::creation::<unknown>";
+ }
+ return s << "afio::handle::creation::" << values[static_cast<size_t>(v)]; // NOLINT
+}
+inline std::ostream &operator<<(std::ostream &s, const handle::caching &v)
+{
+ static constexpr const char *values[] = {"unchanged", "none", "only_metadata", "reads", "all", "reads_and_metadata", "temporary", "safety_fsyncs"};
+ if(static_cast<size_t>(v) >= sizeof(values) / sizeof(values[0]) || (values[static_cast<size_t>(v)] == nullptr)) // NOLINT
+ {
+ return s << "afio::handle::caching::<unknown>";
+ }
+ return s << "afio::handle::caching::" << values[static_cast<size_t>(v)]; // NOLINT
+}
+inline std::ostream &operator<<(std::ostream &s, const handle::flag &v)
+{
+ std::string temp;
+ if(!!(v & handle::flag::unlink_on_first_close))
+ {
+ temp.append("unlink_on_first_close|");
+ }
+ if(!!(v & handle::flag::disable_safety_fsyncs))
+ {
+ temp.append("disable_safety_fsyncs|");
+ }
+ if(!!(v & handle::flag::disable_prefetching))
+ {
+ temp.append("disable_prefetching|");
+ }
+ if(!!(v & handle::flag::maximum_prefetching))
+ {
+ temp.append("maximum_prefetching|");
+ }
+ if(!!(v & handle::flag::win_disable_unlink_emulation))
+ {
+ temp.append("win_disable_unlink_emulation|");
+ }
+ if(!!(v & handle::flag::win_disable_sparse_file_creation))
+ {
+ temp.append("win_disable_sparse_file_creation|");
+ }
+ if(!!(v & handle::flag::overlapped))
+ {
+ temp.append("overlapped|");
+ }
+ if(!!(v & handle::flag::byte_lock_insanity))
+ {
+ temp.append("byte_lock_insanity|");
+ }
+ if(!!(v & handle::flag::anonymous_inode))
+ {
+ temp.append("anonymous_inode|");
+ }
+ if(!temp.empty())
+ {
+ temp.resize(temp.size() - 1);
+ if(std::count(temp.cbegin(), temp.cend(), '|') > 0)
+ {
+ temp = "(" + temp + ")";
+ }
+ }
+ else
+ {
+ temp = "none";
+ }
+ return s << "afio::handle::flag::" << temp;
+}
+
+/*! \brief Metaprogramming shim for constructing any `handle` subclass.
+
+Each `handle` implementation provides one or more static member functions used to construct it.
+Each of these has a descriptive, unique name so it can be used as a free function which is
+convenient and intuitive for human programmers.
+
+This design pattern is however inconvenient for generic code which needs a single way
+of constructing some arbitrary unknown `handle` implementation. This shim function
+provides that.
+*/
+template <class T> struct construct
+{
+ result<T> operator()() const noexcept { static_assert(!std::is_same<T, T>::value, "construct<T>() was not specialised for the type T supplied"); }
+};
+
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+namespace detail
+{
+ template <class Dest, class Src> inline void fill_failure_info(Dest &dest, const Src &src)
+ {
+ (void) src;
+ auto &tls = detail::tls_errored_results();
+ if(!tls.reentering_self)
+ {
+ handle *currenth = tls.current_handle;
+ native_handle_type nativeh;
+ if(currenth != nullptr)
+ {
+ nativeh = currenth->native_handle();
+ // This may fail, if it does it will construct an error_info thus reentering ourselves. Prevent that.
+ tls.reentering_self = true;
+ auto currentpath_ = currenth->current_path();
+ tls.reentering_self = false;
+ if(currentpath_)
+ {
+ auto currentpath = currentpath_.value().u8string();
+ dest._thread_id = tls.this_thread_id;
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4996) // the function may be unsafe
+#endif
+ strncpy(tls.next(dest._tls_path_id1), QUICKCPPLIB_NAMESPACE::ringbuffer_log::last190(currentpath), 190);
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ dest._tls_path_id2 = dest._tls_path_id1 - 17; // guaranteed invalid
+ }
+ }
+#if AFIO_LOGGING_LEVEL >= 2
+ if(log().log_level() >= log_level::error)
+ {
+ dest._log_id = log().emplace_back(log_level::error, src.message().c_str(), static_cast<uint32_t>(nativeh._init), tls.this_thread_id);
+ }
+#endif
+ }
+ }
+}
+#endif
+
+#if AFIO_EXPERIMENTAL_STATUS_CODE
+
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+
+//! Helper for constructing an error code from an errc
+inline error_code generic_error(errc c)
+{
+ SYSTEM_ERROR2_NAMESPACE::status_code<error_domain<SYSTEM_ERROR2_NAMESPACE::generic_code::domain_type>> sc(c);
+ if(sc.failure())
+ {
+ detail::fill_failure_info(sc.value(), sc);
+ }
+ return sc;
+}
+#ifndef _WIN32
+//! Helper for constructing an error code from a POSIX errno
+inline error_code posix_error(int c)
+{
+ SYSTEM_ERROR2_NAMESPACE::status_code<error_domain<SYSTEM_ERROR2_NAMESPACE::posix_code::domain_type>> sc(c);
+ if(sc.failure())
+ {
+ detail::fill_failure_info(sc.value(), sc);
+ }
+ return sc;
+}
+#else
+//! Helper for constructing an error code from a DWORD
+inline error_code win32_error(SYSTEM_ERROR2_NAMESPACE::win32::DWORD c)
+{
+ SYSTEM_ERROR2_NAMESPACE::status_code<error_domain<SYSTEM_ERROR2_NAMESPACE::win32_code::domain_type>> sc(c);
+ if(sc.failure())
+ {
+ detail::fill_failure_info(sc.value(), sc);
+ }
+ return sc;
+}
+//! Helper for constructing an error code from a NTSTATUS
+inline error_code ntkernel_error(SYSTEM_ERROR2_NAMESPACE::win32::NTSTATUS c)
+{
+ SYSTEM_ERROR2_NAMESPACE::status_code<error_domain<SYSTEM_ERROR2_NAMESPACE::nt_code::domain_type>> sc(c);
+ if(sc.failure())
+ {
+ detail::fill_failure_info(sc.value(), sc);
+ }
+ return sc;
+}
+#endif
+
+#endif
+
+#else // AFIO_EXPERIMENTAL_STATUS_CODE
+
+// failure_info is defined in config.hpp, this is its constructor which needs
+// to be defined here so that we have handle's definition available
+inline error_info::error_info(std::error_code _ec)
+ : ec(_ec)
+{
+// Here is a VERY useful place to breakpoint!
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+ if(ec)
+ {
+ detail::fill_failure_info(*this, this->ec);
+ }
+#endif
+}
+#endif // AFIO_EXPERIMENTAL_STATUS_CODE
+
+// Define how we log handles and subclasses thereof
+namespace detail
+{
+ template <class T> void log_inst_to_info(const handle *inst, const char *buffer)
+ {
+ (void) inst;
+ (void) buffer;
+ AFIO_LOG_INFO(inst->native_handle()._init, buffer);
+ }
+}
+
+// BEGIN make_free_functions.py
+//! Swap with another instance
+inline void swap(handle &self, handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+//! Immediately close the native handle type managed by this handle
+inline result<void> close(handle &self) noexcept
+{
+ return self.close();
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/handle.ipp"
+#else
+#include "detail/impl/posix/handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/io_handle.hpp b/include/llfio/v2.0/io_handle.hpp
new file mode 100644
index 00000000..2ada9efa
--- /dev/null
+++ b/include/llfio/v2.0/io_handle.hpp
@@ -0,0 +1,611 @@
+/* A handle to something
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_IO_HANDLE_H
+#define AFIO_IO_HANDLE_H
+
+#include "handle.hpp"
+
+//! \file io_handle.hpp Provides i/o handle
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+/*! \class io_handle
+\brief A handle to something capable of scatter-gather i/o.
+*/
+class AFIO_DECL io_handle : public handle
+{
+public:
+ using path_type = handle::path_type;
+ using extent_type = handle::extent_type;
+ using size_type = handle::size_type;
+ using mode = handle::mode;
+ using creation = handle::creation;
+ using caching = handle::caching;
+ using flag = handle::flag;
+
+ //! The scatter buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`.
+ struct buffer_type
+ {
+ //! Type of the pointer to memory.
+ using pointer = byte *;
+ //! Type of the iterator to memory.
+ using iterator = byte *;
+ //! Type of the iterator to memory.
+ using const_iterator = const byte *;
+ //! Type of the length of memory.
+ using size_type = size_t;
+
+ //! Pointer to memory to be filled by a read. Try to make this 64 byte, or ideally, `page_size()` aligned where possible.
+ pointer data;
+ //! The number of bytes to fill into this address. Try to make this a 64 byte multiple, or ideally, a whole multiple of `page_size()`.
+ size_type len;
+
+ //! Returns an iterator to the beginning of the buffer
+ constexpr iterator begin() { return data; }
+ //! Returns an iterator to the beginning of the buffer
+ constexpr const_iterator begin() const { return data; }
+ //! Returns an iterator to the beginning of the buffer
+ constexpr const_iterator cbegin() const { return data; }
+ //! Returns an iterator to after the end of the buffer
+ constexpr iterator end() { return data + len; }
+ //! Returns an iterator to after the end of the buffer
+ constexpr const_iterator end() const { return data + len; }
+ //! Returns an iterator to after the end of the buffer
+ constexpr const_iterator cend() const { return data + len; }
+ };
+ //! The gather buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`.
+ struct const_buffer_type
+ {
+ //! Type of the pointer to memory.
+ using pointer = const byte *;
+ //! Type of the iterator to memory.
+ using iterator = const byte *;
+ //! Type of the iterator to memory.
+ using const_iterator = const byte *;
+ //! Type of the length of memory.
+ using size_type = size_t;
+
+ //! Pointer to memory to be written. Try to make this 64 byte, or ideally, `page_size()` aligned where possible.
+ pointer data;
+ //! The number of bytes to write from this address. Try to make this a 64 byte multiple, or ideally, a whole multiple of `page_size()`.
+ size_type len;
+
+ //! Returns an iterator to the beginning of the buffer
+ constexpr iterator begin() { return data; }
+ //! Returns an iterator to the beginning of the buffer
+ constexpr const_iterator begin() const { return data; }
+ //! Returns an iterator to the beginning of the buffer
+ constexpr const_iterator cbegin() const { return data; }
+ //! Returns an iterator to after the end of the buffer
+ constexpr iterator end() { return data + len; }
+ //! Returns an iterator to after the end of the buffer
+ constexpr const_iterator end() const { return data + len; }
+ //! Returns an iterator to after the end of the buffer
+ constexpr const_iterator cend() const { return data + len; }
+ };
+#ifndef NDEBUG
+ static_assert(std::is_trivial<buffer_type>::value, "buffer_type is not a trivial type!");
+ static_assert(std::is_trivial<const_buffer_type>::value, "const_buffer_type is not a trivial type!");
+ static_assert(std::is_standard_layout<buffer_type>::value, "buffer_type is not a standard layout type!");
+ static_assert(std::is_standard_layout<const_buffer_type>::value, "const_buffer_type is not a standard layout type!");
+#endif
+ //! The scatter buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ using buffers_type = span<buffer_type>;
+ //! The gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ using const_buffers_type = span<const_buffer_type>;
+#ifndef NDEBUG
+ // Is trivial in all ways, except default constructibility
+ static_assert(std::is_trivially_copyable<buffers_type>::value, "buffers_type is not trivially copyable!");
+ // static_assert(std::is_trivially_assignable<buffers_type, buffers_type>::value, "buffers_type is not trivially assignable!");
+ // static_assert(std::is_trivially_destructible<buffers_type>::value, "buffers_type is not trivially destructible!");
+ // static_assert(std::is_trivially_copy_constructible<buffers_type>::value, "buffers_type is not trivially copy constructible!");
+ // static_assert(std::is_trivially_move_constructible<buffers_type>::value, "buffers_type is not trivially move constructible!");
+ // static_assert(std::is_trivially_copy_assignable<buffers_type>::value, "buffers_type is not trivially copy assignable!");
+ // static_assert(std::is_trivially_move_assignable<buffers_type>::value, "buffers_type is not trivially move assignable!");
+ static_assert(std::is_standard_layout<buffers_type>::value, "buffers_type is not a standard layout type!");
+#endif
+ //! The i/o request type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ template <class T> struct io_request
+ {
+ T buffers{};
+ extent_type offset{0};
+ constexpr io_request() {} // NOLINT (defaulting this breaks clang and GCC, so don't do it!)
+ constexpr io_request(T _buffers, extent_type _offset)
+ : buffers(std::move(_buffers))
+ , offset(_offset)
+ {
+ }
+ };
+#ifndef NDEBUG
+ // Is trivial in all ways, except default constructibility
+ static_assert(std::is_trivially_copyable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copyable!");
+ // static_assert(std::is_trivially_assignable<io_request<buffers_type>, io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially assignable!");
+ // static_assert(std::is_trivially_destructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially destructible!");
+ // static_assert(std::is_trivially_copy_constructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copy constructible!");
+ // static_assert(std::is_trivially_move_constructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially move constructible!");
+ // static_assert(std::is_trivially_copy_assignable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copy assignable!");
+ // static_assert(std::is_trivially_move_assignable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially move assignable!");
+ static_assert(std::is_standard_layout<io_request<buffers_type>>::value, "io_request<buffers_type> is not a standard layout type!");
+#endif
+ //! The i/o result type used by this handle. Guaranteed to be `TrivialType` apart from construction..
+ template <class T> struct io_result : public AFIO_V2_NAMESPACE::result<T>
+ {
+ using Base = AFIO_V2_NAMESPACE::result<T>;
+ size_type _bytes_transferred{static_cast<size_type>(-1)};
+
+#if defined(_MSC_VER) && !defined(__clang__) // workaround MSVC parsing bug
+ constexpr io_result()
+ : Base()
+ {
+ }
+ template <class... Args>
+ constexpr io_result(Args &&... args)
+ : Base(std::forward<Args>(args)...)
+ {
+ }
+#else
+ using Base::Base;
+ io_result() = default;
+#endif
+ ~io_result() = default;
+ io_result(const io_result &) = default;
+ io_result(io_result &&) = default; // NOLINT
+ io_result &operator=(const io_result &) = default;
+ io_result &operator=(io_result &&) = default; // NOLINT
+ //! Returns bytes transferred
+ size_type bytes_transferred() noexcept
+ {
+ if(_bytes_transferred == static_cast<size_type>(-1))
+ {
+ _bytes_transferred = 0;
+ for(auto &i : this->value())
+ {
+ _bytes_transferred += i.second;
+ }
+ }
+ return _bytes_transferred;
+ }
+ };
+#ifndef NDEBUG
+ // Is trivial in all ways, except default constructibility
+ static_assert(std::is_trivially_copyable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copyable!");
+// static_assert(std::is_trivially_assignable<io_result<buffers_type>, io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially assignable!");
+// static_assert(std::is_trivially_destructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially destructible!");
+// static_assert(std::is_trivially_copy_constructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copy constructible!");
+// static_assert(std::is_trivially_move_constructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially move constructible!");
+// static_assert(std::is_trivially_copy_assignable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copy assignable!");
+// static_assert(std::is_trivially_move_assignable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially move assignable!");
+//! \todo Why is io_result<buffers_type> not a standard layout type?
+// static_assert(std::is_standard_layout<result<buffers_type>>::value, "result<buffers_type> is not a standard layout type!");
+// static_assert(std::is_standard_layout<io_result<buffers_type>>::value, "io_result<buffers_type> is not a standard layout type!");
+#endif
+
+public:
+ //! Default constructor
+ constexpr io_handle() {} // NOLINT
+ ~io_handle() = default;
+ //! Construct a handle from a supplied native handle
+ constexpr explicit io_handle(native_handle_type h, caching caching = caching::none, flag flags = flag::none)
+ : handle(h, caching, flags)
+ {
+ }
+ //! Explicit conversion from handle permitted
+ explicit constexpr io_handle(handle &&o) noexcept : handle(std::move(o)) {}
+ //! Move construction permitted
+ io_handle(io_handle &&) = default;
+ //! No copy construction (use `clone()`)
+ io_handle(const io_handle &) = delete;
+ //! Move assignment permitted
+ io_handle &operator=(io_handle &&) = default;
+ //! No copy assignment
+ io_handle &operator=(const io_handle &) = delete;
+
+ /*! \brief The *maximum* number of buffers which a single read or write syscall can process at a time
+ for this specific open handle. On POSIX, this is known as `IOV_MAX`.
+
+ Note that the actual number of buffers accepted for a read or a write may be significantly
+ lower than this system-defined limit, depending on available resources. The `read()` or `write()`
+ call will return the buffers accepted.
+
+ Note also that some OSs will error out if you supply more than this limit to `read()` or `write()`,
+ but other OSs do not. Some OSs guarantee that each i/o syscall has effects atomically visible or not
+ to other i/o, other OSs do not.
+
+ Microsoft Windows and OS X does not implement scatter-gather file i/o syscalls.
+ Thus this function will always return `1` in that situation.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t max_buffers() const noexcept;
+
+ /*! \brief Read data from the open handle.
+
+ \warning Depending on the implementation backend, **very** different buffers may be returned than you
+ supplied. You should **always** use the buffers returned and assume that they point to different
+ memory and that each buffer's size will have changed.
+
+ \return The buffers read, which may not be the buffers input. The size of each scatter-gather
+ buffer is updated with the number of bytes of that buffer transferred, and the pointer to
+ the data may be \em completely different to what was submitted (e.g. it may point into a
+ memory map).
+ \param reqs A scatter-gather and offset request.
+ \param d An optional deadline by which the i/o must complete, else it is cancelled.
+ Note function may return significantly after this deadline if the i/o takes long to cancel.
+ \errors Any of the values POSIX read() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be
+ returned if deadline i/o is not possible with this particular handle configuration (e.g.
+ reading from regular files on POSIX or reading from a non-overlapped HANDLE on Windows).
+ \mallocs The default synchronous implementation in file_handle performs no memory allocation.
+ The asynchronous implementation in async_file_handle performs one calloc and one free.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept;
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ io_result<buffers_type> read(extent_type offset, std::initializer_list<buffer_type> lst, deadline d = deadline()) noexcept
+ {
+ buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
+ memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
+ io_request<buffers_type> reqs(buffers_type(_reqs, lst.size()), offset);
+ return read(reqs, d);
+ }
+
+ /*! \brief Write data to the open handle.
+
+ \warning Depending on the implementation backend, not all of the buffers input may be written and
+ the some buffers at the end of the returned buffers may return with zero bytes written.
+ For example, with a zeroed deadline, some backends may only consume as many buffers as the system has available write slots
+ for, thus for those backends this call is "non-blocking" in the sense that it will return immediately even if it
+ could not schedule a single buffer write. Another example is that some implementations will not
+ auto-extend the length of a file when a write exceeds the maximum extent, you will need to issue
+ a `truncate(newsize)` first.
+
+ \return The buffers written, which may not be the buffers input. The size of each scatter-gather
+ buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d An optional deadline by which the i/o must complete, else it is cancelled.
+ Note function may return significantly after this deadline if the i/o takes long to cancel.
+ \errors Any of the values POSIX write() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be
+ returned if deadline i/o is not possible with this particular handle configuration (e.g.
+ writing to regular files on POSIX or writing to a non-overlapped HANDLE on Windows).
+ \mallocs The default synchronous implementation in file_handle performs no memory allocation.
+ The asynchronous implementation in async_file_handle performs one calloc and one free.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept;
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ io_result<const_buffers_type> write(extent_type offset, std::initializer_list<const_buffer_type> lst, deadline d = deadline()) noexcept
+ {
+ const_buffer_type *_reqs = reinterpret_cast<const_buffer_type *>(alloca(sizeof(const_buffer_type) * lst.size()));
+ memcpy(_reqs, lst.begin(), sizeof(const_buffer_type) * lst.size());
+ io_request<const_buffers_type> reqs(const_buffers_type(_reqs, lst.size()), offset);
+ return write(reqs, d);
+ }
+
+ /*! \brief Issue a write reordering barrier such that writes preceding the barrier will reach storage
+ before writes after this barrier.
+
+ \warning **Assume that this call is a no-op**. It is not reliably implemented in many common use cases,
+ for example if your code is running inside a LXC container, or if the user has mounted the filing
+ system with non-default options. Instead open the handle with `caching::reads` which means that all
+ writes form a strict sequential order not completing until acknowledged by the storage device.
+ Filing system can and do use different algorithms to give much better performance with `caching::reads`,
+ some (e.g. ZFS) spectacularly better.
+
+ \warning Let me repeat again: consider this call to be a **hint** to poke the kernel with a stick to
+ go start to do some work sooner rather than later. **It may be ignored entirely**.
+
+ \warning For portability, you can only assume that barriers write order for a single handle
+ instance. You cannot assume that barriers write order across multiple handles to the same inode, or
+ across processes.
+
+ \return The buffers barriered, which may not be the buffers input. The size of each scatter-gather
+ buffer is updated with the number of bytes of that buffer barriered.
+ \param reqs A scatter-gather and offset request for what range to barrier. May be ignored on some platforms
+ which always write barrier the entire file. Supplying a default initialised reqs write barriers the entire file.
+ \param wait_for_device True if you want the call to wait until data reaches storage and that storage
+ has acknowledged the data is physically written. Slow.
+ \param and_metadata True if you want the call to sync the metadata for retrieving the writes before the
+ barrier after a sudden power loss event. Slow. Setting this to false enables much faster performance,
+ especially on non-volatile memory.
+ \param d An optional deadline by which the i/o must complete, else it is cancelled.
+ Note function may return significantly after this deadline if the i/o takes long to cancel.
+ \errors Any of the values POSIX fdatasync() or Windows NtFlushBuffersFileEx() can return.
+ \mallocs None.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ virtual io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept = 0;
+
+ /*! \class extent_guard
+ \brief RAII holder a locked extent of bytes in a file.
+ */
+ class extent_guard
+ {
+ friend class io_handle;
+ io_handle *_h{nullptr};
+ extent_type _offset{0}, _length{0};
+ bool _exclusive{false};
+ constexpr extent_guard(io_handle *h, extent_type offset, extent_type length, bool exclusive)
+ : _h(h)
+ , _offset(offset)
+ , _length(length)
+ , _exclusive(exclusive)
+ {
+ }
+
+ public:
+ extent_guard(const extent_guard &) = delete;
+ extent_guard &operator=(const extent_guard &) = delete;
+
+ //! Default constructor
+ constexpr extent_guard() {} // NOLINT
+ //! Move constructor
+ extent_guard(extent_guard &&o) noexcept : _h(o._h), _offset(o._offset), _length(o._length), _exclusive(o._exclusive) { o.release(); }
+ //! Move assign
+ extent_guard &operator=(extent_guard &&o) noexcept
+ {
+ unlock();
+ _h = o._h;
+ _offset = o._offset;
+ _length = o._length;
+ _exclusive = o._exclusive;
+ o.release();
+ return *this;
+ }
+ ~extent_guard()
+ {
+ if(_h != nullptr)
+ {
+ unlock();
+ }
+ }
+ //! True if extent guard is valid
+ explicit operator bool() const noexcept { return _h != nullptr; }
+ //! True if extent guard is invalid
+ bool operator!() const noexcept { return _h == nullptr; }
+
+ //! The io_handle to be unlocked
+ io_handle *handle() const noexcept { return _h; }
+ //! Sets the io_handle to be unlocked
+ void set_handle(io_handle *h) noexcept { _h = h; }
+ //! The extent to be unlocked
+ std::tuple<extent_type, extent_type, bool> extent() const noexcept { return std::make_tuple(_offset, _length, _exclusive); }
+
+ //! Unlocks the locked extent immediately
+ void unlock() noexcept
+ {
+ if(_h != nullptr)
+ {
+ _h->unlock(_offset, _length);
+ release();
+ }
+ }
+
+ //! Detach this RAII unlocker from the locked state
+ void release() noexcept
+ {
+ _h = nullptr;
+ _offset = 0;
+ _length = 0;
+ _exclusive = false;
+ }
+ };
+
+ /*! \brief Tries to lock the range of bytes specified for shared or exclusive access. Be aware this passes through
+ the same semantics as the underlying OS call, including any POSIX insanity present on your platform:
+
+ - Any fd closed on an inode must release all byte range locks on that inode for all
+ other fds. If your OS isn't new enough to support the non-insane lock API, `flag::byte_lock_insanity` will be set
+ in flags() after the first call to this function.
+ - Threads replace each other's locks, indeed locks replace each other's locks.
+
+ You almost cetainly should use your choice of an `algorithm::shared_fs_mutex::*` instead of this
+ as those are more portable and performant.
+
+ \warning This is a low-level API which you should not use directly in portable code. Another issue is that
+ atomic lock upgrade/downgrade, if your platform implements that (you should assume it does not in
+ portable code), means that on POSIX you need to *release* the old `extent_guard` after creating a new one over the
+ same byte range, otherwise the old `extent_guard`'s destructor will simply unlock the range entirely. On
+ Windows however upgrade/downgrade locks overlay, so on that platform you must *not* release the old
+ `extent_guard`. Look into `algorithm::shared_fs_mutex::safe_byte_ranges` for a portable solution.
+
+ \return An extent guard, the destruction of which will call unlock().
+ \param offset The offset to lock. Note that on POSIX the top bit is always cleared before use
+ as POSIX uses signed transport for offsets. If you want an advisory rather than mandatory lock
+ on Windows, one technique is to force top bit set so the region you lock is not the one you will
+ i/o - obviously this reduces maximum file size to (2^63)-1.
+ \param bytes The number of bytes to lock. Zero means lock the entire file using any more
+ efficient alternative algorithm where available on your platform (specifically, on BSD and OS X use
+ flock() for non-insane semantics).
+ \param exclusive Whether the lock is to be exclusive.
+ \param d An optional deadline by which the lock must complete, else it is cancelled.
+ \errors Any of the values POSIX fcntl() can return, `errc::timed_out`, `errc::not_supported` may be
+ returned if deadline i/o is not possible with this particular handle configuration (e.g.
+ non-overlapped HANDLE on Windows).
+ \mallocs The default synchronous implementation in file_handle performs no memory allocation.
+ The asynchronous implementation in async_file_handle performs one calloc and one free.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_guard> lock(extent_type offset, extent_type bytes, bool exclusive = true, deadline d = deadline()) noexcept;
+ //! \overload
+ result<extent_guard> try_lock(extent_type offset, extent_type bytes, bool exclusive = true) noexcept { return lock(offset, bytes, exclusive, deadline(std::chrono::seconds(0))); }
+ //! \overload Locks for shared access
+ result<extent_guard> lock(io_request<buffers_type> reqs, deadline d = deadline()) noexcept
+ {
+ size_t bytes = 0;
+ for(auto &i : reqs.buffers)
+ {
+ if(bytes + i.len < bytes)
+ {
+ return errc::value_too_large;
+ }
+ bytes += i.len;
+ }
+ return lock(reqs.offset, bytes, false, d);
+ }
+ //! \overload Locks for exclusive access
+ result<extent_guard> lock(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept
+ {
+ size_t bytes = 0;
+ for(auto &i : reqs.buffers)
+ {
+ if(bytes + i.len < bytes)
+ {
+ return errc::value_too_large;
+ }
+ bytes += i.len;
+ }
+ return lock(reqs.offset, bytes, true, d);
+ }
+
+ /*! \brief Unlocks a byte range previously locked.
+
+ \param offset The offset to unlock. This should be an offset previously locked.
+ \param bytes The number of bytes to unlock. This should be a byte extent previously locked.
+ \errors Any of the values POSIX fcntl() can return.
+ \mallocs None.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void unlock(extent_type offset, extent_type bytes) noexcept;
+};
+
+
+// BEGIN make_free_functions.py
+/*! \brief Read data from the open handle.
+
+\warning Depending on the implementation backend, **very** different buffers may be returned than you
+supplied. You should **always** use the buffers returned and assume that they point to different
+memory and that each buffer's size will have changed.
+
+\return The buffers read, which may not be the buffers input. The size of each scatter-gather
+buffer is updated with the number of bytes of that buffer transferred, and the pointer to
+the data may be \em completely different to what was submitted (e.g. it may point into a
+memory map).
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\param d An optional deadline by which the i/o must complete, else it is cancelled.
+Note function may return significantly after this deadline if the i/o takes long to cancel.
+\errors Any of the values POSIX read() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be
+returned if deadline i/o is not possible with this particular handle configuration (e.g.
+reading from regular files on POSIX or reading from a non-overlapped HANDLE on Windows).
+\mallocs The default synchronous implementation in file_handle performs no memory allocation.
+The asynchronous implementation in async_file_handle performs one calloc and one free.
+*/
+inline io_handle::io_result<io_handle::buffers_type> read(io_handle &self, io_handle::io_request<io_handle::buffers_type> reqs, deadline d = deadline()) noexcept
+{
+ return self.read(std::forward<decltype(reqs)>(reqs), std::forward<decltype(d)>(d));
+}
+//! \overload
+inline io_handle::io_result<io_handle::buffers_type> read(io_handle &self, io_handle::extent_type offset, std::initializer_list<io_handle::buffer_type> lst, deadline d = deadline()) noexcept
+{
+ return self.read(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(lst), std::forward<decltype(d)>(d));
+}
+/*! \brief Write data to the open handle.
+
+\warning Depending on the implementation backend, not all of the buffers input may be written and
+the some buffers at the end of the returned buffers may return with zero bytes written.
+For example, with a zeroed deadline, some backends may only consume as many buffers as the system has available write slots
+for, thus for those backends this call is "non-blocking" in the sense that it will return immediately even if it
+could not schedule a single buffer write. Another example is that some implementations will not
+auto-extend the length of a file when a write exceeds the maximum extent, you will need to issue
+a `truncate(newsize)` first.
+
+\return The buffers written, which may not be the buffers input. The size of each scatter-gather
+buffer is updated with the number of bytes of that buffer transferred.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\param d An optional deadline by which the i/o must complete, else it is cancelled.
+Note function may return significantly after this deadline if the i/o takes long to cancel.
+\errors Any of the values POSIX write() can return, `errc::timed_out`, `errc::operation_canceled`. `errc::not_supported` may be
+returned if deadline i/o is not possible with this particular handle configuration (e.g.
+writing to regular files on POSIX or writing to a non-overlapped HANDLE on Windows).
+\mallocs The default synchronous implementation in file_handle performs no memory allocation.
+The asynchronous implementation in async_file_handle performs one calloc and one free.
+*/
+inline io_handle::io_result<io_handle::const_buffers_type> write(io_handle &self, io_handle::io_request<io_handle::const_buffers_type> reqs, deadline d = deadline()) noexcept
+{
+ return self.write(std::forward<decltype(reqs)>(reqs), std::forward<decltype(d)>(d));
+}
+//! \overload
+inline io_handle::io_result<io_handle::const_buffers_type> write(io_handle &self, io_handle::extent_type offset, std::initializer_list<io_handle::const_buffer_type> lst, deadline d = deadline()) noexcept
+{
+ return self.write(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(lst), std::forward<decltype(d)>(d));
+}
+/*! \brief Issue a write reordering barrier such that writes preceding the barrier will reach storage
+before writes after this barrier.
+
+\warning **Assume that this call is a no-op**. It is not reliably implemented in many common use cases,
+for example if your code is running inside a LXC container, or if the user has mounted the filing
+system with non-default options. Instead open the handle with `caching::reads` which means that all
+writes form a strict sequential order not completing until acknowledged by the storage device.
+Filing system can and do use different algorithms to give much better performance with `caching::reads`,
+some (e.g. ZFS) spectacularly better.
+
+\warning Let me repeat again: consider this call to be a **hint** to poke the kernel with a stick to
+go start to do some work sooner rather than later. **It may be ignored entirely**.
+
+\warning For portability, you can only assume that barriers write order for a single handle
+instance. You cannot assume that barriers write order across multiple handles to the same inode, or
+across processes.
+
+\return The buffers barriered, which may not be the buffers input. The size of each scatter-gather
+buffer is updated with the number of bytes of that buffer barriered.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request for what range to barrier. May be ignored on some platforms
+which always write barrier the entire file. Supplying a default initialised reqs write barriers the entire file.
+\param wait_for_device True if you want the call to wait until data reaches storage and that storage
+has acknowledged the data is physically written. Slow.
+\param and_metadata True if you want the call to sync the metadata for retrieving the writes before the
+barrier after a sudden power loss event. Slow. Setting this to false enables much faster performance,
+especially on non-volatile memory.
+\param d An optional deadline by which the i/o must complete, else it is cancelled.
+Note function may return significantly after this deadline if the i/o takes long to cancel.
+\errors Any of the values POSIX fdatasync() or Windows NtFlushBuffersFileEx() can return.
+\mallocs None.
+*/
+inline io_handle::io_result<io_handle::const_buffers_type> barrier(io_handle &self, io_handle::io_request<io_handle::const_buffers_type> reqs = io_handle::io_request<io_handle::const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept
+{
+ return self.barrier(std::forward<decltype(reqs)>(reqs), std::forward<decltype(wait_for_device)>(wait_for_device), std::forward<decltype(and_metadata)>(and_metadata), std::forward<decltype(d)>(d));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/io_handle.ipp"
+#else
+#include "detail/impl/posix/io_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/io_service.hpp b/include/llfio/v2.0/io_service.hpp
new file mode 100644
index 00000000..ad898919
--- /dev/null
+++ b/include/llfio/v2.0/io_service.hpp
@@ -0,0 +1,331 @@
+/* Multiplex file i/o
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (9 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_IO_SERVICE_H
+#define AFIO_IO_SERVICE_H
+
+#include "handle.hpp"
+
+#include <cassert>
+#include <deque>
+#include <mutex>
+
+#if defined(__cpp_coroutines)
+// clang-format off
+#if defined(__has_include)
+#if __has_include(<coroutine>)
+#include <coroutine>
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+template<class T = void> using coroutine_handle = std::coroutine_handle<T>;
+AFIO_V2_NAMESPACE_END
+#elif __has_include(<experimental/coroutine>)
+#include <experimental/coroutine>
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+template<class T = void> using coroutine_handle = std::experimental::coroutine_handle<T>;
+AFIO_V2_NAMESPACE_END
+#else
+#error Cannot use C++ Coroutines without the <coroutine> header!
+#endif
+#endif
+// clang-format on
+#endif
+
+#undef _threadid // windows macro splosh sigh
+
+//! \file io_service.hpp Provides io_service.
+
+// Need to decide which kind of POSIX AIO to use
+#ifndef _WIN32
+// Right now the only thing we support is POSIX AIO
+#if !defined(AFIO_USE_POSIX_AIO)
+/*! \brief Undefined to autodetect, 1 to use POSIX AIO, 0 to not use
+
+\warning On FreeBSD the AIO kernel module needs to be loaded for POSIX AIO to work.
+Run as root 'kldload aio' or add 'aio_load=YES' in loader.conf.
+*/
+#define AFIO_USE_POSIX_AIO 1
+#endif
+// BSD kqueues not implemented yet
+//# if defined(__FreeBSD__) && !defined(AFIO_COMPILE_KQUEUES)
+//# define AFIO_COMPILE_KQUEUES 1
+//# endif
+#if AFIO_COMPILE_KQUEUES
+#if defined(AFIO_USE_POSIX_AIO) && !AFIO_USE_POSIX_AIO
+#error BSD kqueues must be combined with POSIX AIO!
+#endif
+#if !defined(AFIO_USE_POSIX_AIO)
+#define AFIO_USE_POSIX_AIO 1
+#endif
+#endif
+#if DOXYGEN_SHOULD_SKIP_THIS
+//! Undefined to autodetect, 1 to compile in BSD kqueue support, 0 to leave it out
+#define AFIO_COMPILE_KQUEUES 0
+#endif
+
+#if AFIO_USE_POSIX_AIO
+// We'll be using POSIX AIO and signal based interruption for post()
+#include <csignal>
+// Do we have realtime signals?
+#if !defined(AFIO_HAVE_REALTIME_SIGNALS) && defined(_POSIX_RTSIG_MAX) && defined(SIGRTMIN)
+#ifndef AFIO_IO_POST_SIGNAL
+#define AFIO_IO_POST_SIGNAL (-1)
+#endif
+#define AFIO_HAVE_REALTIME_SIGNALS 1
+#else
+#ifndef AFIO_IO_POST_SIGNAL
+//! Undefined to autoset to first free SIGRTMIN if realtime signals available, else SIGUSR1. Only used if AFIO_USE_KQUEUES=0.
+#define AFIO_IO_POST_SIGNAL (SIGUSR1)
+#endif
+//! Undefined to autodetect. 0 to use non-realtime signals. Note performance in this use case is abysmal.
+#define AFIO_HAVE_REALTIME_SIGNALS 0
+#endif
+struct aiocb;
+#endif
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+class io_service;
+class async_file_handle;
+
+/*! \class io_service
+\brief An asynchronous i/o multiplexer service.
+
+This service is used in conjunction with `async_file_handle` to multiplex
+initating i/o and completing it onto a single kernel thread.
+Unlike the `io_service` in ASIO or the Networking TS, this `io_service`
+is much simpler, in particular it is single threaded per instance only
+i.e. you must run a separate `io_service` instance one per kernel thread
+if you wish to run i/o processing across multiple threads. AFIO does not
+do this for you (and for good reason, unlike socket i/o, it is generally
+unwise to distribute file i/o across kernel threads due to the much
+more code executable between user space and physical storage i.e. keeping
+processing per CPU core hot in cache delivers outsize benefits compared
+to socket i/o).
+
+Furthermore, you cannot use this i/o service in any way from any
+thread other than where it was created. You cannot call its `run()`
+from any thread other than where it was created. And you cannot
+initiate i/o on an `async_file_handle` from any thread other than where
+its owning i/o service was created.
+
+In other words, keep your i/o service and all associated file handles
+on their owning thread. The sole function you can call from another
+thread is `post()` which lets you execute some callback in the `run()`
+of the owning thread. This lets you schedule i/o from other threads
+if you really must do that.
+
+\snippet coroutines.cpp coroutines_example
+*/
+class AFIO_DECL io_service
+{
+ friend class async_file_handle;
+
+public:
+ //! The file extent type used by this i/o service
+ using extent_type = io_handle::extent_type;
+ //! The memory extent type used by this i/o service
+ using size_type = io_handle::size_type;
+ //! The scatter buffer type used by this i/o service
+ using buffer_type = io_handle::buffer_type;
+ //! The gather buffer type used by this i/o service
+ using const_buffer_type = io_handle::const_buffer_type;
+ //! The scatter buffers type used by this i/o service
+ using buffers_type = io_handle::buffers_type;
+ //! The gather buffers type used by this i/o service
+ using const_buffers_type = io_handle::const_buffers_type;
+ //! The i/o request type used by this i/o service
+ template <class T> using io_request = io_handle::io_request<T>;
+ //! The i/o result type used by this i/o service
+ template <class T> using io_result = io_handle::io_result<T>;
+
+private:
+#ifdef _WIN32
+ win::handle _threadh{};
+ win::dword _threadid;
+#else
+ pthread_t _threadh;
+#endif
+ std::mutex _posts_lock;
+ struct post_info
+ {
+ io_service *service;
+ detail::function_ptr<void(io_service *)> f;
+ post_info(io_service *s, detail::function_ptr<void(io_service *)> _f)
+ : service(s)
+ , f(std::move(_f))
+ {
+ }
+ };
+ std::deque<post_info> _posts;
+ using shared_size_type = std::atomic<size_type>;
+ shared_size_type _work_queued;
+#if AFIO_USE_POSIX_AIO
+ bool _use_kqueues;
+#if AFIO_COMPILE_KQUEUES
+ int _kqueueh;
+#endif
+ std::vector<struct aiocb *> _aiocbsv; // for fast aio_suspend()
+#endif
+public:
+ // LOCK MUST BE HELD ON ENTRY!
+ void __post_done(post_info *pi)
+ {
+ // Find the post_info and remove it
+ for(auto &i : _posts)
+ {
+ if(&i == pi)
+ {
+ i.f.reset();
+ i.service = nullptr;
+ pi = nullptr;
+ break;
+ }
+ }
+ assert(!pi);
+ if(pi != nullptr)
+ {
+ abort();
+ }
+ _work_done();
+ while(!_posts.empty() && (_posts.front().service == nullptr))
+ {
+ _posts.pop_front();
+ }
+ }
+ void _post_done(post_info *pi)
+ {
+ std::lock_guard<decltype(_posts_lock)> g(_posts_lock);
+ return __post_done(pi);
+ }
+ void _work_enqueued(size_type i = 1) { _work_queued += i; }
+ void _work_done() { --_work_queued; }
+ /*! Creates an i/o service for the calling thread, installing a
+ global signal handler via set_interruption_signal() if not yet installed
+ if on POSIX and BSD kqueues not in use.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC io_service();
+ io_service(io_service &&) = delete;
+ io_service(const io_service &) = delete;
+ io_service &operator=(io_service &&) = delete;
+ io_service &operator=(const io_service &) = delete;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~io_service();
+
+#ifdef AFIO_IO_POST_SIGNAL
+private:
+ int _blocked_interrupt_signal{0};
+ std::atomic<bool> _need_signal{false}; // false = signal not needed, true = signal needed
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC void _block_interruption() noexcept;
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC void _unblock_interruption() noexcept;
+
+public:
+ /*! Returns the signal used for interrupting run_until(). Only used on POSIX when
+ BSD kqueues are not used. Defaults to AFIO_IO_POST_SIGNAL on platforms which use it.
+
+ \note Only present if AFIO_IO_POST_SIGNAL is defined.
+ */
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC int interruption_signal() noexcept;
+ /*! Sets the signal used for interrupting run_until(), returning the former signal
+ setting. Only used on POSIX when BSD kqueues are not used. Special values are
+ 0 for deinstall global signal handler, and -1 for install to first unused signal
+ between SIGRTMIN and SIGRTMAX. Changing this while any io_service instances exist
+ is a bad idea.
+
+ \note Only present if AFIO_IO_POST_SIGNAL is defined.
+ */
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC int set_interruption_signal(int signo = AFIO_IO_POST_SIGNAL);
+#endif
+
+#if AFIO_USE_POSIX_AIO
+ //! True if this i/o service is using BSD kqueues
+ bool using_kqueues() const noexcept { return _use_kqueues; }
+ //! Force disable any use of BSD kqueues
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC void disable_kqueues();
+#endif
+
+ /*! Runs the i/o service for the thread owning this i/o service. Returns true if more
+ work remains and we just handled an i/o or post; false if there is no more work; `errc::timed_out` if
+ the deadline passed; `errc::operation_not_supported` if you try to call it from a non-owning thread; `errc::invalid_argument`
+ if deadline is invalid.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<bool> run_until(deadline d) noexcept;
+ //! \overload
+ result<bool> run() noexcept { return run_until(deadline()); }
+
+private:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC void _post(detail::function_ptr<void(io_service *)> &&f);
+
+public:
+ /*! Schedule the callable to be invoked by the thread owning this object and executing `run()` at its next
+ available opportunity. Unlike any other function in this API layer, this function is thread safe.
+ */
+ template <class U> void post(U &&f) { _post(detail::make_function_ptr<void(io_service *)>(std::forward<U>(f))); }
+
+#if defined(__cpp_coroutines) || defined(DOXYGEN_IS_IN_THE_HOUSE)
+ /*! An awaitable suspending execution of this coroutine on the current kernel thread,
+ and resuming execution on the kernel thread running this i/o service. This is a
+ convenience wrapper for `post()`.
+ */
+ struct awaitable_post_to_self
+ {
+ io_service *service;
+
+ //! Constructor, takes the i/o service whose kernel thread we are to reschedule onto
+ explicit awaitable_post_to_self(io_service &_service)
+ : service(&_service)
+ {
+ }
+
+ bool await_ready() { return false; }
+ void await_suspend(coroutine_handle<> co)
+ {
+ service->post([co = co](io_service * /*unused*/) { co.resume(); });
+ }
+ void await_resume() {}
+ };
+#endif
+};
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/io_service.ipp"
+#else
+#include "detail/impl/posix/io_service.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/logging.hpp b/include/llfio/v2.0/logging.hpp
new file mode 100644
index 00000000..76030684
--- /dev/null
+++ b/include/llfio/v2.0/logging.hpp
@@ -0,0 +1,336 @@
+/* Configures AFIO
+(C) 2015-2018 Niall Douglas <http://www.nedproductions.biz/> (24 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_LOGGING_HPP
+#define AFIO_LOGGING_HPP
+
+#include "config.hpp"
+
+#if AFIO_LOGGING_LEVEL
+
+/*! \todo TODO FIXME Replace in-memory log with memory map file backed log.
+*/
+AFIO_V2_NAMESPACE_BEGIN
+
+//! The log used by AFIO
+inline AFIO_DECL QUICKCPPLIB_NAMESPACE::ringbuffer_log::simple_ringbuffer_log<AFIO_LOGGING_MEMORY> &log() noexcept
+{
+ static QUICKCPPLIB_NAMESPACE::ringbuffer_log::simple_ringbuffer_log<AFIO_LOGGING_MEMORY> _log(static_cast<QUICKCPPLIB_NAMESPACE::ringbuffer_log::level>(AFIO_LOGGING_LEVEL));
+#ifdef AFIO_LOG_TO_OSTREAM
+ if(_log.immediate() != &AFIO_LOG_TO_OSTREAM)
+ {
+ _log.immediate(&AFIO_LOG_TO_OSTREAM);
+ }
+#endif
+ return _log;
+}
+//! Enum for the log level
+using log_level = QUICKCPPLIB_NAMESPACE::ringbuffer_log::level;
+//! RAII class for temporarily adjusting the log level
+class log_level_guard
+{
+ log_level _v;
+
+public:
+ log_level_guard() = delete;
+ log_level_guard(const log_level_guard &) = delete;
+ log_level_guard(log_level_guard &&) = delete;
+ log_level_guard &operator=(const log_level_guard &) = delete;
+ log_level_guard &operator=(log_level_guard &&) = delete;
+ explicit log_level_guard(log_level n)
+ : _v(log().log_level())
+ {
+ log().log_level(n);
+ }
+ ~log_level_guard() { log().log_level(_v); }
+};
+
+// Infrastructure for recording the current path for when failure occurs
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+namespace detail
+{
+ // Our thread local store
+ struct tls_errored_results_t
+ {
+ uint32_t this_thread_id{QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id()};
+ handle *current_handle{nullptr}; // The current handle for this thread. Changed via RAII via AFIO_LOG_FUNCTION_CALL, see below.
+ bool reentering_self{false}; // Prevents any failed call to current_path() by us reentering ourselves
+
+ char paths[190][16]{}; // Last 190 chars of path
+ uint16_t pathidx{0};
+ char *next(uint16_t &idx)
+ {
+ idx = pathidx++;
+ return paths[idx % 16]; // NOLINT
+ }
+ const char *get(uint16_t idx) const
+ {
+ // If the idx is stale, return not found
+ if(idx - pathidx >= 16)
+ {
+ return nullptr;
+ }
+ return paths[idx % 16]; // NOLINT
+ }
+ };
+ inline tls_errored_results_t &tls_errored_results()
+ {
+#if AFIO_THREAD_LOCAL_IS_CXX11
+ static thread_local tls_errored_results_t v;
+ return v;
+#else
+ static AFIO_THREAD_LOCAL tls_errored_results_t *v;
+ if(!v)
+ {
+ v = new tls_errored_results_t;
+ }
+ return *v;
+#endif
+ }
+ template <bool _enabled> struct tls_current_handle_holder
+ {
+ handle *old{nullptr};
+ bool enabled{false};
+ tls_current_handle_holder() = delete;
+ tls_current_handle_holder(const tls_current_handle_holder &) = delete;
+ tls_current_handle_holder(tls_current_handle_holder &&) = delete;
+ tls_current_handle_holder &operator=(const tls_current_handle_holder &) = delete;
+ tls_current_handle_holder &operator=(tls_current_handle_holder &&) = delete;
+ explicit tls_current_handle_holder(const handle *h)
+ {
+ if(h != nullptr && log().log_level() >= log_level::error)
+ {
+ auto &tls = tls_errored_results();
+ old = tls.current_handle;
+ tls.current_handle = const_cast<handle *>(h); // NOLINT
+ enabled = true;
+ }
+ }
+ ~tls_current_handle_holder()
+ {
+ if(enabled)
+ {
+ auto &tls = tls_errored_results();
+ tls.current_handle = old;
+ }
+ }
+ };
+ template <> struct tls_current_handle_holder<false>
+ {
+ tls_current_handle_holder() = delete;
+ tls_current_handle_holder(const tls_current_handle_holder &) = delete;
+ tls_current_handle_holder(tls_current_handle_holder &&) = delete;
+ tls_current_handle_holder &operator=(const tls_current_handle_holder &) = delete;
+ tls_current_handle_holder &operator=(tls_current_handle_holder &&) = delete;
+ ~tls_current_handle_holder() = default;
+ template <class T> explicit tls_current_handle_holder(T && /*unused*/) {}
+ };
+#define AFIO_LOG_INST_TO_TLS(inst) ::AFIO_V2_NAMESPACE::detail::tls_current_handle_holder<std::is_base_of<::AFIO_V2_NAMESPACE::handle, std::decay_t<std::remove_pointer_t<decltype(inst)>>>::value> AFIO_UNIQUE_NAME(inst)
+} // namespace detail
+#else // AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+#define AFIO_LOG_INST_TO_TLS(inst)
+#endif // AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+
+AFIO_V2_NAMESPACE_END
+
+#ifndef AFIO_LOG_FATAL_TO_CERR
+#include <cstdio>
+#define AFIO_LOG_FATAL_TO_CERR(expr) \
+ fprintf(stderr, "%s\n", (expr)); \
+ fflush(stderr)
+#endif
+#endif // AFIO_LOGGING_LEVEL
+
+#if AFIO_LOGGING_LEVEL >= 1
+#define AFIO_LOG_FATAL(inst, message) \
+ { \
+ ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::fatal, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 1U)) ? nullptr : __func__, __LINE__); \
+ AFIO_LOG_FATAL_TO_CERR(message); \
+ }
+#else
+#define AFIO_LOG_FATAL(inst, message) AFIO_LOG_FATAL_TO_CERR(message)
+#endif
+#if AFIO_LOGGING_LEVEL >= 2
+#define AFIO_LOG_ERROR(inst, message) \
+ ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::error, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 2U)) ? nullptr : __func__, __LINE__)
+#else
+#define AFIO_LOG_ERROR(inst, message)
+#endif
+#if AFIO_LOGGING_LEVEL >= 3
+#define AFIO_LOG_WARN(inst, message) \
+ ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::warn, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 3U)) ? nullptr : __func__, __LINE__)
+#else
+#define AFIO_LOG_WARN(inst, message)
+#endif
+#if AFIO_LOGGING_LEVEL >= 4
+#define AFIO_LOG_INFO(inst, message) \
+ ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::info, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 4U)) ? nullptr : __func__, __LINE__)
+
+// Need to expand out our namespace into a string
+#define AFIO_LOG_STRINGIFY9(s) #s "::"
+#define AFIO_LOG_STRINGIFY8(s) AFIO_LOG_STRINGIFY9(s)
+#define AFIO_LOG_STRINGIFY7(s) AFIO_LOG_STRINGIFY8(s)
+#define AFIO_LOG_STRINGIFY6(s) AFIO_LOG_STRINGIFY7(s)
+#define AFIO_LOG_STRINGIFY5(s) AFIO_LOG_STRINGIFY6(s)
+#define AFIO_LOG_STRINGIFY4(s) AFIO_LOG_STRINGIFY5(s)
+#define AFIO_LOG_STRINGIFY3(s) AFIO_LOG_STRINGIFY4(s)
+#define AFIO_LOG_STRINGIFY2(s) AFIO_LOG_STRINGIFY3(s)
+#define AFIO_LOG_STRINGIFY(s) AFIO_LOG_STRINGIFY2(s)
+AFIO_V2_NAMESPACE_BEGIN
+namespace detail
+{
+ // Returns the AFIO namespace as a string
+ inline span<char> afio_namespace_string()
+ {
+ static char buffer[64];
+ static size_t length;
+ if(length)
+ return span<char>(buffer, length);
+ const char *src = AFIO_LOG_STRINGIFY(AFIO_V2_NAMESPACE);
+ char *bufferp = buffer;
+ for(; *src && (bufferp - buffer) < (ptrdiff_t) sizeof(buffer); src++)
+ {
+ if(*src != ' ')
+ *bufferp++ = *src;
+ }
+ *bufferp = 0;
+ length = bufferp - buffer;
+ return span<char>(buffer, length);
+ }
+ // Returns the Outcome namespace as a string
+ inline span<char> outcome_namespace_string()
+ {
+ static char buffer[64];
+ static size_t length;
+ if(length)
+ return span<char>(buffer, length);
+ const char *src = AFIO_LOG_STRINGIFY(OUTCOME_V2_NAMESPACE);
+ char *bufferp = buffer;
+ for(; *src && (bufferp - buffer) < (ptrdiff_t) sizeof(buffer); src++)
+ {
+ if(*src != ' ')
+ *bufferp++ = *src;
+ }
+ *bufferp = 0;
+ length = bufferp - buffer;
+ return span<char>(buffer, length);
+ }
+ // Strips a __PRETTY_FUNCTION__ of all instances of ::AFIO_V2_NAMESPACE:: and ::AFIO_V2_NAMESPACE::
+ inline void strip_pretty_function(char *out, size_t bytes, const char *in)
+ {
+ const span<char> remove1 = afio_namespace_string();
+ const span<char> remove2 = outcome_namespace_string();
+ for(--bytes; bytes && *in; --bytes)
+ {
+ if(!strncmp(in, remove1.data(), remove1.size()))
+ in += remove1.size();
+ if(!strncmp(in, remove2.data(), remove2.size()))
+ in += remove2.size();
+ *out++ = *in++;
+ }
+ *out = 0;
+ }
+ template <class T> void log_inst_to_info(T &&inst, const char *buffer) { AFIO_LOG_INFO(inst, buffer); }
+}
+AFIO_V2_NAMESPACE_END
+#ifdef _MSC_VER
+#define AFIO_LOG_FUNCTION_CALL(inst) \
+ if(log().log_level() >= log_level::info) \
+ { \
+ char buffer[256]; \
+ ::AFIO_V2_NAMESPACE::detail::strip_pretty_function(buffer, sizeof(buffer), __FUNCSIG__); \
+ ::AFIO_V2_NAMESPACE::detail::log_inst_to_info(inst, buffer); \
+ } \
+ AFIO_LOG_INST_TO_TLS(inst)
+#else
+#define AFIO_LOG_FUNCTION_CALL(inst) \
+ if(log().log_level() >= log_level::info) \
+ { \
+ char buffer[256]; \
+ ::AFIO_V2_NAMESPACE::detail::strip_pretty_function(buffer, sizeof(buffer), __PRETTY_FUNCTION__); \
+ ::AFIO_V2_NAMESPACE::detail::log_inst_to_info(inst, buffer); \
+ } \
+ AFIO_LOG_INST_TO_TLS(inst)
+#endif
+#else
+#define AFIO_LOG_INFO(inst, message)
+#define AFIO_LOG_FUNCTION_CALL(inst) AFIO_LOG_INST_TO_TLS(inst)
+#endif
+#if AFIO_LOGGING_LEVEL >= 5
+#define AFIO_LOG_DEBUG(inst, message) \
+ ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::debug, ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 5U)) ? nullptr : __func__, __LINE__)
+#else
+#define AFIO_LOG_DEBUG(inst, message)
+#endif
+#if AFIO_LOGGING_LEVEL >= 6
+#define AFIO_LOG_ALL(inst, message) \
+ ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::all, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 6U)) ? nullptr : __func__, __LINE__)
+#else
+#define AFIO_LOG_ALL(inst, message)
+#endif
+
+
+#if !AFIO_EXPERIMENTAL_STATUS_CODE
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace detail
+{
+ template <class Src> inline void append_path_info(Src &src, std::string &ret)
+ {
+ if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == src._thread_id)
+ {
+ auto &tls = detail::tls_errored_results();
+ const char *path1 = tls.get(src._tls_path_id1), *path2 = tls.get(src._tls_path_id2);
+ if(path1 != nullptr)
+ {
+ ret.append(" [path1 = ");
+ ret.append(path1);
+ if(path2 != nullptr)
+ {
+ ret.append(", path2 = ");
+ ret.append(path2);
+ }
+ ret.append("]");
+ }
+ }
+#if AFIO_LOGGING_LEVEL >= 2
+ if(src._log_id != static_cast<uint32_t>(-1))
+ {
+ if(log().valid(src._log_id))
+ {
+ ret.append(" [location = ");
+ ret.append(location(log()[src._log_id]));
+ ret.append("]");
+ }
+ }
+#endif
+ }
+}
+
+AFIO_V2_NAMESPACE_END
+#endif
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/map_handle.hpp b/include/llfio/v2.0/map_handle.hpp
new file mode 100644
index 00000000..8c66450a
--- /dev/null
+++ b/include/llfio/v2.0/map_handle.hpp
@@ -0,0 +1,748 @@
+/* A handle to a source of mapped memory
+(C) 2016-2018 Niall Douglas <http://www.nedproductions.biz/> (14 commits)
+File Created: August 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_MAP_HANDLE_H
+#define AFIO_MAP_HANDLE_H
+
+#include "file_handle.hpp"
+
+//! \file map_handle.hpp Provides `map_handle`
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+/*! \class section_handle
+\brief A handle to a source of mapped memory.
+
+There are two configurations of section handle, one where the user supplies the file backing for the
+section, and the other where an internal file descriptor to an unnamed inode in a tmpfs or ramfs based
+temporary directory is kept and managed. The latter is merely a convenience for creating an anonymous
+source of memory which can be resized whilst preserving contents: see `algorithm::trivial_vector<T>`.
+
+On Windows the native handle of this handle is that of the NT kernel section object. On POSIX it is
+a cloned file descriptor of the backing storage if there is backing storage, else it will be the
+aforementioned file descriptor to an unnamed inode.
+*/
+class AFIO_DECL section_handle : public handle
+{
+public:
+ using extent_type = handle::extent_type;
+ using size_type = handle::size_type;
+
+ //! The behaviour of the memory section
+ QUICKCPPLIB_BITFIELD_BEGIN(flag){none = 0U, //!< No flags
+ read = 1U << 0U, //!< Memory views can be read
+ write = 1U << 1U, //!< Memory views can be written
+ cow = 1U << 2U, //!< Memory views can be copy on written
+ execute = 1U << 3U, //!< Memory views can execute code
+
+ nocommit = 1U << 8U, //!< Don't allocate space for this memory in the system immediately
+ prefault = 1U << 9U, //!< Prefault, as if by reading every page, any views of memory upon creation.
+ executable = 1U << 10U, //!< The backing storage is in fact an executable program binary.
+ singleton = 1U << 11U, //!< A single instance of this section is to be shared by all processes using the same backing file.
+
+ barrier_on_close = 1U << 16U, //!< Maps of this section, if writable, issue a `barrier()` when destructed blocking until data (not metadata) reaches physical storage.
+ nvram = 1U << 17U, //!< This section is of non-volatile RAM
+
+ // NOTE: IF UPDATING THIS UPDATE THE std::ostream PRINTER BELOW!!!
+
+ readwrite = (read | write)};
+ QUICKCPPLIB_BITFIELD_END(flag);
+
+protected:
+ file_handle *_backing{nullptr};
+ file_handle _anonymous;
+ flag _flag{flag::none};
+
+public:
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~section_handle() override;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override;
+ //! Default constructor
+ constexpr section_handle() {} // NOLINT
+ //! Construct a section handle using the given native handle type for the section and the given i/o handle for the backing storage
+ explicit section_handle(native_handle_type sectionh, file_handle *backing, file_handle anonymous, flag __flag)
+ : handle(sectionh, handle::caching::all)
+ , _backing(backing)
+ , _anonymous(std::move(anonymous))
+ , _flag(__flag)
+ {
+ }
+ //! Implicit move construction of section_handle permitted
+ constexpr section_handle(section_handle &&o) noexcept : handle(std::move(o)), _backing(o._backing), _anonymous(std::move(o._anonymous)), _flag(o._flag)
+ {
+ o._backing = nullptr;
+ o._flag = flag::none;
+ }
+ //! No copy construction (use `clone()`)
+ section_handle(const section_handle &) = delete;
+ //! Move assignment of section_handle permitted
+ section_handle &operator=(section_handle &&o) noexcept
+ {
+ this->~section_handle();
+ new(this) section_handle(std::move(o));
+ return *this;
+ }
+ //! No copy assignment
+ section_handle &operator=(const section_handle &) = delete;
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(section_handle &o) noexcept
+ {
+ section_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! \brief Create a memory section backed by a file.
+ \param backing The handle to use as backing storage.
+ \param maximum_size The initial size of this section, which cannot be larger than any backing file. Zero means to use `backing.maximum_extent()`.
+ \param _flag How to create the section.
+
+ \errors Any of the values POSIX dup(), open() or NtCreateSection() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<section_handle> section(file_handle &backing, extent_type maximum_size, flag _flag) noexcept;
+ /*! \brief Create a memory section backed by a file.
+ \param backing The handle to use as backing storage.
+ \param bytes The initial size of this section, which cannot be larger than any backing file. Zero means to use `backing.maximum_extent()`.
+
+ This convenience overload create a writable section if the backing file is writable, otherwise a read-only section.
+
+ \errors Any of the values POSIX dup(), open() or NtCreateSection() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static result<section_handle> section(file_handle &backing, extent_type bytes = 0) noexcept { return section(backing, bytes, backing.is_writable() ? (flag::readwrite) : (flag::read)); }
+ /*! \brief Create a memory section backed by an anonymous, managed file.
+ \param bytes The initial size of this section. Cannot be zero.
+ \param dirh Where to create the anonymous, managed file.
+ \param _flag How to create the section.
+
+ \errors Any of the values POSIX dup(), open() or NtCreateSection() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<section_handle> section(extent_type bytes, const path_handle &dirh = path_discovery::storage_backed_temporary_files_directory(), flag _flag = flag::read | flag::write) noexcept;
+
+ //! Returns the memory section's flags
+ flag section_flags() const noexcept { return _flag; }
+ //! True if the section reflects non-volatile RAM
+ bool is_nvram() const noexcept { return !!(_flag & flag::nvram); }
+ //! Returns the borrowed handle backing this section, if any
+ file_handle *backing() const noexcept { return _backing; }
+ //! Sets the borrowed handle backing this section, if any
+ void set_backing(file_handle *fh) noexcept { _backing = fh; }
+ //! Returns the borrowed native handle backing this section
+ native_handle_type backing_native_handle() const noexcept { return _backing != nullptr ? _backing->native_handle() : native_handle_type(); }
+ //! Return the current length of the memory section.
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<extent_type> length() const noexcept;
+
+ /*! Resize the current maximum permitted extent of the memory section to the given extent.
+ \param newsize The new size of the memory section, which cannot be zero. Specify zero to use `backing.maximum_extent()`.
+ This cannot exceed the size of any backing file used if that file is not writable.
+
+ \errors Any of the values `NtExtendSection()` or `ftruncate()` can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<extent_type> truncate(extent_type newsize = 0) noexcept;
+};
+inline std::ostream &operator<<(std::ostream &s, const section_handle::flag &v)
+{
+ std::string temp;
+ if(!!(v & section_handle::flag::read))
+ {
+ temp.append("read|");
+ }
+ if(!!(v & section_handle::flag::write))
+ {
+ temp.append("write|");
+ }
+ if(!!(v & section_handle::flag::cow))
+ {
+ temp.append("cow|");
+ }
+ if(!!(v & section_handle::flag::execute))
+ {
+ temp.append("execute|");
+ }
+ if(!!(v & section_handle::flag::nocommit))
+ {
+ temp.append("nocommit|");
+ }
+ if(!!(v & section_handle::flag::prefault))
+ {
+ temp.append("prefault|");
+ }
+ if(!!(v & section_handle::flag::executable))
+ {
+ temp.append("executable|");
+ }
+ if(!!(v & section_handle::flag::singleton))
+ {
+ temp.append("singleton|");
+ }
+ if(!!(v & section_handle::flag::barrier_on_close))
+ {
+ temp.append("barrier_on_close|");
+ }
+ if(!!(v & section_handle::flag::nvram))
+ {
+ temp.append("nvram|");
+ }
+ if(!temp.empty())
+ {
+ temp.resize(temp.size() - 1);
+ if(std::count(temp.cbegin(), temp.cend(), '|') > 0)
+ {
+ temp = "(" + temp + ")";
+ }
+ }
+ else
+ {
+ temp = "none";
+ }
+ return s << "afio::section_handle::flag::" << temp;
+}
+
+//! \brief Constructor for `section_handle`
+template <> struct construct<section_handle>
+{
+ file_handle &backing;
+ section_handle::extent_type maximum_size = 0;
+ section_handle::flag _flag = section_handle::flag::read | section_handle::flag::write;
+ result<section_handle> operator()() const noexcept { return section_handle::section(backing, maximum_size, _flag); }
+};
+
+class mapped_file_handle;
+
+/*! \class map_handle
+\brief A handle to a memory mapped region of memory, either backed by the system page file or by a section.
+
+An important concept to realise with mapped regions is that they can far exceed the size of their backing
+storage. This allows one to reserve address space for a file which may grow in the future. This is how
+`mapped_file_handle` is implemented to provide very fast memory mapped file i/o of a potentially growing
+file.
+
+The size you specify when creating the map handle is the address space reservation. The map's `length()`
+will return the last known **valid** length of the mapped data i.e. the backing storage's length at the
+time of construction. This length is used by `read()` and `write()` to prevent reading and writing off
+the end of the mapped region. You can update this length to the backing storage's length using `update_map()`
+up to the reservation limit.
+
+You can attempt to modify the address space reservation after creation using `truncate()`. If successful,
+this will be more efficient than tearing down the map and creating a new larger map.
+
+The native handle returned by this map handle is always that of the backing storage, but closing this handle
+does not close that of the backing storage, nor does releasing this handle release that of the backing storage.
+Locking byte ranges of this handle is therefore equal to locking byte ranges in the original backing storage,
+which can be very useful.
+
+## Barriers:
+
+`map_handle`, because it implements `io_handle`, implements `barrier()` in a very conservative way
+to account for OS differences i.e. it calls `msync()`, and then the `barrier()` implementation for the backing file
+(probably `fsync()` or equivalent on most platforms, which synchronises the entire file).
+
+This is vast overkill if you are using non-volatile RAM, so a special *inlined* `barrier()` implementation
+taking a single buffer and no other arguments is also provided. This calls the appropriate architecture-specific
+instructions to cause the CPU to write all preceding writes out of the write buffers and CPU caches to main
+memory, so for Intel CPUs this would be `CLWB <each cache line>; SFENCE;`. As this is inlined, it ought to
+produce optimal code. If your CPU does not support the requisite instructions (or AFIO has not added support),
+and empty buffer will be returned to indicate that nothing was barriered, same as the normal `barrier()`
+function.
+
+\sa `mapped_file_handle`, `algorithm::mapped_span`
+*/
+class AFIO_DECL map_handle : public io_handle
+{
+ friend class mapped_file_handle;
+
+public:
+ using extent_type = io_handle::extent_type;
+ using size_type = io_handle::size_type;
+ using mode = io_handle::mode;
+ using creation = io_handle::creation;
+ using caching = io_handle::caching;
+ using flag = io_handle::flag;
+ using buffer_type = io_handle::buffer_type;
+ using const_buffer_type = io_handle::const_buffer_type;
+ using buffers_type = io_handle::buffers_type;
+ using const_buffers_type = io_handle::const_buffers_type;
+ template <class T> using io_request = io_handle::io_request<T>;
+ template <class T> using io_result = io_handle::io_result<T>;
+
+protected:
+ section_handle *_section{nullptr};
+ byte *_addr{nullptr};
+ extent_type _offset{0};
+ size_type _reservation{0}, _length{0};
+ section_handle::flag _flag{section_handle::flag::none};
+
+ explicit map_handle(section_handle *section)
+ : _section(section)
+ , _flag(section != nullptr ? section->section_flags() : section_handle::flag::none)
+ {
+ }
+
+public:
+ //! Default constructor
+ constexpr map_handle() {} // NOLINT
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~map_handle() override;
+ //! Implicit move construction of map_handle permitted
+ constexpr map_handle(map_handle &&o) noexcept : io_handle(std::move(o)), _section(o._section), _addr(o._addr), _offset(o._offset), _reservation(o._reservation), _length(o._length), _flag(o._flag)
+ {
+ o._section = nullptr;
+ o._addr = nullptr;
+ o._offset = 0;
+ o._reservation = 0;
+ o._length = 0;
+ o._flag = section_handle::flag::none;
+ }
+ //! No copy construction (use `clone()`)
+ map_handle(const map_handle &) = delete;
+ //! Move assignment of map_handle permitted
+ map_handle &operator=(map_handle &&o) noexcept
+ {
+ this->~map_handle();
+ new(this) map_handle(std::move(o));
+ return *this;
+ }
+ //! No copy assignment
+ map_handle &operator=(const map_handle &) = delete;
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(map_handle &o) noexcept
+ {
+ map_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ //! Unmap the mapped view.
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override;
+ //! Releases the mapped view, but does NOT release the native handle.
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override;
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override;
+ /*! Lightweight inlined barrier which causes the CPU to write out all buffered writes and dirty cache lines
+ in the request to main memory.
+ \return The cache lines actually barriered. This may be empty. This function does not return an error.
+ \param req The range of cache lines to write barrier.
+ \param evict Whether to also evict the cache lines from CPU caches, useful if they will not be used again.
+
+ Upon return, one knows that memory in the returned buffer has been barriered
+ (it may be empty if there is no support for this operation in AFIO, or if the current CPU does not
+ support this operation). You may find the `is_nvram()` observer of particular use here.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static const_buffer_type barrier(const_buffer_type req, bool evict = false) noexcept
+ {
+ const_buffer_type ret{(const_buffer_type::pointer)(((uintptr_t) req.data) & 31), 0};
+ ret.len = req.data + req.len - ret.data;
+ for(const_buffer_type::pointer addr = ret.data; addr < ret.data + ret.len; addr += 32)
+ {
+ // Slightly UB ...
+ auto *p = reinterpret_cast<const persistent<byte> *>(addr);
+ if(memory_flush_none == p->flush(evict ? memory_flush_evict : memory_flush_retain))
+ {
+ req.len = 0;
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /*! Create new memory and map it into view.
+ \param bytes How many bytes to create and map. Typically will be rounded up to a multiple of the page size (see `utils::page_sizes()`) on POSIX, 64Kb on Windows.
+ \param _flag The permissions with which to map the view. `flag::none` can be useful for reserving virtual address space without committing system resources, use commit() to later change availability of memory.
+
+ \note On Microsoft Windows this constructor uses the faster VirtualAlloc() which creates less versatile page backed memory. If you want anonymous memory
+ allocated from a paging file backed section instead, create a page file backed section and then a mapped view from that using
+ the other constructor. This makes available all those very useful VM tricks Windows can do with section mapped memory which
+ VirtualAlloc() memory cannot do.
+
+ \errors Any of the values POSIX mmap() or VirtualAlloc() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<map_handle> map(size_type bytes, section_handle::flag _flag = section_handle::flag::readwrite) noexcept;
+
+ /*! Create a memory mapped view of a backing storage, optionally reserving additional address space for later growth.
+ \param section A memory section handle specifying the backing storage to use.
+ \param bytes How many bytes to reserve (0 = the size of the section). Rounded up to nearest 64Kb on Windows.
+ \param offset The offset into the backing storage to map from. Typically needs to be at least a multiple of the page size (see utils::page_sizes()), on Windows it needs to be a multiple of the kernel memory allocation granularity (typically 64Kb).
+ \param _flag The permissions with which to map the view which are constrained by the permissions of the memory section. `flag::none` can be useful for reserving virtual address space without committing system resources, use commit() to later change availability of memory.
+
+ \errors Any of the values POSIX mmap() or NtMapViewOfSection() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<map_handle> map(section_handle &section, size_type bytes = 0, extent_type offset = 0, section_handle::flag _flag = section_handle::flag::readwrite) noexcept;
+
+ //! The memory section this handle is using
+ section_handle *section() const noexcept { return _section; }
+ //! Sets the memory section this handle is using
+ void set_section(section_handle *s) noexcept { _section = s; }
+
+ //! The address in memory where this mapped view resides
+ byte *address() const noexcept { return _addr; }
+
+ //! The offset of the memory map.
+ extent_type offset() const noexcept { return _offset; }
+
+ //! The reservation size of the memory map.
+ size_type capacity() const noexcept { return _reservation; }
+
+ //! The size of the memory map. This is the accessible size, NOT the reservation size.
+ AFIO_MAKE_FREE_FUNCTION
+ size_type length() const noexcept { return _length; }
+
+ //! True if the map is of non-volatile RAM
+ bool is_nvram() const noexcept { return !!(_flag & section_handle::flag::nvram); }
+
+ //! Update the size of the memory map to that of any backing section, up to the reservation limit.
+ result<size_type> update_map() noexcept
+ {
+ if(_section == nullptr)
+ {
+ return _reservation;
+ }
+ OUTCOME_TRY(length, _section->length()); // length of the backing file
+ length -= _offset;
+ if(length > _reservation)
+ {
+ length = _reservation;
+ }
+ _length = static_cast<size_type>(length);
+ return _length;
+ }
+
+ /*! Resize the reservation of the memory map without changing the address (unless the map
+ was zero sized, in which case a new address will be chosen).
+
+ If shrinking, address space is released on POSIX, and on Windows if the new size is zero.
+ If the new size is zero, the address is set to null to prevent surprises.
+ Windows does not support modifying existing mapped regions, so if the new size is not
+ zero, the call will probably fail. Windows should let you truncate a previous extension
+ however, if it is exact.
+
+ If expanding, an attempt is made to map in new reservation immediately after the current address
+ reservation, thus extending the reservation. If anything else is mapped in after
+ the current reservation, the function fails.
+
+ \note On all supported platforms apart from OS X, proprietary flags exist to avoid
+ performing a map if a map extension cannot be immediately placed after the current map. On OS X,
+ we hint where we'd like the new map to go, but if something is already there OS X will
+ place the map elsewhere. In this situation, we delete the new map and return failure,
+ which is inefficient, but there is nothing else we can do.
+
+ \return The bytes actually reserved.
+ \param newsize The bytes to truncate the map reservation to. Rounded up to the nearest page size (POSIX) or 64Kb on Windows.
+ \param permit_relocation Permit the address to change (some OSs provide a syscall for resizing
+ a memory map).
+ \errors Any of the values POSIX `mremap()`, `mmap(addr)` or `VirtualAlloc(addr)` can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_type> truncate(size_type newsize, bool permit_relocation = false) noexcept;
+
+ //! Ask the system to commit the system resources to make the memory represented by the buffer available with the given permissions. addr and length should be page aligned (see utils::page_sizes()), if not the returned buffer is the region actually committed.
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<buffer_type> commit(buffer_type region, section_handle::flag flag = section_handle::flag::readwrite) noexcept;
+
+ //! Ask the system to make the memory represented by the buffer unavailable and to decommit the system resources representing them. addr and length should be page aligned (see utils::page_sizes()), if not the returned buffer is the region actually decommitted.
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<buffer_type> decommit(buffer_type region) noexcept;
+
+ /*! Zero the memory represented by the buffer. Differs from zero() because it acts on mapped memory, but may call zero() internally.
+
+ On Linux, Windows and FreeBSD any full 4Kb pages will be deallocated from the
+ system entirely, including the extents for them in any backing storage. On newer Linux kernels the kernel can additionally swap whole 4Kb pages for
+ freshly zeroed ones making this a very efficient way of zeroing large ranges of memory.
+ \errors Any of the errors returnable by madvise() or DiscardVirtualMemory or the zero() function.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<void> zero_memory(buffer_type region) noexcept;
+
+ /*! Ask the system to unset the dirty flag for the memory represented by the buffer. This will prevent any changes not yet sent to the backing storage from being sent in the future, also if the system kicks out this page and reloads it you may see some edition of the underlying storage instead of what was here. addr
+ and length should be page aligned (see utils::page_sizes()), if not the returned buffer is the region actually undirtied.
+
+ \warning This function destroys the contents of unwritten pages in the region in a totally unpredictable fashion. Only use it if you don't care how much of
+ the region reaches physical storage or not. Note that the region is not necessarily zeroed, and may be randomly zeroed.
+
+ \note Microsoft Windows does not support unsetting the dirty flag on file backed maps, so on Windows this call does nothing.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<buffer_type> do_not_store(buffer_type region) noexcept;
+
+ //! Ask the system to begin to asynchronously prefetch the span of memory regions given, returning the regions actually prefetched. Note that on Windows 7 or earlier the system call to implement this was not available, and so you will see an empty span returned.
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<span<buffer_type>> prefetch(span<buffer_type> regions) noexcept;
+ //! \overload
+ static result<buffer_type> prefetch(buffer_type region) noexcept
+ {
+ OUTCOME_TRY(ret, prefetch(span<buffer_type>(&region, 1)));
+ return *ret.data();
+ }
+
+ /*! \brief Read data from the mapped view.
+
+ \note Because this implementation never copies memory, you can pass in buffers with a null address. As this
+ function never reads any memory, no attempt to trap signal raises can be made, this falls onto the user of
+ this function. See `QUICKCPPLIB_NAMESPACE::signal_guard` for a helper function.
+
+ \return The buffers read, which will never be the buffers input, because they will point into the mapped view.
+ The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d Ignored.
+ \errors None, though the various signals and structured exception throws common to using memory maps may occur.
+ \mallocs None.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
+ using io_handle::read;
+
+ /*! \brief Write data to the mapped view.
+
+ \note This call traps signals and structured exception throws using `QUICKCPPLIB_NAMESPACE::signal_guard`.
+ Instantiating a `QUICKCPPLIB_NAMESPACE::signal_guard_install` somewhere much higher up in the call stack
+ will improve performance enormously. The signal guard may cost less than 100 CPU cycles depending on how
+ you configure it. If you don't want the guard, you can write memory directly using `address()`.
+
+ \return The buffers written, which will never be the buffers input because they will point at where the data was copied into the mapped view.
+ The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d Ignored.
+ \errors If during the attempt to write the buffers to the map a `SIGBUS` or `EXCEPTION_IN_PAGE_ERROR` is raised,
+ an error code comparing equal to `errc::no_space_on_device` will be returned. This may not always be the cause
+ of the raised signal, but it is by far the most likely.
+ \mallocs None if a `QUICKCPPLIB_NAMESPACE::signal_guard_install` is already instanced.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override;
+ using io_handle::write;
+};
+
+//! \brief Constructor for `map_handle`
+template <> struct construct<map_handle>
+{
+ section_handle &section;
+ map_handle::size_type bytes = 0;
+ map_handle::extent_type offset = 0;
+ section_handle::flag _flag = section_handle::flag::readwrite;
+ result<map_handle> operator()() const noexcept { return map_handle::map(section, bytes, offset, _flag); }
+};
+
+// BEGIN make_free_functions.py
+//! Swap with another instance
+inline void swap(section_handle &self, section_handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+/*! \brief Create a memory section backed by a file.
+\param backing The handle to use as backing storage.
+\param maximum_size The initial size of this section, which cannot be larger than any backing file. Zero means to use `backing.maximum_extent()`.
+\param _flag How to create the section.
+
+\errors Any of the values POSIX dup(), open() or NtCreateSection() can return.
+*/
+inline result<section_handle> section(file_handle &backing, section_handle::extent_type maximum_size, section_handle::flag _flag) noexcept
+{
+ return section_handle::section(std::forward<decltype(backing)>(backing), std::forward<decltype(maximum_size)>(maximum_size), std::forward<decltype(_flag)>(_flag));
+}
+/*! \brief Create a memory section backed by a file.
+\param backing The handle to use as backing storage.
+\param bytes The initial size of this section, which cannot be larger than any backing file. Zero means to use `backing.maximum_extent()`.
+
+This convenience overload create a writable section if the backing file is writable, otherwise a read-only section.
+
+\errors Any of the values POSIX dup(), open() or NtCreateSection() can return.
+*/
+inline result<section_handle> section(file_handle &backing, section_handle::extent_type bytes = 0) noexcept
+{
+ return section_handle::section(std::forward<decltype(backing)>(backing), std::forward<decltype(bytes)>(bytes));
+}
+/*! \brief Create a memory section backed by an anonymous, managed file.
+\param bytes The initial size of this section. Cannot be zero.
+\param dirh Where to create the anonymous, managed file.
+\param _flag How to create the section.
+
+\errors Any of the values POSIX dup(), open() or NtCreateSection() can return.
+*/
+inline result<section_handle> section(section_handle::extent_type bytes, const path_handle &dirh = path_discovery::storage_backed_temporary_files_directory(), section_handle::flag _flag = section_handle::flag::read | section_handle::flag::write) noexcept
+{
+ return section_handle::section(std::forward<decltype(bytes)>(bytes), std::forward<decltype(dirh)>(dirh), std::forward<decltype(_flag)>(_flag));
+}
+//! Return the current maximum permitted extent of the memory section.
+inline result<section_handle::extent_type> length(const section_handle &self) noexcept
+{
+ return self.length();
+}
+/*! Resize the current maximum permitted extent of the memory section to the given extent.
+\param self The object whose member function to call.
+\param newsize The new size of the memory section, which cannot be zero. Specify zero to use `backing.maximum_extent()`.
+This cannot exceed the size of any backing file used if that file is not writable.
+
+\errors Any of the values `NtExtendSection()` or `ftruncate()` can return.
+*/
+inline result<section_handle::extent_type> truncate(section_handle &self, section_handle::extent_type newsize = 0) noexcept
+{
+ return self.truncate(std::forward<decltype(newsize)>(newsize));
+}
+//! Swap with another instance
+inline void swap(map_handle &self, map_handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+//! Unmap the mapped view.
+inline result<void> close(map_handle &self) noexcept
+{
+ return self.close();
+}
+inline map_handle::io_result<map_handle::const_buffers_type> barrier(map_handle &self, map_handle::io_request<map_handle::const_buffers_type> reqs = map_handle::io_request<map_handle::const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept
+{
+ return self.barrier(std::forward<decltype(reqs)>(reqs), std::forward<decltype(wait_for_device)>(wait_for_device), std::forward<decltype(and_metadata)>(and_metadata), std::forward<decltype(d)>(d));
+}
+/*! Lightweight inlined barrier which causes the CPU to write out all buffered writes and dirty cache lines
+in the request to main memory.
+\return The cache lines actually barriered. This may be empty. This function does not return an error.
+\param self The object whose member function to call.
+\param req The range of cache lines to write barrier.
+\param evict Whether to also evict the cache lines from CPU caches, useful if they will not be used again.
+
+Upon return, one knows that memory in the returned buffer has been barriered
+(it may be empty if there is no support for this operation in AFIO, or if the current CPU does not
+support this operation). You may find the `is_nvram()` observer of particular use here.
+*/
+inline map_handle::const_buffer_type barrier(map_handle &self, map_handle::const_buffer_type req, bool evict = false) noexcept
+{
+ return self.barrier(std::forward<decltype(req)>(req), std::forward<decltype(evict)>(evict));
+}
+/*! Create new memory and map it into view.
+\param bytes How many bytes to create and map. Typically will be rounded up to a multiple of the page size (see `utils::page_sizes()`) on POSIX, 64Kb on Windows.
+\param _flag The permissions with which to map the view. `flag::none` can be useful for reserving virtual address space without committing system resources, use commit() to later change availability of memory.
+
+\note On Microsoft Windows this constructor uses the faster VirtualAlloc() which creates less versatile page backed memory. If you want anonymous memory
+allocated from a paging file backed section instead, create a page file backed section and then a mapped view from that using
+the other constructor. This makes available all those very useful VM tricks Windows can do with section mapped memory which
+VirtualAlloc() memory cannot do.
+
+\errors Any of the values POSIX mmap() or VirtualAlloc() can return.
+*/
+inline result<map_handle> map(map_handle::size_type bytes, section_handle::flag _flag = section_handle::flag::readwrite) noexcept
+{
+ return map_handle::map(std::forward<decltype(bytes)>(bytes), std::forward<decltype(_flag)>(_flag));
+}
+/*! Create a memory mapped view of a backing storage, optionally reserving additional address space for later growth.
+\param section A memory section handle specifying the backing storage to use.
+\param bytes How many bytes to reserve (0 = the size of the section). Rounded up to nearest 64Kb on Windows.
+\param offset The offset into the backing storage to map from. Typically needs to be at least a multiple of the page size (see utils::page_sizes()), on Windows it needs to be a multiple of the kernel memory allocation granularity (typically 64Kb).
+\param _flag The permissions with which to map the view which are constrained by the permissions of the memory section. `flag::none` can be useful for reserving virtual address space without committing system resources, use commit() to later change availability of memory.
+
+\errors Any of the values POSIX mmap() or NtMapViewOfSection() can return.
+*/
+inline result<map_handle> map(section_handle &section, map_handle::size_type bytes = 0, map_handle::extent_type offset = 0, section_handle::flag _flag = section_handle::flag::readwrite) noexcept
+{
+ return map_handle::map(std::forward<decltype(section)>(section), std::forward<decltype(bytes)>(bytes), std::forward<decltype(offset)>(offset), std::forward<decltype(_flag)>(_flag));
+}
+//! The size of the memory map. This is the accessible size, NOT the reservation size.
+inline map_handle::size_type length(const map_handle &self) noexcept
+{
+ return self.length();
+}
+/*! Resize the reservation of the memory map without changing the address (unless the map
+was zero sized, in which case a new address will be chosen).
+
+If shrinking, address space is released on POSIX, and on Windows if the new size is zero.
+If the new size is zero, the address is set to null to prevent surprises.
+Windows does not support modifying existing mapped regions, so if the new size is not
+zero, the call will probably fail. Windows should let you truncate a previous extension
+however, if it is exact.
+
+If expanding, an attempt is made to map in new reservation immediately after the current address
+reservation, thus extending the reservation. If anything else is mapped in after
+the current reservation, the function fails.
+
+\note On all supported platforms apart from OS X, proprietary flags exist to avoid
+performing a map if a map extension cannot be immediately placed after the current map. On OS X,
+we hint where we'd like the new map to go, but if something is already there OS X will
+place the map elsewhere. In this situation, we delete the new map and return failure,
+which is inefficient, but there is nothing else we can do.
+
+\return The bytes actually reserved.
+\param self The object whose member function to call.
+\param newsize The bytes to truncate the map reservation to. Rounded up to the nearest page size (POSIX) or 64Kb on Windows.
+\param permit_relocation Permit the address to change (some OSs provide a syscall for resizing
+a memory map).
+\errors Any of the values POSIX `mremap()`, `mmap(addr)` or `VirtualAlloc(addr)` can return.
+*/
+inline result<map_handle::size_type> truncate(map_handle &self, map_handle::size_type newsize, bool permit_relocation = false) noexcept
+{
+ return self.truncate(std::forward<decltype(newsize)>(newsize), std::forward<decltype(permit_relocation)>(permit_relocation));
+}
+/*! \brief Read data from the mapped view.
+
+\note Because this implementation never copies memory, you can pass in buffers with a null address.
+
+\return The buffers read, which will never be the buffers input because they will point into the mapped view.
+The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\param d Ignored.
+\errors None, though the various signals and structured exception throws common to using memory maps may occur.
+\mallocs None.
+*/
+inline map_handle::io_result<map_handle::buffers_type> read(map_handle &self, map_handle::io_request<map_handle::buffers_type> reqs, deadline d = deadline()) noexcept
+{
+ return self.read(std::forward<decltype(reqs)>(reqs), std::forward<decltype(d)>(d));
+}
+/*! \brief Write data to the mapped view.
+
+\return The buffers written, which will never be the buffers input because they will point at where the data was copied into the mapped view.
+The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+\param self The object whose member function to call.
+\param reqs A scatter-gather and offset request.
+\param d Ignored.
+\errors None, though the various signals and structured exception throws common to using memory maps may occur.
+\mallocs None.
+*/
+inline map_handle::io_result<map_handle::const_buffers_type> write(map_handle &self, map_handle::io_request<map_handle::const_buffers_type> reqs, deadline d = deadline()) noexcept
+{
+ return self.write(std::forward<decltype(reqs)>(reqs), std::forward<decltype(d)>(d));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/map_handle.ipp"
+#else
+#include "detail/impl/posix/map_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/mapped_file_handle.hpp b/include/llfio/v2.0/mapped_file_handle.hpp
new file mode 100644
index 00000000..37c0b49b
--- /dev/null
+++ b/include/llfio/v2.0/mapped_file_handle.hpp
@@ -0,0 +1,519 @@
+/* An mapped handle to a file
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#include "map_handle.hpp"
+
+//! \file mapped_file_handle.hpp Provides mapped_file_handle
+
+#ifndef AFIO_MAPPED_FILE_HANDLE_H
+#define AFIO_MAPPED_FILE_HANDLE_H
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+/*! \class mapped_file_handle
+\brief A memory mapped regular file or device
+
+<table>
+<tr><th></th><th>Cost of opening</th><th>Cost of i/o</th><th>Concurrency and Atomicity</th><th>Other remarks</th></tr>
+<tr><td>`file_handle`</td><td>Least</td><td>Syscall</td><td>POSIX guarantees (usually)</td><td>Least gotcha</td></tr>
+<tr><td>`async_file_handle`</td><td>More</td><td>Most (syscall + malloc/free + reactor)</td><td>POSIX guarantees (usually)</td><td>Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`</td></tr>
+<tr><td>`mapped_file_handle`</td><td>Most</td><td>Least</td><td>None</td><td>Cannot be used with uncached i/o</td></tr>
+</table>
+
+All the major OSs on all the major 64 bit CPU architectures now offer at least 127 Tb of address
+spaces to user mode processes. This makes feasible mapping multi-Tb files directly into
+memory, and thus avoiding the syscall overhead involved when reading and writing. This
+becames **especially** important with next-gen storage devices capable of Direct Access
+Storage (DAX) like Optane from 2018 onwards, performance via syscalls will always be
+but a fraction of speaking directly to the storage device via directly mapped memory.
+
+As an example of the gains, on Microsoft Windows to read or write 1Kb using the standard
+syscalls takes about fifteen times longer than the exact same i/o via mapped memory. On Linux,
+OS X or FreeBSD the gain is considerably lower, a 1Kb i/o might only be 50% slower via syscalls
+than memory maps. However for lots of say 64 byte i/o, the gain of memory maps over
+syscalls is unsurpassable.
+
+This class combines a `file_handle` with a `section_handle` and a `map_handle` to
+implement a fully memory mapped `file_handle`. The whole file is always mapped entirely
+into memory, and `read()` and `write()` i/o is performed directly with the map.
+Reads always return the original mapped data, and do not fill any buffers passed in.
+For obvious reasons the utility of this class on 32-bit systems is limited,
+but can be useful when used with smaller files.
+
+Note that zero length files cannot be memory mapped, and writes past the maximum
+extent do NOT auto-extend the size of the file, rather the data written beyond the maximum valid
+extent has undefined kernel-specific behaviour, which includes segfaulting. You must therefore always
+`truncate(newsize)` to resize the file and its maps before you can read or write to it,
+and be VERY careful to not read or write beyond the maximum extent of the file.
+
+Therefore, when a file is created or is otherwise of zero length, `address()` will return
+a null pointer. Similarly, calling `truncate(0)` will close the map and section handles,
+they will be recreated on next truncation to a non-zero size.
+
+For better performance when handling files which are growing, there is a concept of
+"address space reservation" via `reserve()` and `capacity()`, which on some kernels
+is automatically and efficiently expanded into when the underlying file grows.
+The implementation asks the
+kernel to set out a contiguous region of pages matching that reservation, and to map the
+file into the beginning of the reservation. The remainder of the pages may be inaccessible
+and may generate a segfault, or they may automatically reflect any growth in the underlying
+file. This is why `read()` and `write()` only know about the reservation size, and will read
+and write memory up to that reservation size, without checking if the memory involved exists
+or not yet. You are guaranteed that `address()` will not return a new
+value unless you truncate from a bigger length to a smaller length, or you call `reserve()`
+with a new reservation or `truncate()` with a value bigger than the reservation.
+
+`maximum_extent()` reports the last truncated length of the mapped file (possibly by any process in
+the system) up to the reservation limit, NOT the maximum extent of the underlying file.
+When you know that another process has extended the file and you wish to map the newly appended
+data, you can call `update_map()` which guarantees that the mapping your
+process sees is up to date, rather than relying on any kernel-specific automatic mapping.
+Whether automatic or enforced by `update_map()`, the reservation limit will not be exceeded
+nor will `address()` suddenly return something different.
+
+It is thus up to you to detect that the reservation has been exhausted, and to
+reserve a new reservation which will change the value returned by `address()`. This
+entirely manual system is a bit tedious and cumbersome to use, but as mapping files
+is an expensive operation given TLB shootdown, we leave it up to the end user to decide
+when to expend the cost of mapping.
+
+\warning You must be cautious when the file is being extended by third parties which are
+not using this `mapped_file_handle` to write the new data. With unified page cache kernels,
+mixing mapped and normal i/o is generally safe except at the end of a file where race
+conditions and outright kernel bugs tend to abound. To avoid these, solely and exclusively
+use a dedicated handle configured to atomic append only to do the appends.
+*/
+class AFIO_DECL mapped_file_handle : public file_handle
+{
+public:
+ using dev_t = file_handle::dev_t;
+ using ino_t = file_handle::ino_t;
+ using path_view_type = file_handle::path_view_type;
+ using path_type = io_handle::path_type;
+ using extent_type = io_handle::extent_type;
+ using size_type = io_handle::size_type;
+ using mode = io_handle::mode;
+ using creation = io_handle::creation;
+ using caching = io_handle::caching;
+ using flag = io_handle::flag;
+ using buffer_type = io_handle::buffer_type;
+ using const_buffer_type = io_handle::const_buffer_type;
+ using buffers_type = io_handle::buffers_type;
+ using const_buffers_type = io_handle::const_buffers_type;
+ template <class T> using io_request = io_handle::io_request<T>;
+ template <class T> using io_result = io_handle::io_result<T>;
+
+protected:
+ size_type _reservation{0};
+ section_handle _sh; // Tracks the file (i.e. *this) somewhat lazily
+ map_handle _mh; // The current map with valid extent
+
+public:
+ //! Default constructor
+ constexpr mapped_file_handle() {} // NOLINT
+
+ //! Implicit move construction of mapped_file_handle permitted
+ mapped_file_handle(mapped_file_handle &&o) noexcept : file_handle(std::move(o)), _reservation(o._reservation), _sh(std::move(o._sh)), _mh(std::move(o._mh))
+ {
+ _sh.set_backing(this);
+ _mh.set_section(&_sh);
+ }
+ //! No copy construction (use `clone()`)
+ mapped_file_handle(const mapped_file_handle &) = delete;
+ //! Explicit conversion from file_handle permitted
+ explicit constexpr mapped_file_handle(file_handle &&o) noexcept : file_handle(std::move(o)) {}
+ //! Explicit conversion from file_handle permitted, this overload also attempts to map the file
+ explicit mapped_file_handle(file_handle &&o, size_type reservation) noexcept : file_handle(std::move(o))
+ {
+ auto out = reserve(reservation);
+ if(!out)
+ {
+ _reservation = utils::round_up_to_page_size(reservation);
+ // sink the error
+ }
+ }
+ //! Move assignment of mapped_file_handle permitted
+ mapped_file_handle &operator=(mapped_file_handle &&o) noexcept
+ {
+ this->~mapped_file_handle();
+ new(this) mapped_file_handle(std::move(o));
+ return *this;
+ }
+ //! No copy assignment
+ mapped_file_handle &operator=(const mapped_file_handle &) = delete;
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(mapped_file_handle &o) noexcept
+ {
+ mapped_file_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! Create a memory mapped file handle opening access to a file on path.
+ \param reservation The number of bytes to reserve for later expansion when mapping. Zero means reserve only the current file length.
+ \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+ \param _path The path relative to base to open.
+ \param _mode How to open the file.
+ \param _creation How to create the file.
+ \param _caching How to ask the kernel to cache the file.
+ \param flags Any additional custom behaviours.
+
+ Note that if the file is currently zero sized, no mapping occurs now, but
+ later when `truncate()` or `update_map()` is called.
+
+ \errors Any of the values which the constructors for `file_handle`, `section_handle` and `map_handle` can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_file(size_type reservation, const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept
+ {
+ if(_mode == mode::append)
+ {
+ return errc::invalid_argument;
+ }
+ OUTCOME_TRY(fh, file_handle::file(base, _path, _mode, _creation, _caching, flags));
+ switch(_creation)
+ {
+ default:
+ {
+ // Attempt mapping now (may silently fail if file is empty)
+ mapped_file_handle mfh(std::move(fh), reservation);
+ return {std::move(mfh)};
+ }
+ case creation::only_if_not_exist:
+ case creation::truncate:
+ {
+ // Don't attempt mapping now as file will be empty
+ mapped_file_handle mfh(std::move(fh));
+ mfh._reservation = reservation;
+ return {std::move(mfh)};
+ }
+ }
+ }
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_file(const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept { return mapped_file(0, base, _path, _mode, _creation, _caching, flags); }
+
+ /*! Create an mapped file handle creating a randomly named file on a path.
+ The file is opened exclusively with `creation::only_if_not_exist` so it
+ will never collide with nor overwrite any existing file. Note also
+ that caching defaults to temporary which hints to the OS to only
+ flush changes to physical storage as lately as possible.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_random_file(size_type reservation, const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::temporary, flag flags = flag::none) noexcept
+ {
+ try
+ {
+ for(;;)
+ {
+ auto randomname = utils::random_string(32);
+ randomname.append(".random");
+ result<mapped_file_handle> ret = mapped_file(reservation, dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags);
+ if(ret || (!ret && ret.error() != errc::file_exists))
+ {
+ return ret;
+ }
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ /*! Create a mapped file handle creating the named file on some path which
+ the OS declares to be suitable for temporary files. Most OSs are
+ very lazy about flushing changes made to these temporary files.
+ Note the default flags are to have the newly created file deleted
+ on first handle close.
+ Note also that an empty name is equivalent to calling
+ `mapped_random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+ parameter is ignored.
+
+ \note If the temporary file you are creating is not going to have its
+ path sent to another process for usage, this is the WRONG function
+ to use. Use `temp_inode()` instead, it is far more secure.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_temp_file(size_type reservation, path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::temporary, flag flags = flag::unlink_on_first_close) noexcept
+ {
+ auto &tempdirh = path_discovery::storage_backed_temporary_files_directory();
+ return name.empty() ? mapped_random_file(reservation, tempdirh, _mode, _caching, flags) : mapped_file(reservation, tempdirh, name, _mode, _creation, _caching, flags);
+ }
+ /*! \em Securely create a mapped file handle creating a temporary anonymous inode in
+ the filesystem referred to by \em dirpath. The inode created has
+ no name nor accessible path on the filing system and ceases to
+ exist as soon as the last handle is closed, making it ideal for use as
+ a temporary file where other processes do not need to have access
+ to its contents via some path on the filing system (a classic use case
+ is for backing shared memory maps).
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<mapped_file_handle> mapped_temp_inode(const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept
+ {
+ OUTCOME_TRY(v, file_handle::temp_inode(dir, _mode, flags));
+ mapped_file_handle ret(std::move(v));
+ return std::move(ret);
+ }
+
+ //! The memory section this handle is using
+ const section_handle &section() const noexcept { return _sh; }
+ //! The memory section this handle is using
+ section_handle &section() noexcept { return _sh; }
+
+ //! The map this handle is using
+ const map_handle &map() const noexcept { return _mh; }
+ //! The map this handle is using
+ map_handle &map() noexcept { return _mh; }
+
+ //! The address in memory where this mapped file resides
+ byte *address() const noexcept { return _mh.address(); }
+
+ //! The maximum extent of the underlying file
+ result<extent_type> underlying_file_maximum_extent() const noexcept { return file_handle::maximum_extent(); }
+
+ //! The address space (to be) reserved for future expansion of this file.
+ size_type capacity() const noexcept { return _reservation; }
+
+ /*! \brief Reserve a new amount of address space for mapping future expansion of this file.
+ \param reservation The number of bytes of virtual address space to reserve. Zero means reserve
+ the current length of the underlying file.
+
+ Note that this is an expensive call, and `address()` may return a different value afterwards.
+ This call will fail if the underlying file has zero length.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_type> reserve(size_type reservation = 0) noexcept;
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~mapped_file_handle() override
+ {
+ if(_v)
+ {
+ (void) mapped_file_handle::close();
+ }
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override { return _mh.barrier(reqs, wait_for_device, and_metadata, d); }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept override
+ {
+ OUTCOME_TRY(fh, file_handle::clone(mode_, caching_, d));
+ mapped_file_handle ret(std::move(fh), _reservation);
+ return static_cast<file_handle &&>(ret);
+ }
+ result<mapped_file_handle> clone(size_type reservation, mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept
+ {
+ OUTCOME_TRY(fh, file_handle::clone(mode_, caching_, d));
+ return mapped_file_handle(std::move(fh), reservation);
+ }
+ //! Return the current maximum permitted extent of the file.
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> maximum_extent() const noexcept override { return _mh.length(); }
+
+ /*! \brief Resize the current maximum permitted extent of the mapped file to the given extent, avoiding any
+ new allocation of physical storage where supported, and mapping or unmapping any new pages
+ up to the reservation to reflect the new maximum extent. If the new size exceeds the reservation,
+ `reserve()` will be called to increase the reservation.
+
+ Note that on extents based filing systems
+ this will succeed even if there is insufficient free space on the storage medium. Only when
+ pages are written to will the lack of sufficient free space be realised, resulting in an
+ operating system specific exception.
+
+ \note On Microsoft Windows you cannot shrink a file with a section handle open on it in any
+ process in the system. We therefore *always* destroy the internal map and section before
+ truncating, and then recreate the map and section afterwards if the new size is not zero.
+ `address()` therefore may change.
+ You will need to ensure all other users of the same file close their section and
+ map handles before any process can shrink the underlying file.
+
+ \return The bytes actually truncated to.
+ \param newsize The bytes to truncate the file to. Zero causes the maps to be closed before
+ truncation.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> truncate(extent_type newsize) noexcept override;
+
+ /*! \brief Efficiently update the mapping to match that of the underlying file,
+ returning the size of the underlying file.
+
+ This call is often considerably less heavyweight than `truncate(newsize)`, and should be used where possible.
+
+ If the internal section and map handle are invalid, they are restored unless the underlying file is zero length.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<extent_type> update_map() noexcept;
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> zero(extent_type offset, extent_type bytes, deadline /*unused*/ = deadline()) noexcept override
+ {
+ OUTCOME_TRYV(_mh.zero_memory({_mh.address() + offset, bytes}));
+ return bytes;
+ }
+
+ using file_handle::read;
+ using file_handle::write;
+
+ /*! \brief Read data from the mapped file.
+
+ \note Because this implementation never copies memory, you can pass in buffers with a null address. As this
+ function never reads any memory, no attempt to trap signal raises can be made, this falls onto the user of
+ this function. See `QUICKCPPLIB_NAMESPACE::signal_guard` for a helper function.
+
+ \return The buffers read, which will never be the buffers input, because they will point into the mapped view.
+ The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d Ignored.
+ \errors None, though the various signals and structured exception throws common to using memory maps may occur.
+ \mallocs None.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override { return _mh.read(reqs, d); }
+ /*! \brief Write data to the mapped file.
+
+ \note This call traps signals and structured exception throws using `QUICKCPPLIB_NAMESPACE::signal_guard`.
+ Instantiating a `QUICKCPPLIB_NAMESPACE::signal_guard_install` somewhere much higher up in the call stack
+ will improve performance enormously. The signal guard may cost less than 100 CPU cycles depending on how
+ you configure it. If you don't want the guard, you can write memory directly using `address()`.
+
+ \return The buffers written, which will never be the buffers input because they will point at where the data was copied into the mapped view.
+ The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d Ignored.
+ \errors If during the attempt to write the buffers to the map a `SIGBUS` or `EXCEPTION_IN_PAGE_ERROR` is raised,
+ an error code comparing equal to `errc::no_space_on_device` will be returned. This may not always be the cause
+ of the raised signal, but it is by far the most likely.
+ \mallocs None if a `QUICKCPPLIB_NAMESPACE::signal_guard_install` is already instanced.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override { return _mh.write(reqs, d); }
+};
+
+//! \brief Constructor for `mapped_file_handle`
+template <> struct construct<mapped_file_handle>
+{
+ mapped_file_handle::size_type reservation;
+ const path_handle &base;
+ mapped_file_handle::path_view_type _path;
+ mapped_file_handle::mode _mode = mapped_file_handle::mode::read;
+ mapped_file_handle::creation _creation = mapped_file_handle::creation::open_existing;
+ mapped_file_handle::caching _caching = mapped_file_handle::caching::all;
+ mapped_file_handle::flag flags = mapped_file_handle::flag::none;
+ result<mapped_file_handle> operator()() const noexcept { return mapped_file_handle::mapped_file(reservation, base, _path, _mode, _creation, _caching, flags); }
+};
+
+
+// BEGIN make_free_functions.py
+//! Swap with another instance
+inline void swap(mapped_file_handle &self, mapped_file_handle &o) noexcept
+{
+ return self.swap(std::forward<decltype(o)>(o));
+}
+/*! Create a memory mapped file handle opening access to a file on path.
+\param reservation The number of bytes to reserve for later expansion when mapping. Zero means reserve only the current file length.
+\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+\param _path The path relative to base to open.
+\param _mode How to open the file.
+\param _creation How to create the file.
+\param _caching How to ask the kernel to cache the file.
+\param flags Any additional custom behaviours.
+
+Note that if the file is currently zero sized, no mapping occurs now, but
+later when `truncate()` or `update_map()` is called.
+
+\errors Any of the values which the constructors for `file_handle`, `section_handle` and `map_handle` can return.
+*/
+inline result<mapped_file_handle> mapped_file(mapped_file_handle::size_type reservation, const path_handle &base, mapped_file_handle::path_view_type _path, mapped_file_handle::mode _mode = mapped_file_handle::mode::read, mapped_file_handle::creation _creation = mapped_file_handle::creation::open_existing,
+ mapped_file_handle::caching _caching = mapped_file_handle::caching::all, mapped_file_handle::flag flags = mapped_file_handle::flag::none) noexcept
+{
+ return mapped_file_handle::mapped_file(std::forward<decltype(reservation)>(reservation), std::forward<decltype(base)>(base), std::forward<decltype(_path)>(_path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching),
+ std::forward<decltype(flags)>(flags));
+}
+//! \overload
+inline result<mapped_file_handle> mapped_file(const path_handle &base, mapped_file_handle::path_view_type _path, mapped_file_handle::mode _mode = mapped_file_handle::mode::read, mapped_file_handle::creation _creation = mapped_file_handle::creation::open_existing,
+ mapped_file_handle::caching _caching = mapped_file_handle::caching::all, mapped_file_handle::flag flags = mapped_file_handle::flag::none) noexcept
+{
+ return mapped_file_handle::mapped_file(std::forward<decltype(base)>(base), std::forward<decltype(_path)>(_path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create an mapped file handle creating a randomly named file on a path.
+The file is opened exclusively with `creation::only_if_not_exist` so it
+will never collide with nor overwrite any existing file. Note also
+that caching defaults to temporary which hints to the OS to only
+flush changes to physical storage as lately as possible.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<mapped_file_handle> mapped_random_file(mapped_file_handle::size_type reservation, const path_handle &dirpath, mapped_file_handle::mode _mode = mapped_file_handle::mode::write, mapped_file_handle::caching _caching = mapped_file_handle::caching::temporary,
+ mapped_file_handle::flag flags = mapped_file_handle::flag::none) noexcept
+{
+ return mapped_file_handle::mapped_random_file(std::forward<decltype(reservation)>(reservation), std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! Create a mapped file handle creating the named file on some path which
+the OS declares to be suitable for temporary files. Most OSs are
+very lazy about flushing changes made to these temporary files.
+Note the default flags are to have the newly created file deleted
+on first handle close.
+Note also that an empty name is equivalent to calling
+`mapped_random_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
+parameter is ignored.
+
+\note If the temporary file you are creating is not going to have its
+path sent to another process for usage, this is the WRONG function
+to use. Use `temp_inode()` instead, it is far more secure.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<mapped_file_handle> mapped_temp_file(mapped_file_handle::size_type reservation, mapped_file_handle::path_view_type name = mapped_file_handle::path_view_type(), mapped_file_handle::mode _mode = mapped_file_handle::mode::write,
+ mapped_file_handle::creation _creation = mapped_file_handle::creation::if_needed, mapped_file_handle::caching _caching = mapped_file_handle::caching::temporary, mapped_file_handle::flag flags = mapped_file_handle::flag::unlink_on_first_close) noexcept
+{
+ return mapped_file_handle::mapped_temp_file(std::forward<decltype(reservation)>(reservation), std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
+}
+/*! \em Securely create a mapped file handle creating a temporary anonymous inode in
+the filesystem referred to by \em dirpath. The inode created has
+no name nor accessible path on the filing system and ceases to
+exist as soon as the last handle is closed, making it ideal for use as
+a temporary file where other processes do not need to have access
+to its contents via some path on the filing system (a classic use case
+is for backing shared memory maps).
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<mapped_file_handle> mapped_temp_inode(const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), mapped_file_handle::mode _mode = mapped_file_handle::mode::write, mapped_file_handle::flag flags = mapped_file_handle::flag::none) noexcept
+{
+ return mapped_file_handle::mapped_temp_inode(std::forward<decltype(dir)>(dir), std::forward<decltype(_mode)>(_mode), std::forward<decltype(flags)>(flags));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/mapped_file_handle.ipp"
+#else
+#include "detail/impl/posix/mapped_file_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/native_handle_type.hpp b/include/llfio/v2.0/native_handle_type.hpp
new file mode 100644
index 00000000..7c351f83
--- /dev/null
+++ b/include/llfio/v2.0/native_handle_type.hpp
@@ -0,0 +1,147 @@
+/* Wraps the platform specific i/o reference object
+(C) 2016-2017 Niall Douglas <http://www.nedproductions.biz/> (8 commits)
+File Created: March 2016
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_CONFIG_HPP
+#error You must include the master afio.hpp, not individual header files directly
+#endif
+#include "config.hpp"
+
+//! \file native_handle_type.hpp Provides native_handle_type
+
+#ifndef AFIO_NATIVE_HANDLE_TYPE_H
+#define AFIO_NATIVE_HANDLE_TYPE_H
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+/*! \struct native_handle_type
+\brief A native handle type used for wrapping file descriptors, process ids or HANDLEs.
+Unmanaged, wrap in a handle object to manage.
+*/
+struct native_handle_type // NOLINT
+{
+ //! The type of handle.
+ QUICKCPPLIB_BITFIELD_BEGIN(disposition)
+ {
+ invalid = 0U, //!< Invalid handle
+
+ readable = 1U << 0U, //!< Is readable
+ writable = 1U << 1U, //!< Is writable
+ append_only = 1U << 2U, //!< Is append only
+
+ overlapped = 1U << 4U, //!< Requires additional synchronisation
+ seekable = 1U << 5U, //!< Is seekable
+ aligned_io = 1U << 6U, //!< Requires sector aligned i/o (typically 512 or 4096)
+
+ file = 1U << 8U, //!< Is a regular file
+ directory = 1U << 9U, //!< Is a directory
+ symlink = 1U << 10U, //!< Is a symlink
+ multiplexer = 1U << 11U, //!< Is a kqueue/epoll/iocp
+ process = 1U << 12U, //!< Is a child process
+ section = 1U << 13U //!< Is a memory section
+ }
+ QUICKCPPLIB_BITFIELD_END(disposition)
+ disposition behaviour; //! The behaviour of the handle
+ union {
+ intptr_t _init{-1};
+ //! A POSIX file descriptor
+ int fd; // NOLINT
+ //! A POSIX process identifier
+ int pid; // NOLINT
+ //! A Windows HANDLE
+ win::handle h; // NOLINT
+ };
+ //! Constructs a default instance
+ constexpr native_handle_type() {} // NOLINT
+ ~native_handle_type() = default;
+ //! Construct from a POSIX file descriptor
+ constexpr native_handle_type(disposition _behaviour, int _fd) noexcept : behaviour(_behaviour), fd(_fd) {} // NOLINT
+ //! Construct from a Windows HANDLE
+ constexpr native_handle_type(disposition _behaviour, win::handle _h) noexcept : behaviour(_behaviour), h(_h) {} // NOLINT
+
+ //! Copy construct
+ native_handle_type(const native_handle_type &) = default;
+ //! Move construct
+ constexpr native_handle_type(native_handle_type &&o) noexcept : behaviour(o.behaviour), _init(o._init) // NOLINT
+ {
+ o.behaviour = disposition();
+ o._init = -1;
+ }
+ //! Copy assign
+ native_handle_type &operator=(const native_handle_type &) = default;
+ //! Move assign
+ constexpr native_handle_type &operator=(native_handle_type &&o) noexcept
+ {
+ behaviour = o.behaviour;
+ _init = o._init;
+ o.behaviour = disposition();
+ o._init = -1;
+ return *this;
+ }
+ //! Swaps with another instance
+ void swap(native_handle_type &o) noexcept
+ {
+ std::swap(behaviour, o.behaviour);
+ std::swap(_init, o._init);
+ }
+
+ //! True if valid
+ explicit constexpr operator bool() const noexcept { return is_valid(); }
+ //! True if invalid
+ constexpr bool operator!() const noexcept { return !is_valid(); }
+
+ //! True if the handle is valid
+ constexpr bool is_valid() const noexcept { return _init != -1 && static_cast<unsigned>(behaviour) != 0; }
+
+ //! True if the handle is readable
+ constexpr bool is_readable() const noexcept { return (behaviour & disposition::readable) ? true : false; }
+ //! True if the handle is writable
+ constexpr bool is_writable() const noexcept { return (behaviour & disposition::writable) ? true : false; }
+ //! True if the handle is append only
+ constexpr bool is_append_only() const noexcept { return (behaviour & disposition::append_only) ? true : false; }
+
+ //! True if overlapped
+ constexpr bool is_overlapped() const noexcept { return (behaviour & disposition::overlapped) ? true : false; }
+ //! True if seekable
+ constexpr bool is_seekable() const noexcept { return (behaviour & disposition::seekable) ? true : false; }
+ //! True if requires aligned i/o
+ constexpr bool requires_aligned_io() const noexcept { return (behaviour & disposition::aligned_io) ? true : false; }
+
+ //! True if a regular file or device
+ constexpr bool is_regular() const noexcept { return (behaviour & disposition::file) ? true : false; }
+ //! True if a directory
+ constexpr bool is_directory() const noexcept { return (behaviour & disposition::directory) ? true : false; }
+ //! True if a symlink
+ constexpr bool is_symlink() const noexcept { return (behaviour & disposition::symlink) ? true : false; }
+ //! True if a multiplexer like BSD kqueues, Linux epoll or Windows IOCP
+ constexpr bool is_multiplexer() const noexcept { return (behaviour & disposition::multiplexer) ? true : false; }
+ //! True if a process
+ constexpr bool is_process() const noexcept { return (behaviour & disposition::process) ? true : false; }
+ //! True if a memory section
+ constexpr bool is_section() const noexcept { return (behaviour & disposition::section) ? true : false; }
+};
+
+AFIO_V2_NAMESPACE_END
+
+
+#endif
diff --git a/include/llfio/v2.0/outcome b/include/llfio/v2.0/outcome
new file mode 160000
+Subproject 2f521b22a9417cd75108c73aa62822a17538fa9
diff --git a/include/llfio/v2.0/path_discovery.hpp b/include/llfio/v2.0/path_discovery.hpp
new file mode 100644
index 00000000..ad6ee9fb
--- /dev/null
+++ b/include/llfio/v2.0/path_discovery.hpp
@@ -0,0 +1,130 @@
+/* Discovery of various useful filesystem paths
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Sept 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_PATH_DISCOVERY_H
+#define AFIO_PATH_DISCOVERY_H
+
+#include "fs_handle.hpp"
+#include "stat.hpp"
+
+//! \file path_discovery.hpp Provides `path_discovery`
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+//! \brief Contains functions used to discover suitable paths for things
+namespace path_discovery
+{
+ //! \brief A discovered path.
+ struct discovered_path
+ {
+ path_view path; //!< The path discovered.
+ //! Source of the discovered path.
+ enum class source_type
+ {
+ unknown, //!< This path comes from an unknown source.
+ local, //!< This path was added locally.
+ environment, //!< This path came from an environment variable (an override?).
+ system, //!< This path came from querying the system.
+ hardcoded //!< This path came from an internal hardcoded list of paths likely for this system.
+ } source{source_type::unknown};
+
+ /*! If this path was successfully probed for criteria verification, this was its stat after any symlink
+ derefencing at that time. Secure applications ought to verify that any handles opened to the path have
+ the same `st_ino` and `st_dev` as this structure before use.
+ */
+ optional<stat_t> stat;
+ };
+ inline std::ostream &operator<<(std::ostream &s, const discovered_path::source_type &v)
+ {
+ static constexpr const char *values[] = {"local", "environment", "system", "hardcoded"};
+ if(static_cast<size_t>(v) >= sizeof(values) / sizeof(values[0]) || (values[static_cast<size_t>(v)] == nullptr))
+ {
+ return s << "afio::path_discovery::discovered_path::source_type::<unknown>";
+ }
+ return s << "afio::path_discovery::discovered_path::source_type::" << values[static_cast<size_t>(v)];
+ }
+
+ /*! \brief Returns a list of potential directories which might be usuable for temporary files.
+
+ This is a fairly lightweight call which builds a master list of all potential temporary file directories
+ given the environment block of this process (unless SUID or SGID or Privilege Elevation are in effect) and the user
+ running this process. It does not verify if any of them exist, or are writable, or anything else about them.
+ An internal mutex is held for the duration of this call.
+
+ \mallocs Allocates the master list of discovered temporary directories exactly once per process,
+ unless `refresh` is true in which case the list will be refreshed. The system calls to retrieve paths
+ may allocate additional memory for paths returned.
+ \errors This call never fails, except to return an empty span.
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC span<discovered_path> all_temporary_directories(bool refresh = false) noexcept;
+
+ /*! \brief Returns a subset of `all_temporary_directories()` each of which has been tested to be writable
+ by the current process. No testing is done of available writable space.
+
+ After this call returns, the successfully probed entries returned by `all_temporary_directories()` will have their
+ stat structure set. As the probing involves creating a non-zero sized file in each possible temporary
+ directory to verify its validity, this is not a fast call. It is however cached statically, so the
+ cost occurs exactly once per process, unless someone calls `all_temporary_directories(true)` to wipe and refresh
+ the master list. An internal mutex is held for the duration of this call.
+ \mallocs None.
+ \errors This call never fails, though if it fails to find any writable temporary directory, it will
+ terminate the process.
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC span<discovered_path> verified_temporary_directories() noexcept;
+
+ /*! \brief Returns a reference to an open handle to a verified temporary directory where files created are
+ stored in a filesystem directory, usually under the current user's quota.
+
+ This is implemented by iterating all of the paths returned by `verified_temporary_directories()`
+ and checking what file system is in use. The following regex is used:
+
+ `btrfs|cifs|exfat|ext?|f2fs|hfs|jfs|lxfs|nfs|nilf2|ufs|vfat|xfs|zfs|msdosfs|newnfs|ntfs|smbfs|unionfs|fat|fat32`
+
+ The handle is created during `verified_temporary_directories()` and is statically cached thereafter.
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC const path_handle &storage_backed_temporary_files_directory() noexcept;
+
+ /*! \brief Returns a reference to an open handle to a verified temporary directory where files created are
+ stored in memory/paging file, and thus access may be a lot quicker, but stronger limits on
+ capacity may apply.
+
+ This is implemented by iterating all of the paths returned by `verified_temporary_directories()`
+ and checking what file system is in use. The following regex is used:
+
+ `tmpfs|ramfs`
+
+ The handle is created during `verified_temporary_directories()` and is statically cached thereafter.
+
+ \note If you wish to create an anonymous memory-backed inode for mmap and paging tricks like mapping
+ the same extent into multiple addresses e.g. to implement a constant time zero copy `realloc()`,
+ strongly consider using a non-file-backed `section_handle` as this is more portable.
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC const path_handle &memory_backed_temporary_files_directory() noexcept;
+} // namespace path_discovery
+
+AFIO_V2_NAMESPACE_END
+
+// .ipp is included by file_handle.hpp if in header only mode
+
+#endif
diff --git a/include/llfio/v2.0/path_handle.hpp b/include/llfio/v2.0/path_handle.hpp
new file mode 100644
index 00000000..b759fb1d
--- /dev/null
+++ b/include/llfio/v2.0/path_handle.hpp
@@ -0,0 +1,141 @@
+/* A handle to a filesystem location
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Jul 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_PATH_HANDLE_H
+#define AFIO_PATH_HANDLE_H
+
+#include "handle.hpp"
+#include "path_view.hpp"
+
+//! \file path_handle.hpp Provides a handle to a filesystem location.
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+class directory_handle;
+
+/*! \class path_handle
+\brief A handle to somewhere originally identified by a path on the filing system. Typically used
+as the lightest weight handle to some location on the filing system which may
+unpredictably relocate over time. This handle is thus an *anchor* to a subset island of the
+filing system, free of any race conditions introduced by third party changes to any part
+of the path leading to that island.
+*/
+class AFIO_DECL path_handle : public handle
+{
+ friend class directory_handle;
+
+public:
+ using path_type = handle::path_type;
+ using extent_type = handle::extent_type;
+ using size_type = handle::size_type;
+ using mode = handle::mode;
+ using creation = handle::creation;
+ using caching = handle::caching;
+ using flag = handle::flag;
+
+ //! The path view type used by this handle
+ using path_view_type = path_view;
+
+ //! Default constructor
+ constexpr path_handle() {} // NOLINT
+ ~path_handle() = default;
+ //! Construct a handle from a supplied native handle
+ explicit constexpr path_handle(native_handle_type h, caching caching = caching::all, flag flags = flag::none)
+ : handle(h, caching, flags)
+ {
+ }
+ //! Explicit conversion from handle permitted
+ explicit constexpr path_handle(handle &&o) noexcept : handle(std::move(o)) {}
+ //! Move construction permitted
+ path_handle(path_handle &&) = default;
+ //! No copy construction (use `clone()`)
+ path_handle(const path_handle &) = delete;
+ //! Move assignment permitted
+ path_handle &operator=(path_handle &&) = default;
+ //! No copy assignment
+ path_handle &operator=(const path_handle &) = delete;
+
+ /*! Create a path handle opening access to some location on the filing system.
+ Some operating systems provide a particularly lightweight method of doing this
+ (Linux: `O_PATH`, Windows: no access perms) which is much faster than opening
+ a directory. For other systems, we open a directory with read only permissions.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> path(const path_handle &base, path_view_type path) noexcept;
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> path(path_view_type _path) noexcept { return path(path_handle(), _path); }
+};
+
+//! \brief Constructor for `path_handle`
+template <> struct construct<path_handle>
+{
+ const path_handle &base;
+ path_handle::path_view_type _path;
+ result<path_handle> operator()() const noexcept { return path_handle::path(base, _path); }
+};
+
+// BEGIN make_free_functions.py
+/*! Create a path handle opening access to some location on the filing system.
+Some operating systems provide a particularly lightweight method of doing this
+(Linux: `O_PATH`, Windows: no access perms) which is much faster than opening
+a directory. For other systems, we open a directory with read only permissions.
+
+\errors Any of the values POSIX open() or CreateFile() can return.
+*/
+inline result<path_handle> path(const path_handle &base, path_handle::path_view_type path) noexcept
+{
+ return path_handle::path(std::forward<decltype(base)>(base), std::forward<decltype(path)>(path));
+}
+//! \overload
+inline result<path_handle> path(path_handle::path_view_type _path) noexcept
+{
+ return path_handle::path(std::forward<decltype(_path)>(_path));
+}
+// END make_free_functions.py
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/path_handle.ipp"
+#else
+#include "detail/impl/posix/path_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/path_view.hpp b/include/llfio/v2.0/path_view.hpp
new file mode 100644
index 00000000..ba81c695
--- /dev/null
+++ b/include/llfio/v2.0/path_view.hpp
@@ -0,0 +1,573 @@
+/* A view of a path to something
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (20 commits)
+File Created: Jul 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_PATH_VIEW_H
+#define AFIO_PATH_VIEW_H
+
+#include "config.hpp"
+
+//! \file path_view.hpp Provides view of a path
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace detail
+{
+#if(!_HAS_CXX17 && __cplusplus < 201700) || (defined(__GLIBCXX__) && __GLIBCXX__ <= 20170818) // libstdc++'s char_traits is missing constexpr
+ template <class T> constexpr size_t constexpr_strlen(const T *s) noexcept
+ {
+ const T *e = s;
+ for(; *e; e++)
+ {
+ ;
+ }
+ return e - s;
+ }
+#endif
+} // namespace detail
+
+/*! \class path_view
+\brief A borrowed view of a path. A lightweight trivial-type alternative to
+`std::filesystem::path`.
+
+AFIO is sufficiently fast that `std::filesystem::path` as a wrapper of an
+underlying `std::basic_string<>` can be problematically expensive for some
+filing system operations due to the potential memory allocation. AFIO
+therefore works exclusively with borrowed views of other path storage.
+
+Some of the API for `std::filesystem::path` is replicated here, however any
+APIs which modify the path other than taking subsets are obviously not
+possible with borrowed views.
+
+\todo Lots of member functions remain to be implemented.
+
+# Windows specific notes:
+
+Be aware that on Microsoft Windows, the native path storage
+`std::filesystem::path::value_type` is a `wchar_t` referring to UTF-16.
+However much of AFIO path usage is a `path_handle` to somewhere on the filing
+system plus a relative `const char *` UTF-8 path fragment as the use of
+absolute paths is discouraged. Rather than complicate the ABI to handle
+templated path character types, on Microsoft Windows only we do the following:
+
+- If view input is `wchar_t`, the original source is passed through unmodified
+to the syscall without any memory allocation, copying nor slash conversion.
+- If view input is `char`:
+ 1. The original source **is assumed to be in UTF-8**, not ASCII like most
+`char` paths on Microsoft Windows.
+ 2. Use with any kernel function converts to a temporary UTF-16 internal
+buffer. We use the fast NT kernel UTF8 to UTF16 routine, not the slow Win32
+routine.
+ 3. Any forward slashes are converted to backwards slashes.
+
+AFIO calls the NT kernel API directly rather than the Win32 API for:
+
+- For any paths relative to a `path_handle` (the Win32 API does not provide a
+race free file system API).
+- For any paths beginning with `\!!\`, we pass the path + 3 characters
+directly through. This prefix is a pure AFIO extension, and will not be
+recognised by other code.
+- For any paths beginning with `\??\`, we pass the path + 0 characters
+directly through. Note the NT kernel keeps a symlink at `\??\` which refers to
+the DosDevices namespace for the current login, so as an incorrect relation
+which you should **not** rely on, the Win32 path `C:\foo` probably will appear
+at `\??\C:\foo`.
+
+These prefixes are still passed to the Win32 API:
+
+- `\\?\` which is used to tell a Win32 API that the remaining path is longer
+than a DOS path.
+- `\\.\` which since Windows 7 is treated exactly like `\\?\`.
+
+If the NT kernel API is used directly then:
+
+- Paths are matched case sensitively as raw bytes via `memcmp()`, not case
+insensitively (requires slow locale conversion).
+- The path limit is 32,767 characters.
+
+If you really care about performance, you are very strongly recommended to use
+the NT kernel API wherever possible. Where paths are involved, it is often
+three to five times faster due to the multiple memory allocations and string
+translations that the Win32 functions perform before calling the NT kernel
+routine.
+
+If however you are taking input from some external piece of code, then for
+maximum compatibility you should still use the Win32 API.
+*/
+class AFIO_DECL path_view
+{
+public:
+ //! Character type
+ using value_type = char;
+ //! Pointer type
+ using pointer = char *;
+ //! Const pointer type
+ using const_pointer = const char *;
+ //! Reference type
+ using reference = char &;
+ //! Const reference type
+ using const_reference = const char &;
+ // const_iterator
+ // iterator
+ // reverse_iterator
+ // const_reverse_iterator
+ //! Size type
+ using size_type = std::size_t;
+ //! Difference type
+ using difference_type = std::ptrdiff_t;
+
+private:
+ static constexpr auto _npos = string_view::npos;
+#ifdef _WIN32
+ struct state
+ {
+ string_view _utf8;
+ wstring_view _utf16;
+
+ constexpr state() {} // NOLINT
+ constexpr explicit state(string_view v)
+ : _utf8(v)
+
+ {
+ }
+ constexpr explicit state(wstring_view v)
+ : _utf16(v)
+ {
+ }
+ constexpr void swap(state &o) noexcept
+ {
+ _utf8.swap(o._utf8);
+ _utf16.swap(o._utf16);
+ }
+ } _state;
+ template <class U> constexpr auto _invoke(U &&f) noexcept { return !_state._utf16.empty() ? f(_state._utf16) : f(_state._utf8); }
+ template <class U> constexpr auto _invoke(U &&f) const noexcept { return !_state._utf16.empty() ? f(_state._utf16) : f(_state._utf8); }
+ constexpr auto _find_last_sep() const noexcept
+ {
+ // wchar paths must use backslashes
+ if(!_state._utf16.empty())
+ {
+ return _state._utf16.rfind('\\');
+ }
+ // char paths can use either
+ return _state._utf8.find_last_of("/\\");
+ }
+#else
+ struct state
+ {
+ string_view _utf8;
+
+ constexpr state() {} // NOLINT
+ constexpr explicit state(string_view v)
+ : _utf8(v)
+ {
+ }
+ constexpr void swap(state &o) noexcept { _utf8.swap(o._utf8); }
+ } _state;
+ template <class U> constexpr auto _invoke(U &&f) noexcept { return f(_state._utf8); }
+ template <class U> constexpr auto _invoke(U &&f) const noexcept { return f(_state._utf8); }
+ constexpr auto _find_last_sep() const noexcept { return _state._utf8.rfind(filesystem::path::preferred_separator); }
+#endif
+public:
+ //! Constructs an empty path view
+ constexpr path_view() {} // NOLINT
+ ~path_view() = default;
+ //! Implicitly constructs a path view from a path. The input path MUST continue to exist for this view to be valid.
+ path_view(const filesystem::path &v) noexcept : _state(v.native()) {} // NOLINT
+ //! Implicitly constructs a UTF-8 path view from a string. The input string MUST continue to exist for this view to be valid.
+ path_view(const std::string &v) noexcept : _state(v) {} // NOLINT
+ //! Implicitly constructs a UTF-8 path view from a zero terminated `const char *`. The input string MUST continue to exist for this view to be valid.
+ constexpr path_view(const char *v) noexcept : // NOLINT
+#if(!_HAS_CXX17 && __cplusplus < 201700) || (defined(__GLIBCXX__) && __GLIBCXX__ <= 20170818) // libstdc++'s char_traits is missing constexpr
+ _state(string_view(v, detail::constexpr_strlen(v)))
+#else
+ _state(string_view(v))
+#endif
+ {
+ }
+ //! Constructs a UTF-8 path view from a lengthed `const char *`. The input string MUST continue to exist for this view to be valid.
+ constexpr path_view(const char *v, size_t len) noexcept : _state(string_view(v, len)) {}
+ /*! Implicitly constructs a UTF-8 path view from a string view.
+ \warning The byte after the end of the view must be legal to read.
+ */
+ constexpr path_view(string_view v) noexcept : _state(v) {} // NOLINT
+#ifdef _WIN32
+ //! Implicitly constructs a UTF-16 path view from a string. The input string MUST continue to exist for this view to be valid.
+ path_view(const std::wstring &v) noexcept : _state(v) {} // NOLINT
+ //! Implicitly constructs a UTF-16 path view from a zero terminated `const wchar_t *`. The input string MUST continue to exist for this view to be valid.
+ constexpr path_view(const wchar_t *v) noexcept : // NOLINT
+#if !_HAS_CXX17 && __cplusplus < 201700
+ _state(wstring_view(v, detail::constexpr_strlen(v)))
+#else
+ _state(wstring_view(v))
+#endif
+ {
+ }
+ //! Constructs a UTF-16 path view from a lengthed `const wchar_t *`. The input string MUST continue to exist for this view to be valid.
+ constexpr path_view(const wchar_t *v, size_t len) noexcept : _state(wstring_view(v, len)) {}
+ /*! Implicitly constructs a UTF-16 path view from a wide string view.
+ \warning The character after the end of the view must be legal to read.
+ */
+ constexpr path_view(wstring_view v) noexcept : _state(v) {} // NOLINT
+#endif
+ //! Default copy constructor
+ path_view(const path_view &) = default;
+ //! Default move constructor
+ path_view(path_view &&o) noexcept = default;
+ //! Default copy assignment
+ path_view &operator=(const path_view &p) = default;
+ //! Default move assignment
+ path_view &operator=(path_view &&p) noexcept = default;
+
+ //! Swap the view with another
+ constexpr void swap(path_view &o) noexcept { _state.swap(o._state); }
+
+ //! True if empty
+ constexpr bool empty() const noexcept
+ {
+ return _invoke([](const auto &v) { return v.empty(); });
+ }
+ constexpr bool has_root_path() const noexcept;
+ constexpr bool has_root_name() const noexcept;
+ constexpr bool has_root_directory() const noexcept;
+ constexpr bool has_relative_path() const noexcept;
+ constexpr bool has_parent_path() const noexcept;
+ constexpr bool has_filename() const noexcept;
+ constexpr bool has_stem() const noexcept;
+ constexpr bool has_extension() const noexcept;
+ constexpr bool is_absolute() const noexcept;
+ constexpr bool is_relative() const noexcept;
+ // True if the path view contains any of the characters `*`, `?`, (POSIX only: `[` or `]`).
+ constexpr bool contains_glob() const noexcept
+ {
+#ifdef _WIN32
+ if(!_state._utf16.empty())
+ {
+ return wstring_view::npos != _state._utf16.find_first_of(L"*?");
+ }
+ if(!_state._utf8.empty())
+ {
+ return wstring_view::npos != _state._utf8.find_first_of("*?");
+ }
+ return false;
+#else
+ return string_view::npos != _state._utf8.find_first_of("*?[]");
+#endif
+ }
+#ifdef _WIN32
+ // True if the path view is a NT kernel path starting with `\!!\` or `\??\`
+ constexpr bool is_ntpath() const noexcept
+ {
+ return _invoke([](const auto &v) {
+ if(v.size() < 4)
+ {
+ return false;
+ }
+ const auto *d = v.data();
+ if(d[0] == '\\' && d[1] == '!' && d[2] == '!' && d[3] == '\\')
+ {
+ return true;
+ }
+ if(d[0] == '\\' && d[1] == '?' && d[2] == '?' && d[3] == '\\')
+ {
+ return true;
+ }
+ return false;
+ });
+ }
+ // True if the path view matches the format of an AFIO deleted file
+ constexpr bool is_afio_deleted() const noexcept
+ {
+ return filename()._invoke([](const auto &v) {
+ if(v.size() == 64 + 8)
+ {
+ // Could be one of our "deleted" files, is he all hex + ".deleted"?
+ for(size_t n = 0; n < 64; n++)
+ {
+ auto c = v[n];
+ if(!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')))
+ {
+ return false;
+ }
+ }
+ return v[64] == '.' && v[65] == 'd' && v[66] == 'e' && v[67] == 'l' && v[68] == 'e' && v[69] == 't' && v[70] == 'e' && v[71] == 'd';
+ }
+ return false;
+ });
+ }
+#endif
+
+ //! Adjusts the end of this view to match the final separator.
+ constexpr void remove_filename() noexcept
+ {
+ auto sep_idx = _find_last_sep();
+ _invoke([sep_idx](auto &v) {
+ if(_npos == sep_idx)
+ {
+ v = {};
+ }
+ else
+ {
+ v.remove_suffix(v.size() - sep_idx);
+ }
+ });
+ }
+ //! Returns the size of the view in characters.
+ constexpr size_t native_size() const noexcept
+ {
+ return _invoke([](const auto &v) { return v.size(); });
+ }
+ constexpr path_view root_name() const noexcept;
+ constexpr path_view root_directory() const noexcept;
+ constexpr path_view root_path() const noexcept;
+ constexpr path_view relative_path() const noexcept;
+ constexpr path_view parent_path() const noexcept;
+ //! Returns a view of the filename part of this view.
+ constexpr path_view filename() const noexcept
+ {
+ auto sep_idx = _find_last_sep();
+ if(_npos == sep_idx)
+ {
+ return *this;
+ }
+ return _invoke([sep_idx](const auto &v) { return path_view(v.data() + sep_idx + 1, v.size() - sep_idx - 1); });
+ }
+ constexpr path_view stem() const noexcept;
+ constexpr path_view extension() const noexcept;
+
+ //! Return the path view as a path.
+ filesystem::path path() const
+ {
+#ifdef _WIN32
+ if(!_state._utf16.empty())
+ {
+ return filesystem::path(std::wstring(_state._utf16.data(), _state._utf16.size()));
+ }
+#endif
+ if(!_state._utf8.empty())
+ {
+ return filesystem::path(std::string(_state._utf8.data(), _state._utf8.size()));
+ }
+ return {};
+ }
+
+ /*! Compares the two string views via the view's `compare()` which in turn calls `traits::compare()`.
+ Be aware that on Windows a conversion from UTF-8 to UTF-16 is performed if needed.
+ */
+ constexpr int compare(const path_view &p) const noexcept
+ {
+ return _invoke([&p](const auto &v) { return -p.compare(v); });
+ }
+//! \overload
+#ifndef _WIN32
+ constexpr
+#endif
+ int compare(const char *s) const noexcept
+ {
+ return compare(string_view(s));
+ }
+//! \overload
+#ifndef _WIN32
+ constexpr
+#endif
+ int compare(string_view str) const noexcept
+ {
+#ifdef _WIN32
+ if(!_state._utf16.empty())
+ {
+ c_str z(path_view(str), false);
+ return _state._utf16.compare(wstring_view(z.buffer, z.length));
+ }
+#endif
+ return _state._utf8.compare(str);
+ }
+#ifdef _WIN32
+ int compare(const wchar_t *s) const noexcept { return compare(wstring_view(s)); }
+ int compare(wstring_view str) const noexcept
+ {
+ if(!_state._utf16.empty())
+ {
+ return _state._utf16.compare(str);
+ }
+ c_str z(path_view(*this), false);
+ return -str.compare(wstring_view(z.buffer, z.length));
+ }
+#endif
+
+ // iterator begin() const;
+ // iterator end() const;
+
+ //! Instantiate from a `path_view` to get a zero terminated path suitable for feeding to the kernel
+ struct AFIO_DECL c_str
+ {
+ //! Number of characters, excluding zero terminating char, at buffer
+ uint16_t length{0};
+ const filesystem::path::value_type *buffer{nullptr};
+
+#ifdef _WIN32
+ c_str(const path_view &view, bool ntkernelapi) noexcept
+ {
+ if(!view._state._utf16.empty())
+ {
+ if(view._state._utf16.size() > 32768)
+ {
+ AFIO_LOG_FATAL(&view, "Attempt to send a path exceeding 64Kb to kernel");
+ abort();
+ }
+ length = static_cast<uint16_t>(view._state._utf16.size());
+ // Is this going straight to a NT kernel API? If so, use directly
+ if(ntkernelapi)
+ {
+ buffer = view._state._utf16.data();
+ return;
+ }
+ // Is the byte just after the view a zero? If so, use directly
+ if(0 == view._state._utf16.data()[length])
+ {
+ buffer = view._state._utf16.data();
+ return;
+ }
+ // Otherwise use _buffer and zero terminate.
+ if(length > sizeof(_buffer) - 1)
+ {
+ AFIO_LOG_FATAL(&view, "Attempt to send a path exceeding 64Kb to kernel");
+ abort();
+ }
+ memcpy(_buffer, view._state._utf16.data(), length);
+ _buffer[length] = 0;
+ buffer = _buffer;
+ return;
+ }
+ if(!view._state._utf8.empty())
+ {
+ _from_utf8(view);
+ return;
+ }
+#else
+ c_str(const path_view &view) noexcept // NOLINT
+ {
+ if(!view._state._utf8.empty())
+ {
+ if(view._state._utf8.size() > 32768)
+ {
+ AFIO_LOG_FATAL(&view, "Attempt to send a path exceeding 64Kb to kernel");
+ abort();
+ }
+ length = static_cast<uint16_t>(view._state._utf8.size());
+ // Is the byte just after the view a zero? If so, use directly
+ if(0 == view._state._utf8.data()[length])
+ {
+ buffer = view._state._utf8.data();
+ return;
+ }
+ // Otherwise use _buffer and zero terminate.
+ if(length > sizeof(_buffer) - 1)
+ {
+ AFIO_LOG_FATAL(&view, "Attempt to send a path exceeding 32Kb to kernel");
+ abort();
+ }
+ memcpy(_buffer, view._state._utf8.data(), length);
+ _buffer[length] = 0;
+ buffer = _buffer;
+ return;
+ }
+#endif
+ length = 0;
+ _buffer[0] = 0;
+ buffer = _buffer;
+ }
+ ~c_str() = default;
+ c_str(const c_str &) = delete;
+ c_str(c_str &&) = delete;
+ c_str &operator=(const c_str &) = delete;
+ c_str &operator=(c_str &&) = delete;
+
+ private:
+ filesystem::path::value_type _buffer[32768]{};
+#ifdef _WIN32
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC void _from_utf8(const path_view &view) noexcept;
+#endif
+ };
+ friend struct c_str;
+};
+inline constexpr bool operator==(path_view x, path_view y) noexcept
+{
+ if(x.native_size() != y.native_size())
+ {
+ return false;
+ }
+ return x.compare(y) == 0;
+}
+inline constexpr bool operator!=(path_view x, path_view y) noexcept
+{
+ if(x.native_size() != y.native_size())
+ {
+ return true;
+ }
+ return x.compare(y) != 0;
+}
+inline constexpr bool operator<(path_view x, path_view y) noexcept
+{
+ return x.compare(y) < 0;
+}
+inline constexpr bool operator>(path_view x, path_view y) noexcept
+{
+ return x.compare(y) > 0;
+}
+inline constexpr bool operator<=(path_view x, path_view y) noexcept
+{
+ return x.compare(y) <= 0;
+}
+inline constexpr bool operator>=(path_view x, path_view y) noexcept
+{
+ return x.compare(y) >= 0;
+}
+inline std::ostream &operator<<(std::ostream &s, const path_view &v)
+{
+ return s << v.path();
+}
+
+#ifndef NDEBUG
+static_assert(std::is_trivially_copyable<path_view>::value, "path_view is not a trivially copyable!");
+#endif
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/path_view.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/quickcpplib b/include/llfio/v2.0/quickcpplib
new file mode 160000
+Subproject a820b995ea46514ecec9394324a9bb397853252
diff --git a/include/llfio/v2.0/stat.hpp b/include/llfio/v2.0/stat.hpp
new file mode 100644
index 00000000..c8cd5d4d
--- /dev/null
+++ b/include/llfio/v2.0/stat.hpp
@@ -0,0 +1,170 @@
+/* Information about a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (4 commits)
+File Created: Apr 2017
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_STAT_H
+#define AFIO_STAT_H
+
+#ifndef AFIO_CONFIG_HPP
+#error You must include the master afio.hpp, not individual header files directly
+#endif
+#include "config.hpp"
+
+//! \file stat.hpp Provides stat
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+class handle;
+
+/*! \struct stat_t
+\brief Metadata about a directory entry
+
+This structure looks somewhat like a `struct stat`, and indeed it was derived from BSD's `struct stat`.
+However there are a number of changes to better interoperate with modern practice, specifically:
+
+- inode value containers are forced to 64 bits.
+- Timestamps use C++11's `std::chrono::system_clock::time_point` or Boost equivalent. The resolution
+of these may or may not equal what a `struct timespec` can do depending on your STL.
+- The type of a file, which is available on Windows and on POSIX without needing an additional
+syscall, is provided by `st_type` which is one of the values from `filesystem::file_type`.
+- As type is now separate from permissions, there is no longer a `st_mode`, instead being a
+`st_perms` which is solely the permissions bits. If you want to test permission bits in `st_perms`
+but don't want to include platform specific headers, note that `filesystem::perms` contains
+definitions of the POSIX permissions flags.
+- The st_sparse and st_compressed flags indicate if your file is sparse and/or compressed, or if
+the directory will compress newly created files by default. Note that on POSIX, a file is sparse
+if and only if st_allocated < st_size which can include compressed files if that filing system is mounted
+with compression enabled (e.g. ZFS with ZLE compression which elides runs of zeros).
+- The st_reparse_point is a Windows only flag and is never set on POSIX, even on a NTFS volume.
+*/
+struct stat_t // NOLINT
+{
+ /* NOTE TO THOSE WHO WOULD MODIFY THIS:
+
+ These members need to be non-initialised. Do NOT initialise them. We WANT uninitialised data here
+ by default!
+ */
+ uint64_t st_dev; /*!< inode of device containing file (POSIX only) */
+ uint64_t st_ino; /*!< inode of file (Windows, POSIX) */
+ filesystem::file_type st_type; /*!< type of file (Windows, POSIX) */
+#ifndef _WIN32
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+ uint16_t st_perms;
+#else
+ stil1z::filesystem::perms st_perms; /*!< uint16_t bitfield perms of file (POSIX only) */
+#endif
+#endif
+ int16_t st_nlink; /*!< number of hard links (Windows, POSIX) */
+#ifndef _WIN32
+ int16_t st_uid; /*!< user ID of the file (POSIX only) */
+ int16_t st_gid; /*!< group ID of the file (POSIX only) */
+ dev_t st_rdev; /*!< id of file if special (POSIX only) */
+#endif
+ std::chrono::system_clock::time_point st_atim; /*!< time of last access (Windows, POSIX) */
+ std::chrono::system_clock::time_point st_mtim; /*!< time of last data modification (Windows, POSIX) */
+ std::chrono::system_clock::time_point st_ctim; /*!< time of last status change (Windows, POSIX) */
+ handle::extent_type st_size; /*!< file size, in bytes (Windows, POSIX) */
+ handle::extent_type st_allocated; /*!< bytes allocated for file (Windows, POSIX) */
+ handle::extent_type st_blocks; /*!< number of blocks allocated (Windows, POSIX) */
+ uint16_t st_blksize; /*!< block size used by this device (Windows, POSIX) */
+ uint32_t st_flags; /*!< user defined flags for file (FreeBSD, OS X, zero
+ otherwise) */
+ uint32_t st_gen; /*!< file generation number (FreeBSD, OS X, zero
+ otherwise)*/
+ std::chrono::system_clock::time_point st_birthtim; /*!< time of file creation (Windows, FreeBSD, OS X, zero otherwise) */
+
+ unsigned st_sparse : 1; /*!< if this file is sparse, or this directory capable of sparse files (Windows, POSIX) */
+ unsigned st_compressed : 1; /*!< if this file is compressed, or this directory capable of compressed files (Windows) */
+ unsigned st_reparse_point : 1; /*!< if this file or directory is a reparse point (Windows) */
+
+ //! Used to indicate what metadata should be filled in
+ QUICKCPPLIB_BITFIELD_BEGIN(want)
+ {
+ dev = 1 << 0, ino = 1 << 1, type = 1 << 2, perms = 1 << 3, nlink = 1 << 4, uid = 1 << 5, gid = 1 << 6, rdev = 1 << 7, atim = 1 << 8, mtim = 1 << 9, ctim = 1 << 10, size = 1 << 11, allocated = 1 << 12, blocks = 1 << 13, blksize = 1 << 14, flags = 1 << 15, gen = 1 << 16, birthtim = 1 << 17, sparse = 1 << 24,
+ compressed = 1 << 25, reparse_point = 1 << 26, all = static_cast<unsigned>(-1), none = 0
+ }
+ QUICKCPPLIB_BITFIELD_END(want)
+ //! Constructs a UNINITIALIZED instance i.e. full of random garbage
+ stat_t() {} // NOLINT CANNOT be constexpr because we are INTENTIONALLY not initialising the storage
+ //! Constructs a zeroed instance
+ constexpr explicit stat_t(std::nullptr_t) noexcept : st_dev(0), // NOLINT
+ st_ino(0), // NOLINT
+ st_type(filesystem::file_type::unknown), // NOLINT
+#ifndef _WIN32
+ st_perms(0), // NOLINT
+#endif
+ st_nlink(0), // NOLINT
+#ifndef _WIN32
+ st_uid(0), // NOLINT
+ st_gid(0), // NOLINT
+ st_rdev(0), // NOLINT
+#endif
+ st_size(0), // NOLINT
+ st_allocated(0), // NOLINT
+ st_blocks(0), // NOLINT
+ st_blksize(0), // NOLINT
+ st_flags(0), // NOLINT
+ st_gen(0), // NOLINT
+ st_sparse(0), // NOLINT
+ st_compressed(0), // NOLINT
+ st_reparse_point(0) // NOLINT
+ {
+ }
+#ifdef __cpp_exceptions
+ //! Constructs a filled instance, throwing as an exception any error which might occur
+ explicit stat_t(const handle &h, want wanted = want::all)
+ : stat_t(nullptr)
+ {
+ auto v(fill(h, wanted));
+ if(v.has_error())
+ {
+ v.error().throw_exception();
+ }
+ }
+#endif
+ //! Fills in the structure with metadata, returning number of items filled in
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> fill(const handle &h, want wanted = want::all) noexcept;
+};
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/stat.ipp"
+#else
+#include "detail/impl/posix/stat.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/statfs.hpp b/include/llfio/v2.0/statfs.hpp
new file mode 100644
index 00000000..7f0ae23b
--- /dev/null
+++ b/include/llfio/v2.0/statfs.hpp
@@ -0,0 +1,115 @@
+/* Information about the volume storing a file
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (8 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_STATFS_H
+#define AFIO_STATFS_H
+
+#ifndef AFIO_CONFIG_HPP
+#error You must include the master afio.hpp, not individual header files directly
+#endif
+#include "config.hpp"
+
+//! \file statfs.hpp Provides statfs
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+class handle;
+
+/*! \struct statfs_t
+\brief Metadata about a filing system. Unsupported entries are all bits set.
+*/
+struct AFIO_DECL statfs_t
+{
+ static constexpr uint32_t _allbits1_32 = ~0U;
+ static constexpr uint64_t _allbits1_64 = ~0ULL;
+ struct f_flags_t
+ {
+ uint32_t rdonly : 1; //!< Filing system is read only (Windows, POSIX)
+ uint32_t noexec : 1; //!< Filing system cannot execute programs (POSIX)
+ uint32_t nosuid : 1; //!< Filing system cannot superuser (POSIX)
+ uint32_t acls : 1; //!< Filing system provides ACLs (Windows, POSIX)
+ uint32_t xattr : 1; //!< Filing system provides extended attributes (Windows, POSIX)
+ uint32_t compression : 1; //!< Filing system provides whole volume compression (Windows, POSIX)
+ uint32_t extents : 1; //!< Filing system provides extent based file storage (sparse files) (Windows, POSIX)
+ uint32_t filecompression : 1; //!< Filing system provides per-file selectable compression (Windows)
+ } f_flags{0, 0, 0, 0, 0, 0, 0, 0}; /*!< copy of mount exported flags (Windows, POSIX) */
+ uint64_t f_bsize{_allbits1_64}; /*!< fundamental filesystem block size (Windows, POSIX) */
+ uint64_t f_iosize{_allbits1_64}; /*!< optimal transfer block size (Windows, POSIX) */
+ uint64_t f_blocks{_allbits1_64}; /*!< total data blocks in filesystem (Windows, POSIX) */
+ uint64_t f_bfree{_allbits1_64}; /*!< free blocks in filesystem (Windows, POSIX) */
+ uint64_t f_bavail{_allbits1_64}; /*!< free blocks avail to non-superuser (Windows, POSIX) */
+ uint64_t f_files{_allbits1_64}; /*!< total file nodes in filesystem (POSIX) */
+ uint64_t f_ffree{_allbits1_64}; /*!< free nodes avail to non-superuser (POSIX) */
+ uint32_t f_namemax{_allbits1_32}; /*!< maximum filename length (Windows, POSIX) */
+#ifndef _WIN32
+ int16_t f_owner{-1}; /*!< user that mounted the filesystem (BSD, OS X) */
+#endif
+ uint64_t f_fsid[2]{_allbits1_64, _allbits1_64}; /*!< filesystem id (Windows, POSIX) */
+ std::string f_fstypename; /*!< filesystem type name (Windows, POSIX) */
+ std::string f_mntfromname; /*!< mounted filesystem (Windows, POSIX) */
+ filesystem::path f_mntonname; /*!< directory on which mounted (Windows, POSIX) */
+
+ //! Used to indicate what metadata should be filled in
+ QUICKCPPLIB_BITFIELD_BEGIN(want) { flags = 1 << 0, bsize = 1 << 1, iosize = 1 << 2, blocks = 1 << 3, bfree = 1 << 4, bavail = 1 << 5, files = 1 << 6, ffree = 1 << 7, namemax = 1 << 8, owner = 1 << 9, fsid = 1 << 10, fstypename = 1 << 11, mntfromname = 1 << 12, mntonname = 1 << 13, all = static_cast<unsigned>(-1) }
+ QUICKCPPLIB_BITFIELD_END(want)
+ //! Constructs a default initialised instance (all bits set)
+ statfs_t() {} // NOLINT Cannot be constexpr due to lack of constexpe string default constructor :(
+#ifdef __cpp_exceptions
+ //! Constructs a filled instance, throwing as an exception any error which might occur
+ explicit statfs_t(const handle &h, want wanted = want::all)
+ : statfs_t()
+ {
+ auto v(fill(h, wanted));
+ if(v.has_error())
+ {
+ v.error().throw_exception();
+ }
+ }
+#endif
+ //! Fills in the structure with metadata, returning number of items filled in
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<size_t> fill(const handle &h, want wanted = want::all) noexcept;
+};
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/statfs.ipp"
+#else
+#include "detail/impl/posix/statfs.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/status_code.hpp b/include/llfio/v2.0/status_code.hpp
new file mode 100644
index 00000000..14b089f1
--- /dev/null
+++ b/include/llfio/v2.0/status_code.hpp
@@ -0,0 +1,560 @@
+/* Configures AFIO
+(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (24 commits)
+File Created: June 2018
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_STATUS_CODE_HPP
+#define AFIO_STATUS_CODE_HPP
+
+#include "logging.hpp"
+
+/* The SG14 status code implementation is quite profoundly different to the
+error code implementation. In the error code implementation, std::error_code
+is fixed by the standard library, so we wrap it with extra metadata into
+an error_info type. error_info then constructs off a code and a code domain
+tag.
+
+Status code, on the other hand, is templated and is designed for custom
+domains which can set arbitrary payloads. So we define custom domains and
+status codes for AFIO with these combinations:
+
+- win32_error{ DWORD }
+- ntkernel_error{ LONG }
+- posix_error{ int }
+- generic_error{ errc }
+
+Each of these is a separate AFIO custom status code domain. We also define
+an erased form of these custom domains, and that is typedefed to
+error_domain<intptr_t>::value_type.
+
+This design ensure that AFIO can be configured into either std-based error
+handling or SG14 experimental status code handling. It defaults to the latter
+as that (a) enables safe header only AFIO on Windows (b) produces better codegen
+(c) drags in far fewer STL headers.
+*/
+
+#if AFIO_EXPERIMENTAL_STATUS_CODE
+
+// Bring in a result implementation based on status_code
+#include "outcome/include/outcome/experimental/status_result.hpp"
+#include "outcome/include/outcome/try.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+
+namespace detail
+{
+ template <class T> struct error_domain_value_type
+ {
+ //! \brief The type of code
+ T sc{};
+
+ // The id of the thread where this failure occurred
+ uint32_t _thread_id{0};
+ // The TLS path store entry
+ uint16_t _tls_path_id1{static_cast<uint16_t>(-1)}, _tls_path_id2{static_cast<uint16_t>(-1)};
+ // The id of the relevant log entry in the AFIO log (if logging enabled)
+ size_t _log_id{static_cast<size_t>(-1)};
+
+ //! Default construction
+ error_domain_value_type() = default;
+
+ //! Implicitly constructs an instance
+ constexpr inline error_domain_value_type(T _sc)
+ : sc(_sc)
+ {
+ } // NOLINT
+
+ //! Compares to a T
+ constexpr bool operator==(const T &b) const noexcept { return sc == b; }
+ };
+}
+
+template <class BaseStatusCodeDomain> class error_domain;
+
+AFIO_V2_NAMESPACE_END
+
+// Inject a mixin for our custom status codes
+SYSTEM_ERROR2_NAMESPACE_BEGIN
+namespace mixins
+{
+ template <class Base, class BaseStatusCodeDomain> struct mixin<Base, ::AFIO_V2_NAMESPACE::error_domain<BaseStatusCodeDomain>> : public Base
+ {
+ using Base::Base;
+
+ //! Retrieve the paths associated with this failure
+ std::pair<const char *, const char *> _paths() const noexcept
+ {
+ if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == this->value()._thread_id)
+ {
+ auto &tls = ::AFIO_V2_NAMESPACE::detail::tls_errored_results();
+ const char *path1 = tls.get(this->value()._tls_path_id1);
+ const char *path2 = tls.get(this->value()._tls_path_id2);
+ return {path1, path2};
+ }
+ return {};
+ }
+ //! Retrieve the first path associated with this failure
+ ::AFIO_V2_NAMESPACE::filesystem::path path1() const
+ {
+ if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == this->value()._thread_id)
+ {
+ auto &tls = ::AFIO_V2_NAMESPACE::detail::tls_errored_results();
+ const char *path1 = tls.get(this->value()._tls_path_id1);
+ if(path1 != nullptr)
+ {
+ return ::AFIO_V2_NAMESPACE::filesystem::path(path1);
+ }
+ }
+ return {};
+ }
+ //! Retrieve the second path associated with this failure
+ ::AFIO_V2_NAMESPACE::filesystem::path path2() const
+ {
+ if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == this->value()._thread_id)
+ {
+ auto &tls = ::AFIO_V2_NAMESPACE::detail::tls_errored_results();
+ const char *path2 = tls.get(this->value()._tls_path_id2);
+ if(path2 != nullptr)
+ {
+ return ::AFIO_V2_NAMESPACE::filesystem::path(path2);
+ }
+ }
+ return {};
+ }
+ };
+}
+SYSTEM_ERROR2_NAMESPACE_END
+
+AFIO_V2_NAMESPACE_BEGIN
+
+/*! \class error_domain
+\brief The SG14 status code domain for errors in AFIO.
+*/
+template <class BaseStatusCodeDomain> class error_domain : public BaseStatusCodeDomain
+{
+ friend class SYSTEM_ERROR2_NAMESPACE::status_code<error_domain>;
+ using _base = BaseStatusCodeDomain;
+
+public:
+ using string_ref = typename BaseStatusCodeDomain::string_ref;
+ using atomic_refcounted_string_ref = typename BaseStatusCodeDomain::atomic_refcounted_string_ref;
+
+ //! \brief The value type of errors in AFIO
+ using value_type = detail::error_domain_value_type<typename _base::value_type>;
+
+ error_domain() = default;
+ error_domain(const error_domain &) = default;
+ error_domain(error_domain &&) = default;
+ error_domain &operator=(const error_domain &) = default;
+ error_domain &operator=(error_domain &&) = default;
+ ~error_domain() = default;
+
+protected:
+ virtual inline string_ref _do_message(const SYSTEM_ERROR2_NAMESPACE::status_code<void> &code) const noexcept override final
+ {
+ assert(code.domain() == *this);
+ const auto &v = static_cast<const SYSTEM_ERROR2_NAMESPACE::status_code<error_domain> &>(code); // NOLINT
+ // Get the paths for this failure, if any, using the mixins from above
+ auto paths = v._paths();
+ // Get the base message for this failure
+ auto msg = _base::_do_message(code);
+ if(paths.first == nullptr && paths.second == nullptr)
+ {
+ return msg;
+ }
+ std::string ret;
+ try
+ {
+ ret = msg.c_str();
+ if(paths.first != nullptr)
+ {
+ ret.append(" [path1 = ");
+ ret.append(paths.first);
+ if(paths.second != nullptr)
+ {
+ ret.append(", path2 = ");
+ ret.append(paths.second);
+ }
+ ret.append("]");
+ }
+#if AFIO_LOGGING_LEVEL >= 2
+ if(v.value()._log_id != static_cast<uint32_t>(-1))
+ {
+ if(log().valid(v.value()._log_id))
+ {
+ ret.append(" [location = ");
+ ret.append(location(log()[v.value()._log_id]));
+ ret.append("]");
+ }
+ }
+#endif
+ }
+ catch(...)
+ {
+ return string_ref("Failed to retrieve message for status code");
+ }
+ char *p = (char *) malloc(ret.size() + 1);
+ if(p == nullptr)
+ {
+ return string_ref("Failed to allocate memory to store error string");
+ }
+ memcpy(p, ret.c_str(), ret.size() + 1);
+ return atomic_refcounted_string_ref(p, ret.size());
+ }
+};
+
+#else // AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+template <class BaseStatusCodeDomain> using error_domain = BaseStatusCodeDomain;
+#endif // AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+
+namespace detail
+{
+ using error_domain_value_system_code = error_domain_value_type<SYSTEM_ERROR2_NAMESPACE::system_code::value_type>;
+}
+
+//! An erased status code
+using error_code = SYSTEM_ERROR2_NAMESPACE::errored_status_code<SYSTEM_ERROR2_NAMESPACE::erased<detail::error_domain_value_system_code>>;
+
+
+template <class T> using result = OUTCOME_V2_NAMESPACE::experimental::erased_result<T, error_code>;
+using OUTCOME_V2_NAMESPACE::success;
+using OUTCOME_V2_NAMESPACE::failure;
+using OUTCOME_V2_NAMESPACE::in_place_type;
+
+//! Choose an errc implementation
+using SYSTEM_ERROR2_NAMESPACE::errc;
+
+//! Helper for constructing an error code from an errc
+inline error_code generic_error(errc c);
+#ifndef _WIN32
+//! Helper for constructing an error code from a POSIX errno
+inline error_code posix_error(int c = errno);
+#else
+//! Helper for constructing an error code from a DWORD
+inline error_code win32_error(SYSTEM_ERROR2_NAMESPACE::win32::DWORD c = SYSTEM_ERROR2_NAMESPACE::win32::GetLastError());
+//! Helper for constructing an error code from a NTSTATUS
+inline error_code ntkernel_error(SYSTEM_ERROR2_NAMESPACE::win32::NTSTATUS c);
+#endif
+
+namespace detail
+{
+ inline std::ostream &operator<<(std::ostream &s, const error_code &v) { return s << "afio::error_code(" << v.message().c_str() << ")"; }
+}
+inline error_code error_from_exception(std::exception_ptr &&ep = std::current_exception(), error_code not_matched = generic_error(errc::resource_unavailable_try_again)) noexcept
+{
+ if(!ep)
+ {
+ return generic_error(errc::success);
+ }
+ try
+ {
+ std::rethrow_exception(ep);
+ }
+ catch(const std::invalid_argument & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::invalid_argument);
+ }
+ catch(const std::domain_error & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::argument_out_of_domain);
+ }
+ catch(const std::length_error & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::argument_list_too_long);
+ }
+ catch(const std::out_of_range & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::result_out_of_range);
+ }
+ catch(const std::logic_error & /*unused*/) /* base class for this group */
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::invalid_argument);
+ }
+ catch(const std::system_error &e) /* also catches ios::failure */
+ {
+ ep = std::exception_ptr();
+ if(e.code().category() == std::generic_category())
+ {
+ return generic_error(static_cast<errc>(static_cast<int>(e.code().value())));
+ }
+ // Don't know this error code category, so fall through
+ }
+ catch(const std::overflow_error & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::value_too_large);
+ }
+ catch(const std::range_error & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::result_out_of_range);
+ }
+ catch(const std::runtime_error & /*unused*/) /* base class for this group */
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::resource_unavailable_try_again);
+ }
+ catch(const std::bad_alloc & /*unused*/)
+ {
+ ep = std::exception_ptr();
+ return generic_error(errc::not_enough_memory);
+ }
+ catch(...)
+ {
+ }
+ return not_matched;
+}
+
+AFIO_V2_NAMESPACE_END
+
+
+#else // AFIO_EXPERIMENTAL_STATUS_CODE
+
+
+// Bring in a result implementation based on std::error_code
+#include "outcome/include/outcome.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+
+namespace detail
+{
+ template <class Dest, class Src> inline void fill_failure_info(Dest &dest, const Src &src);
+ template <class Src> inline void append_path_info(Src &src, std::string &ret);
+}
+
+struct error_info;
+inline std::error_code make_error_code(error_info ei);
+
+/*! \struct error_info
+\brief The cause of the failure of an operation in AFIO.
+*/
+struct error_info
+{
+ friend inline std::error_code make_error_code(error_info ei);
+ template <class Src> friend inline void detail::append_path_info(Src &src, std::string &ret);
+ template <class Dest, class Src> friend inline void detail::fill_failure_info(Dest &dest, const Src &src);
+
+private:
+ // The error code for the failure
+ std::error_code ec;
+
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+ // The id of the thread where this failure occurred
+ uint32_t _thread_id{0};
+ // The TLS path store entry
+ uint16_t _tls_path_id1{static_cast<uint16_t>(-1)}, _tls_path_id2{static_cast<uint16_t>(-1)};
+ // The id of the relevant log entry in the AFIO log (if logging enabled)
+ size_t _log_id{static_cast<size_t>(-1)};
+
+public:
+#endif
+
+ //! Default constructor
+ error_info() = default;
+ // Explicit construction from an error code
+ explicit inline error_info(std::error_code _ec); // NOLINT
+ /* NOTE TO SELF: The error_info constructor implementation is in handle.hpp as we need that
+ defined before we can do useful logging.
+ */
+ //! Implicit construct from an error condition enum
+ OUTCOME_TEMPLATE(class ErrorCondEnum)
+ OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value))
+ error_info(ErrorCondEnum &&v) // NOLINT
+ : error_info(make_error_code(std::forward<ErrorCondEnum>(v)))
+ {
+ }
+
+ //! Retrieve the value of the error code
+ int value() const noexcept { return ec.value(); }
+ //! Retrieve any first path associated with this failure. Note this only works if called from the same thread as where the failure occurred.
+ inline filesystem::path path1() const
+ {
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+ if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == _thread_id)
+ {
+ auto &tls = detail::tls_errored_results();
+ const char *path1 = tls.get(_tls_path_id1);
+ if(path1 != nullptr)
+ {
+ return filesystem::path(path1);
+ }
+ }
+#endif
+ return {};
+ }
+ //! Retrieve any second path associated with this failure. Note this only works if called from the same thread as where the failure occurred.
+ inline filesystem::path path2() const
+ {
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+ if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == _thread_id)
+ {
+ auto &tls = detail::tls_errored_results();
+ const char *path2 = tls.get(_tls_path_id2);
+ if(path2 != nullptr)
+ {
+ return filesystem::path(path2);
+ }
+ }
+#endif
+ return {};
+ }
+ //! Retrieve a descriptive message for this failure, possibly with paths and stack backtraces. Extra detail only appears if called from the same thread as where the failure occurred.
+ inline std::string message() const
+ {
+ std::string ret(ec.message());
+#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO
+ detail::append_path_info(*this, ret);
+#endif
+ return ret;
+ }
+ /*! Throw this failure as a C++ exception. Firstly if the error code matches any of the standard
+ C++ exception types e.g. `bad_alloc`, we throw those types using the string from `message()`
+ where possible. We then will throw an `error` exception type.
+ */
+ inline void throw_exception() const;
+};
+inline bool operator==(const error_info &a, const error_info &b)
+{
+ return make_error_code(a) == make_error_code(b);
+}
+inline bool operator!=(const error_info &a, const error_info &b)
+{
+ return make_error_code(a) != make_error_code(b);
+}
+OUTCOME_TEMPLATE(class ErrorCondEnum)
+OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value))
+inline bool operator==(const error_info &a, const ErrorCondEnum &b)
+{
+ return make_error_code(a) == std::error_condition(b);
+}
+OUTCOME_TEMPLATE(class ErrorCondEnum)
+OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value))
+inline bool operator==(const ErrorCondEnum &a, const error_info &b)
+{
+ return std::error_condition(a) == make_error_code(b);
+}
+OUTCOME_TEMPLATE(class ErrorCondEnum)
+OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value))
+inline bool operator!=(const error_info &a, const ErrorCondEnum &b)
+{
+ return make_error_code(a) != std::error_condition(b);
+}
+OUTCOME_TEMPLATE(class ErrorCondEnum)
+OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value))
+inline bool operator!=(const ErrorCondEnum &a, const error_info &b)
+{
+ return std::error_condition(a) != make_error_code(b);
+}
+#ifndef NDEBUG
+// Is trivial in all ways, except default constructibility
+static_assert(std::is_trivially_copyable<error_info>::value, "error_info is not a trivially copyable!");
+#endif
+inline std::ostream &operator<<(std::ostream &s, const error_info &v)
+{
+ if(make_error_code(v))
+ {
+ return s << "afio::error_info(" << v.message() << ")";
+ }
+ return s << "afio::error_info(null)";
+}
+// Tell Outcome that error_info is to be treated as an error_code
+inline std::error_code make_error_code(error_info ei)
+{
+ return ei.ec;
+}
+// Tell Outcome to call error_info::throw_exception() on no-value observation
+inline void outcome_throw_as_system_error_with_payload(const error_info &ei)
+{
+ ei.throw_exception();
+}
+
+/*! \class error
+\brief The exception type synthesised and thrown when an `afio::result` or `afio::outcome` is no-value observed.
+*/
+class error : public filesystem::filesystem_error
+{
+public:
+ error_info ei;
+
+ //! Constructs from an error_info
+ explicit error(error_info _ei)
+ : filesystem::filesystem_error(_ei.message(), _ei.path1(), _ei.path2(), make_error_code(_ei))
+ , ei(_ei)
+ {
+ }
+};
+
+inline void error_info::throw_exception() const
+{
+ std::string msg;
+ try
+ {
+ msg = message();
+ }
+ catch(...)
+ {
+ }
+ OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(ec, msg);
+ throw error(*this);
+}
+
+template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, error_info>;
+template <class T> using outcome = OUTCOME_V2_NAMESPACE::outcome<T, error_info>;
+using OUTCOME_V2_NAMESPACE::success;
+using OUTCOME_V2_NAMESPACE::failure;
+inline error_info error_from_exception(std::exception_ptr &&ep = std::current_exception(), std::error_code not_matched = std::make_error_code(std::errc::resource_unavailable_try_again)) noexcept
+{
+ return error_info(OUTCOME_V2_NAMESPACE::error_from_exception(std::move(ep), not_matched));
+}
+using OUTCOME_V2_NAMESPACE::in_place_type;
+
+static_assert(OUTCOME_V2_NAMESPACE::trait::has_error_code_v<error_info>, "error_info is not detected to be an error code");
+
+//! Choose an errc implementation
+using std::errc;
+
+//! Helper for constructing an error info from an errc
+inline error_info generic_error(errc c)
+{
+ return error_info(make_error_code(c));
+}
+#ifndef _WIN32
+//! Helper for constructing an error info from a POSIX errno
+inline error_info posix_error(int c = errno)
+{
+ return error_info(std::error_code(c, std::system_category()));
+}
+#endif
+
+AFIO_V2_NAMESPACE_END
+
+#endif // AFIO_EXPERIMENTAL_STATUS_CODE
+
+
+#endif
diff --git a/include/llfio/v2.0/storage_profile.hpp b/include/llfio/v2.0/storage_profile.hpp
new file mode 100644
index 00000000..36d50749
--- /dev/null
+++ b/include/llfio/v2.0/storage_profile.hpp
@@ -0,0 +1,410 @@
+/* A profile of an OS and filing system
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (7 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_STORAGE_PROFILE_H
+#define AFIO_STORAGE_PROFILE_H
+
+#include "io_service.hpp"
+
+#if AFIO_EXPERIMENTAL_STATUS_CODE
+#include "outcome/include/outcome/experimental/status_outcome.hpp"
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+template <class T> using outcome = OUTCOME_V2_NAMESPACE::experimental::erased_outcome<T, error_code>;
+AFIO_V2_NAMESPACE_END
+#endif
+
+#include <regex>
+#include <utility>
+//! \file storage_profile.hpp Provides storage_profile
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4251) // dll interface
+#endif
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace storage_profile
+{
+ //! Types potentially storable in a storage profile
+ enum class storage_types
+ {
+ unknown,
+ extent_type,
+ unsigned_int,
+ unsigned_long_long,
+ float_,
+ string
+ };
+ struct storage_profile;
+
+ //! Returns the enum matching type T
+ template <class T> constexpr storage_types map_to_storage_type()
+ {
+ static_assert(0 == sizeof(T), "Unsupported storage_type");
+ return storage_types::unknown;
+ }
+ //! Specialise for a different default value for T
+ template <class T> constexpr T default_value() { return T{}; }
+
+ template <> constexpr storage_types map_to_storage_type<io_service::extent_type>() { return storage_types::extent_type; }
+ template <> constexpr io_service::extent_type default_value<io_service::extent_type>() { return static_cast<io_service::extent_type>(-1); }
+ template <> constexpr storage_types map_to_storage_type<unsigned int>() { return storage_types::unsigned_int; }
+ template <> constexpr unsigned int default_value<unsigned int>() { return static_cast<unsigned int>(-1); }
+ // template<> constexpr storage_types map_to_storage_type<unsigned long long>() { return storage_types::unsigned_long_long; }
+ template <> constexpr storage_types map_to_storage_type<float>() { return storage_types::float_; }
+ template <> constexpr storage_types map_to_storage_type<std::string>() { return storage_types::string; }
+
+ //! Common base class for items
+ struct item_base
+ {
+ static constexpr size_t item_size = 128;
+ //! The type of handle used for testing
+ using handle_type = file_handle;
+
+ const char *name; //!< The name of the item in colon delimited category format
+ const char *description; //!< Some description of the item
+ storage_types type; //!< The type of the value
+ protected:
+ constexpr item_base(const char *_name, const char *_desc, storage_types _type)
+ : name(_name)
+ , description(_desc)
+ , type(_type)
+ {
+ }
+ };
+ //! A tag-value item in the storage profile where T is the type of value stored.
+ template <class T> struct item : public item_base
+ {
+ static constexpr size_t item_size = item_base::item_size;
+ using handle_type = item_base::handle_type;
+ using callable = outcome<void> (*)(storage_profile &sp, handle_type &h);
+
+ callable impl;
+ T value; //!< The storage of the item
+ char _padding[item_size - sizeof(item_base) - sizeof(callable) - sizeof(T)];
+ constexpr item(const char *_name, callable c, const char *_desc = nullptr, T _value = default_value<T>())
+ : item_base(_name, _desc, map_to_storage_type<T>())
+ , impl(c)
+ , value(std::move(_value))
+ , _padding{0}
+ {
+ static_assert(sizeof(*this) == item_size, "");
+ }
+ //! Clear this item, returning value to default
+ void clear() { value = default_value<T>(); }
+ //! Set this item if its value is default
+ outcome<void> operator()(storage_profile &sp, handle_type &h) const
+ {
+ if(value != default_value<T>())
+ {
+ return success();
+ }
+ return impl(sp, h);
+ }
+ };
+ //! A type erased tag-value item
+ struct item_erased : public item_base
+ {
+ static constexpr size_t item_size = item_base::item_size;
+ using handle_type = item_base::handle_type;
+ char _padding[item_size - sizeof(item_base)];
+
+ item_erased() = delete;
+ ~item_erased() = delete;
+ item_erased(const item_erased &) = delete;
+ item_erased(item_erased &&) = delete;
+ item_erased &operator=(const item_erased &) = delete;
+ item_erased &operator=(item_erased &&) = delete;
+ //! Call the callable with the unerased type
+ template <class U> auto invoke(U &&f) const
+ {
+ switch(type)
+ {
+ case storage_types::extent_type:
+ return f(*reinterpret_cast<const item<io_service::extent_type> *>(static_cast<const item_base *>(this)));
+ case storage_types::unsigned_int:
+ return f(*reinterpret_cast<const item<unsigned int> *>(static_cast<const item_base *>(this)));
+ case storage_types::unsigned_long_long:
+ return f(*reinterpret_cast<const item<unsigned long long> *>(static_cast<const item_base *>(this)));
+ case storage_types::float_:
+ return f(*reinterpret_cast<const item<float> *>(static_cast<const item_base *>(this)));
+ case storage_types::string:
+ return f(*reinterpret_cast<const item<std::string> *>(static_cast<const item_base *>(this)));
+ case storage_types::unknown:
+ break;
+ }
+ throw std::invalid_argument("No type set in item"); // NOLINT
+ }
+ //! Set this item if its value is default
+ outcome<void> operator()(storage_profile &sp, handle_type &h) const
+ {
+ return invoke([&sp, &h](auto &item) { return item(sp, h); });
+ }
+ };
+
+ namespace system
+ {
+ // OS name, version
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> os(storage_profile &sp, file_handle &h) noexcept;
+ // CPU name, architecture, physical cores
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> cpu(storage_profile &sp, file_handle &h) noexcept;
+ // System memory quantity, in use, max and min bandwidth
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> mem(storage_profile &sp, file_handle &h) noexcept;
+#ifdef _WIN32
+ namespace windows
+ {
+#else
+ namespace posix
+ {
+#endif
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> _mem(storage_profile &sp, file_handle &h) noexcept;
+ }
+ // High resolution clock granularity
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> clock_granularity(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> yield_overhead(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> sleep_wake_overhead(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> drop_filesystem_cache_support(storage_profile &sp, file_handle & /*unused*/) noexcept;
+ } // namespace system
+ namespace storage
+ {
+ // Device name, size, min i/o size
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> device(storage_profile &sp, file_handle &h) noexcept;
+ // FS name, config, size, in use
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> fs(storage_profile &sp, file_handle &h) noexcept;
+#ifdef _WIN32
+ namespace windows
+ {
+#else
+ namespace posix
+ {
+#endif
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> _device(storage_profile &sp, file_handle &h, const std::string &_mntfromname, const std::string &fstypename) noexcept;
+ }
+ } // namespace storage
+ namespace concurrency
+ {
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> atomic_rewrite_quantum(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> atomic_rewrite_offset_boundary(storage_profile &sp, file_handle &srch) noexcept;
+ }
+ namespace latency
+ {
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> read_nothing(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> write_nothing(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> read_qd1(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> write_qd1(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> read_qd16(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> write_qd16(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> readwrite_qd4(storage_profile &sp, file_handle &srch) noexcept;
+ }
+ namespace response_time
+ {
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_racefree_0b(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_racefree_1b(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_racefree_4k(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_nonracefree_0b(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_nonracefree_1b(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_nonracefree_4k(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_warm_nonracefree_1M(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_cold_racefree_0b(storage_profile &sp, file_handle &srch) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_cold_racefree_1b(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_cold_racefree_4k(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_cold_nonracefree_0b(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_cold_nonracefree_1b(storage_profile &sp, file_handle &h) noexcept;
+ AFIO_HEADERS_ONLY_FUNC_SPEC outcome<void> traversal_cold_nonracefree_4k(storage_profile &sp, file_handle &h) noexcept;
+ } // namespace response_time
+
+ //! A (possibly incomplet) profile of storage
+ struct AFIO_DECL storage_profile
+ {
+ //! The size type
+ using size_type = size_t;
+
+ private:
+ size_type _size{0};
+
+ public:
+ storage_profile() = default;
+
+ //! Value type
+ using value_type = item_erased &;
+ //! Reference type
+ using reference = item_erased &;
+ //! Const reference type
+ using const_reference = const item_erased &;
+ //! Iterator type
+ using iterator = item_erased *;
+ //! Const iterator type
+ using const_iterator = const item_erased *;
+ //! The type of handle used for testing
+ using handle_type = item_base::handle_type;
+
+ //! True if this storage profile is empty
+ bool empty() const noexcept { return _size == 0; }
+ //! Items in this storage profile
+ size_type size() const noexcept { return _size; }
+ //! Potential items in this storage profile
+ size_type max_size() const noexcept { return (sizeof(*this) - sizeof(_size)) / item_base::item_size; }
+ //! Returns an iterator to the first item
+ iterator begin() noexcept { return reinterpret_cast<item_erased *>(static_cast<item_base *>(&os_name)); }
+ //! Returns an iterator to the last item
+ iterator end() noexcept { return begin() + max_size(); }
+ //! Returns an iterator to the first item
+ const_iterator begin() const noexcept { return reinterpret_cast<const item_erased *>(static_cast<const item_base *>(&os_name)); }
+ //! Returns an iterator to the last item
+ const_iterator end() const noexcept { return begin() + max_size(); }
+
+ //! Read the matching items in the storage profile from in as YAML
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC void read(std::istream &in, std::regex which = std::regex(".*"));
+ //! Write the matching items from storage profile as YAML to out with the given indentation
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC void write(std::ostream &out, const std::regex &which = std::regex(".*"), size_t _indent = 0, bool invert_match = false) const;
+
+ // System characteristics
+ item<std::string> os_name = {"system:os:name", &system::os}; // e.g. Microsoft Windows NT
+ item<std::string> os_ver = {"system:os:ver", &system::os}; // e.g. 10.0.10240
+ item<std::string> cpu_name = {"system:cpu:name", &system::cpu}; // e.g. Intel Haswell
+ item<std::string> cpu_architecture = {"system:cpu:architecture", &system::cpu}; // e.g. x64
+ item<unsigned> cpu_physical_cores = {"system:cpu:physical_cores", &system::cpu};
+ item<unsigned long long> mem_max_bandwidth = {"system:mem:max_bandwidth", system::mem, "Main memory bandwidth when accessed sequentially (1 CPU core)"};
+ item<unsigned long long> mem_min_bandwidth = {"system:mem:min_bandwidth", system::mem, "Main memory bandwidth when 4Kb pages are accessed randomly (1 CPU core)"};
+ item<unsigned long long> mem_quantity = {"system:mem:quantity", &system::mem};
+ item<float> mem_in_use = {"system:mem:in_use", &system::mem}; // not including caches etc.
+ item<unsigned> clock_granularity = {"system:timer:ns_per_tick", &system::clock_granularity};
+ item<unsigned> clock_overhead = {"system:timer:ns_overhead", &system::clock_granularity};
+ item<unsigned> yield_overhead = {"system:scheduler:ns_yield", &system::yield_overhead, "Nanoseconds to context switch a thread"};
+ item<unsigned> sleep_wake_overhead = {"system:scheduler:ns_sleep_wake", &system::sleep_wake_overhead, "Nanoseconds to sleep and wake a thread"};
+ item<unsigned> drop_filesystem_cache_support = {"system:drop_filesystem_cache_support", &system::drop_filesystem_cache_support};
+
+ // Controller characteristics
+ item<std::string> controller_type = {"storage:controller:kind", &storage::device}; // e.g. SATA
+ item<unsigned> controller_max_transfer = {"storage:controller:max_transfer", storage::device, "The maximum number of bytes the disk controller can transfer at once"};
+ item<unsigned> controller_max_buffers = {"storage:controller:max_buffers", storage::device, "The maximum number of scatter-gather buffers the disk controller can handle"};
+
+ // Storage characteristics
+ item<std::string> device_name = {"storage:device:name", &storage::device}; // e.g. WDC WD30EFRX-68EUZN0
+ item<unsigned> device_min_io_size = {"storage:device:min_io_size", &storage::device}; // e.g. 4096
+ item<io_service::extent_type> device_size = {"storage:device:size", &storage::device};
+
+ // Filing system characteristics
+ item<std::string> fs_name = {"storage:fs:name", &storage::fs};
+ item<std::string> fs_config = {"storage:fs:config", &storage::fs}; // POSIX mount options, ZFS pool properties etc
+ // item<std::string> fs_ffeatures = { "storage:fs:features" }; // Standardised features???
+ item<io_service::extent_type> fs_size = {"storage:fs:size", &storage::fs};
+ item<float> fs_in_use = {"storage:fs:in_use", &storage::fs};
+
+ // Test results on this filing system, storage and system
+ item<io_service::extent_type> atomic_rewrite_quantum = {"concurrency:atomic_rewrite_quantum", concurrency::atomic_rewrite_quantum, "The i/o modify quantum guaranteed to be atomically visible to readers irrespective of rewrite quantity"};
+ item<io_service::extent_type> max_aligned_atomic_rewrite = {"concurrency:max_aligned_atomic_rewrite", concurrency::atomic_rewrite_quantum,
+ "The maximum single aligned i/o modify quantity atomically visible to readers (can be [potentially unreliably] much larger than atomic_rewrite_quantum). "
+ "A very common value on modern hardware with direct i/o thanks to PCIe DMA is 4096, don't trust values higher than this because of potentially discontiguous memory page mapping."};
+ item<io_service::extent_type> atomic_rewrite_offset_boundary = {"concurrency:atomic_rewrite_offset_boundary", concurrency::atomic_rewrite_offset_boundary, "The multiple of offset in a file where update atomicity breaks, so if you wrote 4096 bytes at a 512 offset and "
+ "this value was 4096, your write would tear at 3584 because all writes would tear on a 4096 offset multiple. "
+ "Linux has a famously broken kernel i/o design which causes this value to be a page multiple, except on "
+ "filing systems which take special measures to work around it. Windows NT appears to lose all atomicity as soon as "
+ "an i/o straddles a 4096 file offset multiple and DMA suddenly goes into many 64 byte cache lines :(, so if "
+ "this value is less than max_aligned_atomic_rewrite and some multiple of the CPU cache line size then this is "
+ "what has happened."};
+ item<unsigned> read_nothing = {"latency:read:nothing", latency::read_nothing, "The nanoseconds to read zero bytes"};
+
+ item<unsigned long long> read_qd1_min = {"latency:read:qd1:min", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (min)"};
+ item<unsigned long long> read_qd1_mean = {"latency:read:qd1:mean", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (arithmetic mean)"};
+ item<unsigned long long> read_qd1_max = {"latency:read:qd1:max", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (max)"};
+ item<unsigned long long> read_qd1_50 = {"latency:read:qd1:50%", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (50% of the time)"};
+ item<unsigned long long> read_qd1_95 = {"latency:read:qd1:95%", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (95% of the time)"};
+ item<unsigned long long> read_qd1_99 = {"latency:read:qd1:99%", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (99% of the time)"};
+ item<unsigned long long> read_qd1_99999 = {"latency:read:qd1:99.999%", latency::read_qd1, "The nanoseconds to read 4Kb at a queue depth of 1 (99.999% of the time)"};
+
+ item<unsigned long long> read_qd16_min = {"latency:read:qd16:min", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (min)"};
+ item<unsigned long long> read_qd16_mean = {"latency:read:qd16:mean", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (arithmetic mean)"};
+ item<unsigned long long> read_qd16_max = {"latency:read:qd16:max", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (max)"};
+ item<unsigned long long> read_qd16_50 = {"latency:read:qd16:50%", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (50% of the time)"};
+ item<unsigned long long> read_qd16_95 = {"latency:read:qd16:95%", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (95% of the time)"};
+ item<unsigned long long> read_qd16_99 = {"latency:read:qd16:99%", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (99% of the time)"};
+ item<unsigned long long> read_qd16_99999 = {"latency:read:qd16:99.999%", latency::read_qd16, "The nanoseconds to read 4Kb at a queue depth of 16 (99.999% of the time)"};
+
+ item<unsigned> write_nothing = {"latency:write:nothing", latency::write_nothing, "The nanoseconds to write zero bytes"};
+
+ item<unsigned long long> write_qd1_min = {"latency:write:qd1:min", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (min)"};
+ item<unsigned long long> write_qd1_mean = {"latency:write:qd1:mean", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (arithmetic mean)"};
+ item<unsigned long long> write_qd1_max = {"latency:write:qd1:max", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (max)"};
+ item<unsigned long long> write_qd1_50 = {"latency:write:qd1:50%", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (50% of the time)"};
+ item<unsigned long long> write_qd1_95 = {"latency:write:qd1:95%", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (95% of the time)"};
+ item<unsigned long long> write_qd1_99 = {"latency:write:qd1:99%", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (99% of the time)"};
+ item<unsigned long long> write_qd1_99999 = {"latency:write:qd1:99.999%", latency::write_qd1, "The nanoseconds to write 4Kb at a queue depth of 1 (99.999% of the time)"};
+
+ item<unsigned long long> write_qd16_min = {"latency:write:qd16:min", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (min)"};
+ item<unsigned long long> write_qd16_mean = {"latency:write:qd16:mean", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (arithmetic mean)"};
+ item<unsigned long long> write_qd16_max = {"latency:write:qd16:max", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (max)"};
+ item<unsigned long long> write_qd16_50 = {"latency:write:qd16:50%", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (50% of the time)"};
+ item<unsigned long long> write_qd16_95 = {"latency:write:qd16:95%", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (95% of the time)"};
+ item<unsigned long long> write_qd16_99 = {"latency:write:qd16:99%", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (99% of the time)"};
+ item<unsigned long long> write_qd16_99999 = {"latency:write:qd16:99.999%", latency::write_qd16, "The nanoseconds to write 4Kb at a queue depth of 16 (99.999% of the time)"};
+
+ item<unsigned long long> readwrite_qd4_min = {"latency:readwrite:qd4:min", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (min)"};
+ item<unsigned long long> readwrite_qd4_mean = {"latency:readwrite:qd4:mean", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (arithmetic mean)"};
+ item<unsigned long long> readwrite_qd4_max = {"latency:readwrite:qd4:max", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (max)"};
+ item<unsigned long long> readwrite_qd4_50 = {"latency:readwrite:qd4:50%", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (50% of the time)"};
+ item<unsigned long long> readwrite_qd4_95 = {"latency:readwrite:qd4:95%", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (95% of the time)"};
+ item<unsigned long long> readwrite_qd4_99 = {"latency:readwrite:qd4:99%", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (99% of the time)"};
+ item<unsigned long long> readwrite_qd4_99999 = {"latency:readwrite:qd4:99.999%", latency::readwrite_qd4, "The nanoseconds to 75% read 25% write 4Kb at a total queue depth of 4 (99.999% of the time)"};
+
+ item<unsigned long long> create_file_warm_racefree_0b = {"response_time:race_free:warm_cache:create_file:0b", response_time::traversal_warm_racefree_0b, "The average nanoseconds to create a 0 byte file (warm cache, race free)"};
+ item<unsigned long long> enumerate_file_warm_racefree_0b = {"response_time:race_free:warm_cache:enumerate_file:0b", response_time::traversal_warm_racefree_0b, "The average nanoseconds to enumerate a 0 byte file (warm cache, race free)"};
+ item<unsigned long long> open_file_read_warm_racefree_0b = {"response_time:race_free:warm_cache:open_file_read:0b", response_time::traversal_warm_racefree_0b, "The average nanoseconds to open a 0 byte file for reading (warm cache, race free)"};
+ item<unsigned long long> open_file_write_warm_racefree_0b = {"response_time:race_free:warm_cache:open_file_write:0b", response_time::traversal_warm_racefree_0b, "The average nanoseconds to open a 0 byte file for writing (warm cache, race free)"};
+ item<unsigned long long> delete_file_warm_racefree_0b = {"response_time:race_free:warm_cache:delete_file:0b", response_time::traversal_warm_racefree_0b, "The average nanoseconds to delete a 0 byte file (warm cache, race free)"};
+
+ item<unsigned long long> create_file_warm_nonracefree_0b = {"response_time:non_race_free:warm_cache:create_file:0b", response_time::traversal_warm_nonracefree_0b, "The average nanoseconds to create a 0 byte file (warm cache, non race free)"};
+ item<unsigned long long> enumerate_file_warm_nonracefree_0b = {"response_time:non_race_free:warm_cache:enumerate_file:0b", response_time::traversal_warm_nonracefree_0b, "The average nanoseconds to enumerate a 0 byte file (warm cache, non race free)"};
+ item<unsigned long long> open_file_read_warm_nonracefree_0b = {"response_time:non_race_free:warm_cache:open_file_read:0b", response_time::traversal_warm_nonracefree_0b, "The average nanoseconds to open a 0 byte file for reading (warm cache, non race free)"};
+ item<unsigned long long> open_file_write_warm_nonracefree_0b = {"response_time:non_race_free:warm_cache:open_file_write:0b", response_time::traversal_warm_nonracefree_0b, "The average nanoseconds to open a 0 byte file for writing (warm cache, non race free)"};
+ item<unsigned long long> delete_file_warm_nonracefree_0b = {"response_time:non_race_free:warm_cache:delete_file:0b", response_time::traversal_warm_nonracefree_0b, "The average nanoseconds to delete a 0 byte file (warm cache, non race free)"};
+
+ item<unsigned long long> create_file_cold_racefree_0b = {"response_time:race_free:cold_cache:create_file:0b", response_time::traversal_cold_racefree_0b, "The average nanoseconds to create a 0 byte file (cold cache, race free)"};
+ item<unsigned long long> enumerate_file_cold_racefree_0b = {"response_time:race_free:cold_cache:enumerate_file:0b", response_time::traversal_cold_racefree_0b, "The average nanoseconds to enumerate a 0 byte file (cold cache, race free)"};
+ item<unsigned long long> open_file_read_cold_racefree_0b = {"response_time:race_free:cold_cache:open_file_read:0b", response_time::traversal_cold_racefree_0b, "The average nanoseconds to open a 0 byte file for reading (cold cache, race free)"};
+ item<unsigned long long> open_file_write_cold_racefree_0b = {"response_time:race_free:cold_cache:open_file_write:0b", response_time::traversal_cold_racefree_0b, "The average nanoseconds to open a 0 byte file for writing (cold cache, race free)"};
+ item<unsigned long long> delete_file_cold_racefree_0b = {"response_time:race_free:cold_cache:delete_file:0b", response_time::traversal_cold_racefree_0b, "The average nanoseconds to delete a 0 byte file (cold cache, race free)"};
+
+ /*
+ item<unsigned> create_1M_files = {"response_time:create_1M_files_single_dir", response_time::traversal_warm_nonracefree_1M, "The milliseconds to create 1M empty files in a single directory"};
+ item<unsigned> enumerate_1M_files = {"response_time:enumerate_1M_files_single_dir", response_time::traversal_warm_nonracefree_1M, "The milliseconds to enumerate 1M empty files in a single directory"};
+ item<unsigned> delete_1M_files = {"response_time:delete_1M_files_single_dir", response_time::traversal_warm_nonracefree_1M, "The milliseconds to delete 1M files in a single directory"};
+ */
+ };
+} // namespace storage_profile
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#include "detail/impl/storage_profile.ipp"
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/include/llfio/v2.0/utils.hpp b/include/llfio/v2.0/utils.hpp
new file mode 100644
index 00000000..5869bf2a
--- /dev/null
+++ b/include/llfio/v2.0/utils.hpp
@@ -0,0 +1,294 @@
+/* Misc utilities
+(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (8 commits)
+File Created: Dec 2015
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License in the accompanying file
+Licence.txt or at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file Licence.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+*/
+
+#ifndef AFIO_UTILS_H
+#define AFIO_UTILS_H
+
+#ifndef AFIO_CONFIG_HPP
+#error You must include the master afio.hpp, not individual header files directly
+#endif
+#include "config.hpp"
+
+#include "quickcpplib/include/algorithm/string.hpp"
+
+//! \file utils.hpp Provides namespace utils
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+namespace utils
+{
+ /*! \brief Returns the smallest page size of this architecture which is useful for calculating direct i/o multiples.
+
+ \return The page size of this architecture.
+ \ingroup utils
+ \complexity{Whatever the system API takes (one would hope constant time).}
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC size_t page_size() noexcept;
+
+ /*! \brief Round a value to its next lowest page size multiple
+ */
+ template <class T> inline T round_down_to_page_size(T i) noexcept
+ {
+ const size_t pagesize = page_size();
+ i = (T)(AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<uintptr_t>(i) & ~(pagesize - 1)); // NOLINT
+ return i;
+ }
+ /*! \brief Round a value to its next highest page size multiple
+ */
+ template <class T> inline T round_up_to_page_size(T i) noexcept
+ {
+ const size_t pagesize = page_size();
+ i = (T)((AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<uintptr_t>(i) + pagesize - 1) & ~(pagesize - 1)); // NOLINT
+ return i;
+ }
+ /*! \brief Round a pair of a pointer and a size_t to their nearest page size multiples. The pointer will be rounded
+ down, the size_t upwards.
+ */
+ template <class T> inline T round_to_page_size(T i) noexcept
+ {
+ const size_t pagesize = page_size();
+ i.data = reinterpret_cast<byte *>((AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<uintptr_t>(i.data)) & ~(pagesize - 1));
+ i.len = (i.len + pagesize - 1) & ~(pagesize - 1);
+ return i;
+ }
+
+ /*! \brief Returns the page sizes of this architecture which is useful for calculating direct i/o multiples.
+
+ \param only_actually_available Only return page sizes actually available to the user running this process
+ \return The page sizes of this architecture.
+ \ingroup utils
+ \complexity{Whatever the system API takes (one would hope constant time).}
+ \exceptionmodel{Any error from the operating system or std::bad_alloc.}
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC std::vector<size_t> page_sizes(bool only_actually_available = true);
+
+ /*! \brief Returns a reasonable default size for page_allocator, typically the closest page size from
+ page_sizes() to 1Mb.
+
+ \return A value of a TLB large page size close to 1Mb.
+ \ingroup utils
+ \complexity{Whatever the system API takes (one would hope constant time).}
+ \exceptionmodel{Any error from the operating system or std::bad_alloc.}
+ */
+ inline size_t file_buffer_default_size()
+ {
+ static size_t size;
+ if(size == 0u)
+ {
+ std::vector<size_t> sizes(page_sizes(true));
+ for(auto &i : sizes)
+ {
+ if(i >= 1024 * 1024)
+ {
+ size = i;
+ break;
+ }
+ }
+ if(size == 0u)
+ {
+ size = 1024 * 1024;
+ }
+ }
+ return size;
+ }
+
+ /*! \brief Fills the buffer supplied with cryptographically strong randomness. Uses the OS kernel API.
+
+ \param buffer A buffer to fill
+ \param bytes How many bytes to fill
+ \ingroup utils
+ \complexity{Whatever the system API takes.}
+ \exceptionmodel{Any error from the operating system.}
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC void random_fill(char *buffer, size_t bytes) noexcept;
+
+ /*! \brief Returns a cryptographically random string capable of being used as a filename. Essentially random_fill() + to_hex_string().
+
+ \param randomlen The number of bytes of randomness to use for the string.
+ \return A string representing the randomness at a 2x ratio, so if 32 bytes were requested, this string would be 64 bytes long.
+ \ingroup utils
+ \complexity{Whatever the system API takes.}
+ \exceptionmodel{Any error from the operating system.}
+ */
+ inline std::string random_string(size_t randomlen)
+ {
+ size_t outlen = randomlen * 2;
+ std::string ret(outlen, 0);
+ random_fill(const_cast<char *>(ret.data()), randomlen);
+ QUICKCPPLIB_NAMESPACE::algorithm::string::to_hex_string(const_cast<char *>(ret.data()), outlen, ret.data(), randomlen);
+ return ret;
+ }
+
+ /*! \brief Tries to flush all modified data to the physical device.
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC result<void> flush_modified_data() noexcept;
+
+ /*! \brief Tries to flush all modified data to the physical device, and then drop the OS filesystem cache,
+ thus making all future reads come from the physical device. Currently only implemented for Microsoft Windows and Linux.
+
+ Note that the OS specific magic called by this routine generally requires elevated privileges for the calling process.
+ For obvious reasons, calling this will have a severe negative impact on performance, but it's very useful for
+ benchmarking cold cache vs warm cache performance.
+ */
+ AFIO_HEADERS_ONLY_FUNC_SPEC result<void> drop_filesystem_cache() noexcept;
+
+ namespace detail
+ {
+ struct large_page_allocation
+ {
+ void *p{nullptr};
+ size_t page_size_used{0};
+ size_t actual_size{0};
+ constexpr large_page_allocation() {} // NOLINT
+ constexpr large_page_allocation(void *_p, size_t pagesize, size_t actual)
+ : p(_p)
+ , page_size_used(pagesize)
+ , actual_size(actual)
+ {
+ }
+ };
+ inline large_page_allocation calculate_large_page_allocation(size_t bytes)
+ {
+ large_page_allocation ret;
+ auto pagesizes(page_sizes());
+ do
+ {
+ ret.page_size_used = pagesizes.back();
+ pagesizes.pop_back();
+ } while(!pagesizes.empty() && ((bytes / ret.page_size_used) == 0u));
+ ret.actual_size = (bytes + ret.page_size_used - 1) & ~(ret.page_size_used - 1);
+ return ret;
+ }
+ AFIO_HEADERS_ONLY_FUNC_SPEC large_page_allocation allocate_large_pages(size_t bytes);
+ AFIO_HEADERS_ONLY_FUNC_SPEC void deallocate_large_pages(void *p, size_t bytes);
+ } // namespace detail
+ /*! \class page_allocator
+ \brief An STL allocator which allocates large TLB page memory.
+ \ingroup utils
+
+ If the operating system is configured to allow it, this type of memory is
+ particularly efficient for doing large scale file i/o. This is because the
+ kernel must normally convert the scatter gather buffers you pass into
+ extended scatter gather buffers as the memory you see as contiguous may not,
+ and probably isn't, actually be contiguous in physical memory. Regions
+ returned by this allocator \em may be allocated contiguously in physical
+ memory and therefore the kernel can pass through your scatter gather buffers
+ unmodified.
+
+ A particularly useful combination with this allocator is with the
+ page_sizes() member function of __afio_dispatcher__. This will return which
+ pages sizes are possible, and which page sizes are enabled for this user. If
+ writing a file copy routine for example, using this allocator with the
+ largest page size as the copy chunk makes a great deal of sense.
+
+ Be aware that as soon as the allocation exceeds a large page size, most
+ systems allocate in multiples of the large page size, so if the large page
+ size were 2Mb and you allocate 2Mb + 1 byte, 4Mb is actually consumed.
+ */
+ template <typename T> class page_allocator
+ {
+ public:
+ using value_type = T;
+ using pointer = T *;
+ using const_pointer = const T *;
+ using reference = T &;
+ using const_reference = const T &;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using propagate_on_container_move_assignment = std::true_type;
+ using is_always_equal = std::true_type;
+
+ template <class U> struct rebind
+ {
+ using other = page_allocator<U>;
+ };
+
+ constexpr page_allocator() noexcept {} // NOLINT
+
+ template <class U> page_allocator(const page_allocator<U> & /*unused*/) noexcept {} // NOLINT
+
+ size_type max_size() const noexcept { return size_type(~0U) / sizeof(T); }
+
+ pointer address(reference x) const noexcept { return std::addressof(x); }
+
+ const_pointer address(const_reference x) const noexcept { return std::addressof(x); }
+
+ pointer allocate(size_type n, const void * /*unused*/ = nullptr)
+ {
+ if(n > max_size())
+ {
+ throw std::bad_alloc();
+ }
+ auto mem(detail::allocate_large_pages(n * sizeof(T)));
+ if(mem.p == nullptr)
+ {
+ throw std::bad_alloc();
+ }
+ return reinterpret_cast<pointer>(mem.p);
+ }
+
+ void deallocate(pointer p, size_type n)
+ {
+ if(n > max_size())
+ {
+ throw std::bad_alloc();
+ }
+ detail::deallocate_large_pages(p, n * sizeof(T));
+ }
+
+ template <class U, class... Args> void construct(U *p, Args &&... args) { ::new(reinterpret_cast<void *>(p)) U(std::forward<Args>(args)...); }
+
+ template <class U> void destroy(U *p) { p->~U(); }
+ };
+ template <> class page_allocator<void>
+ {
+ public:
+ using value_type = void;
+ using pointer = void *;
+ using const_pointer = const void *;
+ using propagate_on_container_move_assignment = std::true_type;
+ using is_always_equal = std::true_type;
+
+ template <class U> struct rebind
+ {
+ using other = page_allocator<U>;
+ };
+ };
+ template <class T, class U> inline bool operator==(const page_allocator<T> & /*unused*/, const page_allocator<U> & /*unused*/) noexcept { return true; }
+} // namespace utils
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/utils.ipp"
+#else
+#include "detail/impl/posix/utils.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+
+#endif
diff --git a/include/llfio/version.hpp b/include/llfio/version.hpp
new file mode 100644
index 00000000..7f337cf3
--- /dev/null
+++ b/include/llfio/version.hpp
@@ -0,0 +1,34 @@
+//! \file version.hpp Controls the version of AFIO for cmake, shared library and C++ namespace mangling
+#undef AFIO_VERSION_MAJOR
+#undef AFIO_VERSION_MINOR
+#undef AFIO_VERSION_PATCH
+#undef AFIO_VERSION_REVISION
+#undef AFIO_VERSION_GLUE2
+#undef AFIO_VERSION_GLUE
+#undef AFIO_HEADERS_VERSION
+#undef AFIO_NAMESPACE_VERSION
+
+//! \brief Major version for cmake and DLL version stamping \ingroup config
+#define AFIO_VERSION_MAJOR 2
+//! \brief Minor version for cmake and DLL version stamping \ingroup config
+#define AFIO_VERSION_MINOR 0
+//! \brief Patch version for cmake and DLL version stamping \ingroup config
+#define AFIO_VERSION_PATCH 0
+//! \brief Revision version for cmake and DLL version stamping \ingroup config
+#define AFIO_VERSION_REVISION 0
+
+//! \brief Defined between stable releases of AFIO. It means the inline namespace
+//! will be permuted per-commit to ensure ABI uniqueness. \ingroup config
+#define AFIO_UNSTABLE_VERSION
+
+#define AFIO_VERSION_GLUE2(a, b, c) a ## b ## c
+#define AFIO_VERSION_GLUE(a, b, c) AFIO_VERSION_GLUE2(a, b, c)
+#define AFIO_NAMESPACE_VERSION AFIO_VERSION_GLUE(AFIO_VERSION_MAJOR, _, AFIO_VERSION_MINOR)
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define AFIO_HEADERS_VERSION AFIO_VERSION_GLUE(AFIO_VERSION_MAJOR, ., AFIO_VERSION_MINOR)
+#else
+#define AFIO_HEADERS_VERSION AFIO_VERSION_MAJOR.AFIO_VERSION_MINOR
+#endif
+//! \brief The namespace AFIO_V2_NAMESPACE::v ## AFIO_NAMESPACE_VERSION
+#define AFIO_NAMESPACE_VERSION AFIO_VERSION_GLUE(AFIO_VERSION_MAJOR, _, AFIO_VERSION_MINOR)