/* A handle to something (C) 2015-2017 Niall Douglas (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 LLFIO_IO_HANDLE_H #define LLFIO_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 LLFIO_V2_NAMESPACE_EXPORT_BEGIN /*! \class io_handle \brief A handle to something capable of scatter-gather i/o. */ class LLFIO_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`. //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. struct buffer_type { //! Type of the pointer to memory. using pointer = byte *; //! Type of the pointer to memory. using const_pointer = const 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; //! Default constructor buffer_type() = default; //! Constructor constexpr buffer_type(pointer data, size_type len) noexcept : _data(data), _len(len) {} buffer_type(const buffer_type &) = default; buffer_type(buffer_type &&) = default; buffer_type &operator=(const buffer_type &) = default; buffer_type &operator=(buffer_type &&) = default; ~buffer_type() = default; // Emulation of this being a span in the TS //! Returns the address of the bytes for this buffer constexpr pointer data() noexcept { return _data; } //! Returns the address of the bytes for this buffer constexpr const_pointer data() const noexcept { return _data; } //! Returns the number of bytes in this buffer constexpr size_type size() const noexcept { return _len; } //! Returns an iterator to the beginning of the buffer constexpr iterator begin() noexcept { return _data; } //! Returns an iterator to the beginning of the buffer constexpr const_iterator begin() const noexcept { return _data; } //! Returns an iterator to the beginning of the buffer constexpr const_iterator cbegin() const noexcept { return _data; } //! Returns an iterator to after the end of the buffer constexpr iterator end() noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer constexpr const_iterator end() const noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer constexpr const_iterator cend() const noexcept { return _data + _len; } private: friend constexpr inline void _check_iovec_match(); pointer _data; size_type _len; }; //! The gather buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. struct const_buffer_type { //! Type of the pointer to memory. using pointer = const byte *; //! Type of the pointer to memory. using const_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; //! Default constructor const_buffer_type() = default; //! Constructor constexpr const_buffer_type(pointer data, size_type len) noexcept : _data(data), _len(len) {} const_buffer_type(const const_buffer_type &) = default; const_buffer_type(const_buffer_type &&) = default; const_buffer_type &operator=(const const_buffer_type &) = default; const_buffer_type &operator=(const_buffer_type &&) = default; ~const_buffer_type() = default; // Emulation of this being a span in the TS //! Returns the address of the bytes for this buffer constexpr pointer data() noexcept { return _data; } //! Returns the address of the bytes for this buffer constexpr const_pointer data() const noexcept { return _data; } //! Returns the number of bytes in this buffer constexpr size_type size() const noexcept { return _len; } //! Returns an iterator to the beginning of the buffer constexpr iterator begin() noexcept { return _data; } //! Returns an iterator to the beginning of the buffer constexpr const_iterator begin() const noexcept { return _data; } //! Returns an iterator to the beginning of the buffer constexpr const_iterator cbegin() const noexcept { return _data; } //! Returns an iterator to after the end of the buffer constexpr iterator end() noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer constexpr const_iterator end() const noexcept { return _data + _len; } //! Returns an iterator to after the end of the buffer constexpr const_iterator cend() const noexcept { return _data + _len; } private: pointer _data; size_type _len; }; #ifndef NDEBUG static_assert(std::is_trivial::value, "buffer_type is not a trivial type!"); static_assert(std::is_trivial::value, "const_buffer_type is not a trivial type!"); static_assert(std::is_standard_layout::value, "buffer_type is not a standard layout type!"); static_assert(std::is_standard_layout::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; //! The gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. using const_buffers_type = span; #ifndef NDEBUG // Is trivial in all ways, except default constructibility static_assert(std::is_trivially_copyable::value, "buffers_type is not trivially copyable!"); // static_assert(std::is_trivially_assignable::value, "buffers_type is not trivially assignable!"); // static_assert(std::is_trivially_destructible::value, "buffers_type is not trivially destructible!"); // static_assert(std::is_trivially_copy_constructible::value, "buffers_type is not trivially copy constructible!"); // static_assert(std::is_trivially_move_constructible::value, "buffers_type is not trivially move constructible!"); // static_assert(std::is_trivially_copy_assignable::value, "buffers_type is not trivially copy assignable!"); // static_assert(std::is_trivially_move_assignable::value, "buffers_type is not trivially move assignable!"); static_assert(std::is_standard_layout::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 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>::value, "io_request is not trivially copyable!"); // static_assert(std::is_trivially_assignable, io_request>::value, "io_request is not trivially assignable!"); // static_assert(std::is_trivially_destructible>::value, "io_request is not trivially destructible!"); // static_assert(std::is_trivially_copy_constructible>::value, "io_request is not trivially copy constructible!"); // static_assert(std::is_trivially_move_constructible>::value, "io_request is not trivially move constructible!"); // static_assert(std::is_trivially_copy_assignable>::value, "io_request is not trivially copy assignable!"); // static_assert(std::is_trivially_move_assignable>::value, "io_request is not trivially move assignable!"); static_assert(std::is_standard_layout>::value, "io_request is not a standard layout type!"); #endif //! The i/o result type used by this handle. Guaranteed to be `TrivialType` apart from construction.. template struct io_result : public LLFIO_V2_NAMESPACE::result { using Base = LLFIO_V2_NAMESPACE::result; size_type _bytes_transferred{static_cast(-1)}; #if defined(_MSC_VER) && !defined(__clang__) // workaround MSVC parsing bug constexpr io_result() : Base() { } template constexpr io_result(Args &&... args) : Base(std::forward(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(-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>::value, "io_result is not trivially copyable!"); // static_assert(std::is_trivially_assignable, io_result>::value, "io_result is not trivially assignable!"); // static_assert(std::is_trivially_destructible>::value, "io_result is not trivially destructible!"); // static_assert(std::is_trivially_copy_constructible>::value, "io_result is not trivially copy constructible!"); // static_assert(std::is_trivially_move_constructible>::value, "io_result is not trivially move constructible!"); // static_assert(std::is_trivially_copy_assignable>::value, "io_result is not trivially copy assignable!"); // static_assert(std::is_trivially_move_assignable>::value, "io_result is not trivially move assignable!"); //! \todo Why is io_result not a standard layout type? // static_assert(std::is_standard_layout>::value, "result is not a standard layout type!"); // static_assert(std::is_standard_layout>::value, "io_result 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. */ LLFIO_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. */ LLFIO_MAKE_FREE_FUNCTION LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept; //! \overload LLFIO_MAKE_FREE_FUNCTION io_result read(extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept { buffer_type *_reqs = reinterpret_cast(alloca(sizeof(buffer_type) * lst.size())); memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size()); io_request 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. */ LLFIO_MAKE_FREE_FUNCTION LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept; //! \overload LLFIO_MAKE_FREE_FUNCTION io_result write(extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept { const_buffer_type *_reqs = reinterpret_cast(alloca(sizeof(const_buffer_type) * lst.size())); memcpy(_reqs, lst.begin(), sizeof(const_buffer_type) * lst.size()); io_request 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. */ LLFIO_MAKE_FREE_FUNCTION virtual io_result barrier(io_request reqs = io_request(), 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() 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. */ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result lock(extent_type offset, extent_type bytes, bool exclusive = true, deadline d = deadline()) noexcept; //! \overload result 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 lock(io_request reqs, deadline d = deadline()) noexcept { size_t bytes = 0; for(auto &i : reqs.buffers) { if(bytes + i.size() < bytes) { return errc::value_too_large; } bytes += i.size(); } return lock(reqs.offset, bytes, false, d); } //! \overload Locks for exclusive access result lock(io_request reqs, deadline d = deadline()) noexcept { size_t bytes = 0; for(auto &i : reqs.buffers) { if(bytes + i.size() < bytes) { return errc::value_too_large; } bytes += i.size(); } 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. */ LLFIO_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 read(io_handle &self, io_handle::io_request reqs, deadline d = deadline()) noexcept { return self.read(std::forward(reqs), std::forward(d)); } //! \overload inline io_handle::io_result read(io_handle &self, io_handle::extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept { return self.read(std::forward(offset), std::forward(lst), std::forward(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 write(io_handle &self, io_handle::io_request reqs, deadline d = deadline()) noexcept { return self.write(std::forward(reqs), std::forward(d)); } //! \overload inline io_handle::io_result write(io_handle &self, io_handle::extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept { return self.write(std::forward(offset), std::forward(lst), std::forward(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 barrier(io_handle &self, io_handle::io_request reqs = io_handle::io_request(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept { return self.barrier(std::forward(reqs), std::forward(wait_for_device), std::forward(and_metadata), std::forward(d)); } // END make_free_functions.py LLFIO_V2_NAMESPACE_END #if LLFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) #define LLFIO_INCLUDED_BY_HEADER 1 #ifdef _WIN32 #include "detail/impl/windows/io_handle.ipp" #else #include "detail/impl/posix/io_handle.ipp" #endif #undef LLFIO_INCLUDED_BY_HEADER #endif #ifdef _MSC_VER #pragma warning(pop) #endif #endif