1 +
//
 
2 +
// Copyright (c) 2026 Michael Vandeberg
 
3 +
//
 
4 +
// Distributed under the Boost Software License, Version 1.0. (See accompanying
 
5 +
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 
6 +
//
 
7 +
// Official repository: https://github.com/cppalliance/corosio
 
8 +
//
 
9 +

 
10 +
#ifndef BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
 
11 +
#define BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
 
12 +

 
13 +
#include <boost/corosio/detail/config.hpp>
 
14 +
#include <boost/corosio/detail/platform.hpp>
 
15 +
#include <boost/corosio/detail/except.hpp>
 
16 +
#include <boost/corosio/detail/native_handle.hpp>
 
17 +
#include <boost/corosio/detail/buffer_param.hpp>
 
18 +
#include <boost/corosio/file_base.hpp>
 
19 +
#include <boost/corosio/io/io_object.hpp>
 
20 +
#include <boost/capy/io_result.hpp>
 
21 +
#include <boost/capy/ex/executor_ref.hpp>
 
22 +
#include <boost/capy/ex/execution_context.hpp>
 
23 +
#include <boost/capy/ex/io_env.hpp>
 
24 +
#include <boost/capy/concept/executor.hpp>
 
25 +
#include <boost/capy/buffers.hpp>
 
26 +

 
27 +
#include <concepts>
 
28 +
#include <coroutine>
 
29 +
#include <cstddef>
 
30 +
#include <cstdint>
 
31 +
#include <type_traits>
 
32 +
#include <filesystem>
 
33 +
#include <stop_token>
 
34 +
#include <system_error>
 
35 +

 
36 +
namespace boost::corosio {
 
37 +

 
38 +
/** An asynchronous random-access file for coroutine I/O.
 
39 +

 
40 +
    Provides asynchronous read and write operations at explicit
 
41 +
    byte offsets, without maintaining an implicit file position.
 
42 +

 
43 +
    On POSIX platforms, file I/O is dispatched to a thread pool
 
44 +
    (blocking `preadv`/`pwritev`) with completion posted back to
 
45 +
    the scheduler. On Windows, true overlapped I/O is used via IOCP.
 
46 +

 
47 +
    @par Thread Safety
 
48 +
    Distinct objects: Safe.@n
 
49 +
    Shared objects: Unsafe. Multiple concurrent reads and writes
 
50 +
    are supported from coroutines sharing the same file object,
 
51 +
    but external synchronization is required for non-async
 
52 +
    operations (open, close, size, resize, etc.).
 
53 +

 
54 +
    @par Example
 
55 +
    @code
 
56 +
    io_context ioc;
 
57 +
    random_access_file f(ioc);
 
58 +
    f.open("data.bin", file_base::read_only);
 
59 +

 
60 +
    char buf[4096];
 
61 +
    auto [ec, n] = co_await f.read_some_at(
 
62 +
        0, capy::mutable_buffer(buf, sizeof(buf)));
 
63 +
    @endcode
 
64 +
*/
 
65 +
class BOOST_COROSIO_DECL random_access_file : public io_object
 
66 +
{
 
67 +
public:
 
68 +
    /** Platform-specific random-access file implementation interface.
 
69 +

 
70 +
        Backends derive from this to provide offset-based file I/O.
 
71 +
    */
 
72 +
    struct implementation : io_object::implementation
 
73 +
    {
 
74 +
        /** Initiate a read at the given offset.
 
75 +

 
76 +
            @param offset Byte offset into the file.
 
77 +
            @param h Coroutine handle to resume on completion.
 
78 +
            @param ex Executor for dispatching the completion.
 
79 +
            @param buf The buffer to read into.
 
80 +
            @param token Stop token for cancellation.
 
81 +
            @param ec Output error code.
 
82 +
            @param bytes_out Output bytes transferred.
 
83 +
            @return Coroutine handle to resume immediately.
 
84 +
        */
 
85 +
        virtual std::coroutine_handle<> read_some_at(
 
86 +
            std::uint64_t offset,
 
87 +
            std::coroutine_handle<> h,
 
88 +
            capy::executor_ref ex,
 
89 +
            buffer_param buf,
 
90 +
            std::stop_token token,
 
91 +
            std::error_code* ec,
 
92 +
            std::size_t* bytes_out) = 0;
 
93 +

 
94 +
        /** Initiate a write at the given offset.
 
95 +

 
96 +
            @param offset Byte offset into the file.
 
97 +
            @param h Coroutine handle to resume on completion.
 
98 +
            @param ex Executor for dispatching the completion.
 
99 +
            @param buf The buffer to write from.
 
100 +
            @param token Stop token for cancellation.
 
101 +
            @param ec Output error code.
 
102 +
            @param bytes_out Output bytes transferred.
 
103 +
            @return Coroutine handle to resume immediately.
 
104 +
        */
 
105 +
        virtual std::coroutine_handle<> write_some_at(
 
106 +
            std::uint64_t offset,
 
107 +
            std::coroutine_handle<> h,
 
108 +
            capy::executor_ref ex,
 
109 +
            buffer_param buf,
 
110 +
            std::stop_token token,
 
111 +
            std::error_code* ec,
 
112 +
            std::size_t* bytes_out) = 0;
 
113 +

 
114 +
        /// Return the platform file descriptor or handle.
 
115 +
        virtual native_handle_type native_handle() const noexcept = 0;
 
116 +

 
117 +
        /// Cancel pending asynchronous operations.
 
118 +
        virtual void cancel() noexcept = 0;
 
119 +

 
120 +
        /// Return the file size in bytes.
 
121 +
        virtual std::uint64_t size() const = 0;
 
122 +

 
123 +
        /// Resize the file to @p new_size bytes.
 
124 +
        virtual void resize(std::uint64_t new_size) = 0;
 
125 +

 
126 +
        /// Synchronize file data to stable storage.
 
127 +
        virtual void sync_data() = 0;
 
128 +

 
129 +
        /// Synchronize file data and metadata to stable storage.
 
130 +
        virtual void sync_all() = 0;
 
131 +

 
132 +
        /// Release ownership of the native handle.
 
133 +
        virtual native_handle_type release() = 0;
 
134 +

 
135 +
        /// Adopt an existing native handle.
 
136 +
        virtual void assign(native_handle_type handle) = 0;
 
137 +
    };
 
138 +

 
139 +
    /** Awaitable for async read-at operations. */
 
140 +
    template<class MutableBufferSequence>
 
141 +
    struct read_some_at_awaitable
 
142 +
    {
 
143 +
        random_access_file& f_;
 
144 +
        std::uint64_t offset_;
 
145 +
        MutableBufferSequence buffers_;
 
146 +
        std::stop_token token_;
 
147 +
        mutable std::error_code ec_;
 
148 +
        mutable std::size_t bytes_ = 0;
 
149 +

 
150 +
        read_some_at_awaitable(
 
151 +
            random_access_file& f,
 
152 +
            std::uint64_t offset,
 
153 +
            MutableBufferSequence buffers)
 
154 +
            noexcept(std::is_nothrow_move_constructible_v<MutableBufferSequence>)
 
155 +
            : f_(f)
 
156 +
            , offset_(offset)
 
157 +
            , buffers_(std::move(buffers))
 
158 +
        {
 
159 +
        }
 
160 +

 
161 +
        bool await_ready() const noexcept
 
162 +
        {
 
163 +
            return false;
 
164 +
        }
 
165 +

 
166 +
        capy::io_result<std::size_t> await_resume() const noexcept
 
167 +
        {
 
168 +
            return {ec_, bytes_};
 
169 +
        }
 
170 +

 
171 +
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
 
172 +
            -> std::coroutine_handle<>
 
173 +
        {
 
174 +
            token_ = env->stop_token;
 
175 +
            return f_.get().read_some_at(
 
176 +
                offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
 
177 +
        }
 
178 +
    };
 
179 +

 
180 +
    /** Awaitable for async write-at operations. */
 
181 +
    template<class ConstBufferSequence>
 
182 +
    struct write_some_at_awaitable
 
183 +
    {
 
184 +
        random_access_file& f_;
 
185 +
        std::uint64_t offset_;
 
186 +
        ConstBufferSequence buffers_;
 
187 +
        std::stop_token token_;
 
188 +
        mutable std::error_code ec_;
 
189 +
        mutable std::size_t bytes_ = 0;
 
190 +

 
191 +
        write_some_at_awaitable(
 
192 +
            random_access_file& f,
 
193 +
            std::uint64_t offset,
 
194 +
            ConstBufferSequence buffers)
 
195 +
            noexcept(std::is_nothrow_move_constructible_v<ConstBufferSequence>)
 
196 +
            : f_(f)
 
197 +
            , offset_(offset)
 
198 +
            , buffers_(std::move(buffers))
 
199 +
        {
 
200 +
        }
 
201 +

 
202 +
        bool await_ready() const noexcept
 
203 +
        {
 
204 +
            return false;
 
205 +
        }
 
206 +

 
207 +
        capy::io_result<std::size_t> await_resume() const noexcept
 
208 +
        {
 
209 +
            return {ec_, bytes_};
 
210 +
        }
 
211 +

 
212 +
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
 
213 +
            -> std::coroutine_handle<>
 
214 +
        {
 
215 +
            token_ = env->stop_token;
 
216 +
            return f_.get().write_some_at(
 
217 +
                offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
 
218 +
        }
 
219 +
    };
 
220 +

 
221 +
public:
 
222 +
    /** Destructor.
 
223 +

 
224 +
        Closes the file if open, cancelling any pending operations.
 
225 +
    */
 
226 +
    ~random_access_file() override;
 
227 +

 
228 +
    /** Construct from an execution context.
 
229 +

 
230 +
        @param ctx The execution context that will own this file.
 
231 +
    */
 
232 +
    explicit random_access_file(capy::execution_context& ctx);
 
233 +

 
234 +
    /** Construct from an executor.
 
235 +

 
236 +
        @param ex The executor whose context will own this file.
 
237 +
    */
 
238 +
    template<class Ex>
 
239 +
        requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
 
240 +
        capy::Executor<Ex>
 
241 +
    explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
 
242 +
    {
 
243 +
    }
 
244 +

 
245 +
    /** Move constructor. */
 
246 +
    random_access_file(random_access_file&& other) noexcept
 
247 +
        : io_object(std::move(other))
 
248 +
    {
 
249 +
    }
 
250 +

 
251 +
    /** Move assignment operator. */
 
252 +
    random_access_file& operator=(random_access_file&& other) noexcept
 
253 +
    {
 
254 +
        if (this != &other)
 
255 +
        {
 
256 +
            close();
 
257 +
            h_ = std::move(other.h_);
 
258 +
        }
 
259 +
        return *this;
 
260 +
    }
 
261 +

 
262 +
    random_access_file(random_access_file const&)            = delete;
 
263 +
    random_access_file& operator=(random_access_file const&) = delete;
 
264 +

 
265 +
    /** Open a file.
 
266 +

 
267 +
        @param path The filesystem path to open.
 
268 +
        @param mode Bitmask of @ref file_base::flags specifying
 
269 +
            access mode and creation behavior.
 
270 +

 
271 +
        @throws std::system_error on failure.
 
272 +
    */
 
273 +
    void open(
 
274 +
        std::filesystem::path const& path,
 
275 +
        file_base::flags mode = file_base::read_only);
 
276 +

 
277 +
    /** Close the file.
 
278 +

 
279 +
        Releases file resources. Any pending operations complete
 
280 +
        with `errc::operation_canceled`.
 
281 +
    */
 
282 +
    void close();
 
283 +

 
284 +
    /** Check if the file is open. */
 
285 +
    bool is_open() const noexcept
 
286 +
    {
 
287 +
#if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
 
288 +
        return h_ && get().native_handle() != ~native_handle_type(0);
 
289 +
#else
 
290 +
        return h_ && get().native_handle() >= 0;
 
291 +
#endif
 
292 +
    }
 
293 +

 
294 +
    /** Read data at the given offset.
 
295 +

 
296 +
        @param offset Byte offset into the file.
 
297 +
        @param buffers The buffer sequence to read into.
 
298 +

 
299 +
        @return An awaitable yielding `(error_code, std::size_t)`.
 
300 +

 
301 +
        @throws std::logic_error if the file is not open.
 
302 +
    */
 
303 +
    template<capy::MutableBufferSequence MB>
 
304 +
    auto read_some_at(std::uint64_t offset, MB const& buffers)
 
305 +
    {
 
306 +
        if (!is_open())
 
307 +
            detail::throw_logic_error("read_some_at: file not open");
 
308 +
        return read_some_at_awaitable<MB>(*this, offset, buffers);
 
309 +
    }
 
310 +

 
311 +
    /** Write data at the given offset.
 
312 +

 
313 +
        @param offset Byte offset into the file.
 
314 +
        @param buffers The buffer sequence to write from.
 
315 +

 
316 +
        @return An awaitable yielding `(error_code, std::size_t)`.
 
317 +

 
318 +
        @throws std::logic_error if the file is not open.
 
319 +
    */
 
320 +
    template<capy::ConstBufferSequence CB>
 
321 +
    auto write_some_at(std::uint64_t offset, CB const& buffers)
 
322 +
    {
 
323 +
        if (!is_open())
 
324 +
            detail::throw_logic_error("write_some_at: file not open");
 
325 +
        return write_some_at_awaitable<CB>(*this, offset, buffers);
 
326 +
    }
 
327 +

 
328 +
    /** Cancel pending asynchronous operations. */
 
329 +
    void cancel();
 
330 +

 
331 +
    /** Get the native file descriptor or handle. */
 
332 +
    native_handle_type native_handle() const noexcept;
 
333 +

 
334 +
    /** Return the file size in bytes. */
 
335 +
    std::uint64_t size() const;
 
336 +

 
337 +
    /** Resize the file. */
 
338 +
    void resize(std::uint64_t new_size);
 
339 +

 
340 +
    /** Synchronize file data to stable storage. */
 
341 +
    void sync_data();
 
342 +

 
343 +
    /** Synchronize file data and metadata to stable storage. */
 
344 +
    void sync_all();
 
345 +

 
346 +
    /** Release ownership of the native handle.
 
347 +

 
348 +
        The file object becomes not-open. The caller is
 
349 +
        responsible for closing the returned handle.
 
350 +

 
351 +
        @return The native file descriptor or handle.
 
352 +
    */
 
353 +
    native_handle_type release();
 
354 +

 
355 +
    /** Adopt an existing native handle.
 
356 +

 
357 +
        Closes any currently open file before adopting.
 
358 +
        The file object takes ownership of the handle.
 
359 +

 
360 +
        @param handle The native file descriptor or handle.
 
361 +
    */
 
362 +
    void assign(native_handle_type handle);
 
363 +

 
364 +
private:
 
365 +
    inline implementation& get() const noexcept
 
366 +
    {
 
367 +
        return *static_cast<implementation*>(h_.get());
 
368 +
    }
 
369 +
};
 
370 +

 
371 +
} // namespace boost::corosio
 
372 +

 
373 +
#endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP