/* An async handle to a file (C) 2015-2017 Niall Douglas (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) */ #ifndef LLFIO_ASYNC_FILE_HANDLE_H #define LLFIO_ASYNC_FILE_HANDLE_H #include "file_handle.hpp" #include "io_service.hpp" //! \file async_file_handle.hpp Provides async_file_handle LLFIO_V2_NAMESPACE_EXPORT_BEGIN namespace detail { #if __cplusplus > 201700 && (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION > 7000 /* approx end of 2017 */) template using is_invocable_r = std::is_invocable_r; #else template 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.
Cost of openingCost of i/oConcurrency and AtomicityOther remarks
`file_handle`LeastSyscallPOSIX guarantees (usually)Least gotcha
`async_file_handle`MoreMost (syscall + malloc/free + reactor)POSIX guarantees (usually)Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`
`mapped_file_handle`MostLeastNoneCannot be used with uncached i/o
\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 LLFIO_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 using io_request = io_handle::io_request; template using io_result = io_handle::io_result; 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 LLFIO_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. */ LLFIO_MAKE_FREE_FUNCTION static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result 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. */ LLFIO_MAKE_FREE_FUNCTION static inline result 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 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. */ LLFIO_MAKE_FREE_FUNCTION static inline result 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. */ LLFIO_MAKE_FREE_FUNCTION static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result 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)}; } LLFIO_MAKE_FREE_FUNCTION LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, 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 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)}; } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result clone(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 {static_cast(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; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdefaulted-function-deleted" #endif // 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 read; io_result write; constexpr result_storage() : read(buffers_type()) { } ~result_storage() { /* needed as io_result is move-only when configured with status code */} } 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) { } LLFIO_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) { LLFIO_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; }; #ifdef __clang__ #pragma clang diagnostic pop #endif struct _io_state_deleter { template void operator()(U *_ptr) const { bool must_deallocate_self = _ptr->must_deallocate_self; _ptr->~U(); if(must_deallocate_self) { auto *ptr = reinterpret_cast(_ptr); ::free(ptr); // NOLINT } } }; public: /*! Smart pointer to state of an i/o in progress. Destroying this before an i/o has completed is blocking 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 result LLFIO_HEADERS_ONLY_MEMFUNC_SPEC _begin_io(span mem, operation_t operation, io_request reqs, _erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept; LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result _begin_io(span mem, operation_t operation, io_request 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 &)`. Note that buffers returned may not be buffers input, see documentation for `barrier()`. \param kind Which kind of write reordering barrier to perform. \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. */ LLFIO_MAKE_FREE_FUNCTION template // LLFIO_REQUIRES(detail::is_invocable_r &>::value) // result async_barrier(io_request reqs, CompletionRoutine &&completion, barrier_kind kind = barrier_kind::nowait_data_only, span mem = {}) noexcept { LLFIO_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(_dest); using msvc_workaround = std::decay_t; new(dest) msvc_workaround(std::move(*this)); } void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.write)); } void *address() noexcept final { return &completion; } } ch{std::forward(completion)}; operation_t operation = operation_t::fsync_sync; if(kind == barrier_kind::nowait_all) { operation = operation_t::fsync_async; } else if(kind == barrier_kind::wait_data_only) { operation = operation_t::dsync_sync; } else if(kind == barrier_kind::nowait_data_only) { operation = operation_t::dsync_async; } return _begin_io(mem, operation, reinterpret_cast &>(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 &&)`. 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. */ LLFIO_MAKE_FREE_FUNCTION template // LLFIO_REQUIRES(detail::is_invocable_r &>::value) // result async_read(io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept { LLFIO_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(_dest); using msvc_workaround = std::decay_t; new(dest) msvc_workaround(std::move(*this)); } void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.read)); } void *address() noexcept final { return &completion; } } ch{std::forward(completion)}; return _begin_io(mem, operation_t::read, io_request({reinterpret_cast(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 &&)`. 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. */ LLFIO_MAKE_FREE_FUNCTION template // LLFIO_REQUIRES(detail::is_invocable_r &>::value) // result async_write(io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept { LLFIO_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(_dest); using msvc_workaround = std::decay_t; new(dest) msvc_workaround(std::move(*this)); } void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.write)); } void *address() noexcept final { return &completion; } } ch{std::forward(completion)}; return _begin_io(mem, operation_t::write, reqs, std::move(ch)); } using file_handle::read; LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept override; using file_handle::write; LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept override; #if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) private: template class awaitable_state { friend class async_file_handle; optional> _suspended; optional> _result; // Called on completion of the i/o void operator()(async_file_handle * /*unused*/, io_result &&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 awaitable { friend class async_file_handle; io_state_ptr _state; awaitable_state *_astate; explicit awaitable(io_state_ptr state) : _state(std::move(state)) , _astate(reinterpret_cast *>(_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 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. */ LLFIO_MAKE_FREE_FUNCTION result> co_read(io_request reqs) noexcept { OUTCOME_TRY(r, async_read(reqs, awaitable_state())); return awaitable(std::move(r)); } //! \overload LLFIO_MAKE_FREE_FUNCTION result> co_read(extent_type offset, std::initializer_list lst) 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 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. */ LLFIO_MAKE_FREE_FUNCTION result> co_write(io_request reqs) noexcept { OUTCOME_TRY(r, async_write(reqs, awaitable_state())); return awaitable(std::move(r)); } //! \overload LLFIO_MAKE_FREE_FUNCTION result> co_write(extent_type offset, std::initializer_list lst) 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 co_write(reqs); } #endif }; //! \brief Constructor for `async_file_handle` template <> struct construct { 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 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(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(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(service), std::forward(base), std::forward(_path), std::forward(_mode), std::forward(_creation), std::forward(_caching), std::forward(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_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(service), std::forward(dirpath), std::forward(_mode), std::forward(_caching), std::forward(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_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(service), std::forward(name), std::forward(_mode), std::forward(_creation), std::forward(_caching), std::forward(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_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(service), std::forward(dir), std::forward(_mode), std::forward(flags)); } /*! \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 &)`. 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 inline result async_read(async_file_handle &self, async_file_handle::io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept { return self.async_read(std::forward(reqs), std::forward(completion), std::forward(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 &)`. 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 inline result async_write(async_file_handle &self, async_file_handle::io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept { return self.async_write(std::forward(reqs), std::forward(completion), std::forward(mem)); } #if defined(LLFIO_ENABLE_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> co_read(async_file_handle &self, async_file_handle::io_request reqs) noexcept { return self.co_read(std::forward(reqs)); } //! \overload inline result> co_read(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list lst) noexcept { return self.co_read(std::forward(offset), std::forward(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> co_write(async_file_handle &self, async_file_handle::io_request reqs) noexcept { return self.co_write(std::forward(reqs)); } //! \overload inline result> co_write(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list lst) noexcept { return self.co_write(std::forward(offset), std::forward(lst)); } #endif // 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_service.ipp" #include "detail/impl/windows/async_file_handle.ipp" #else #include "detail/impl/posix/io_service.ipp" #include "detail/impl/posix/async_file_handle.ipp" #endif #undef LLFIO_INCLUDED_BY_HEADER #endif #endif