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/v2.0/io_handle.hpp
parent69e9c42365a92bc1af57a843f48513e19aaf0cdf (diff)
Rename afio to llfio part 1 of many
Diffstat (limited to 'include/llfio/v2.0/io_handle.hpp')
-rw-r--r--include/llfio/v2.0/io_handle.hpp611
1 files changed, 611 insertions, 0 deletions
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