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_STREAM_FILE_HPP
 
11 +
#define BOOST_COROSIO_STREAM_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/file_base.hpp>
 
18 +
#include <boost/corosio/io/io_stream.hpp>
 
19 +
#include <boost/capy/ex/execution_context.hpp>
 
20 +
#include <boost/capy/concept/executor.hpp>
 
21 +

 
22 +
#include <concepts>
 
23 +
#include <cstdint>
 
24 +
#include <filesystem>
 
25 +

 
26 +
namespace boost::corosio {
 
27 +

 
28 +
/** An asynchronous sequential file for coroutine I/O.
 
29 +

 
30 +
    Provides asynchronous read and write operations on a regular
 
31 +
    file with an implicit position that advances after each
 
32 +
    operation.
 
33 +

 
34 +
    Inherits from @ref io_stream, so `read_some` and `write_some`
 
35 +
    are available and work with any algorithm that accepts an
 
36 +
    `io_stream&`.
 
37 +

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

 
42 +
    @par Thread Safety
 
43 +
    Distinct objects: Safe.@n
 
44 +
    Shared objects: Unsafe. Only one asynchronous operation
 
45 +
    may be in flight at a time.
 
46 +

 
47 +
    @par Example
 
48 +
    @code
 
49 +
    io_context ioc;
 
50 +
    stream_file f(ioc);
 
51 +
    f.open("data.bin", file_base::read_only);
 
52 +

 
53 +
    char buf[4096];
 
54 +
    auto [ec, n] = co_await f.read_some(
 
55 +
        capy::mutable_buffer(buf, sizeof(buf)));
 
56 +
    if (ec == capy::cond::eof)
 
57 +
        // end of file
 
58 +
    @endcode
 
59 +
*/
 
60 +
class BOOST_COROSIO_DECL stream_file : public io_stream
 
61 +
{
 
62 +
public:
 
63 +
    /** Platform-specific file implementation interface.
 
64 +

 
65 +
        Backends derive from this to provide file I/O.
 
66 +
        `read_some` and `write_some` are inherited from
 
67 +
        @ref io_stream::implementation.
 
68 +
    */
 
69 +
    struct implementation : io_stream::implementation
 
70 +
    {
 
71 +
        /// Return the platform file descriptor or handle.
 
72 +
        virtual native_handle_type native_handle() const noexcept = 0;
 
73 +

 
74 +
        /// Cancel pending asynchronous operations.
 
75 +
        virtual void cancel() noexcept = 0;
 
76 +

 
77 +
        /// Return the file size in bytes.
 
78 +
        virtual std::uint64_t size() const = 0;
 
79 +

 
80 +
        /// Resize the file to @p new_size bytes.
 
81 +
        virtual void resize(std::uint64_t new_size) = 0;
 
82 +

 
83 +
        /// Synchronize file data to stable storage.
 
84 +
        virtual void sync_data() = 0;
 
85 +

 
86 +
        /// Synchronize file data and metadata to stable storage.
 
87 +
        virtual void sync_all() = 0;
 
88 +

 
89 +
        /// Release ownership of the native handle.
 
90 +
        virtual native_handle_type release() = 0;
 
91 +

 
92 +
        /// Adopt an existing native handle.
 
93 +
        virtual void assign(native_handle_type handle) = 0;
 
94 +

 
95 +
        /** Move the file position.
 
96 +

 
97 +
            @param offset Signed offset from @p origin.
 
98 +
            @param origin The reference point for the seek.
 
99 +
            @return The new absolute position.
 
100 +
        */
 
101 +
        virtual std::uint64_t
 
102 +
        seek(std::int64_t offset, file_base::seek_basis origin) = 0;
 
103 +
    };
 
104 +

 
105 +
    /** Destructor.
 
106 +

 
107 +
        Closes the file if open, cancelling any pending operations.
 
108 +
    */
 
109 +
    ~stream_file() override;
 
110 +

 
111 +
    /** Construct from an execution context.
 
112 +

 
113 +
        @param ctx The execution context that will own this file.
 
114 +
    */
 
115 +
    explicit stream_file(capy::execution_context& ctx);
 
116 +

 
117 +
    /** Construct from an executor.
 
118 +

 
119 +
        @param ex The executor whose context will own this file.
 
120 +
    */
 
121 +
    template<class Ex>
 
122 +
        requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
 
123 +
        capy::Executor<Ex>
 
124 +
    explicit stream_file(Ex const& ex) : stream_file(ex.context())
 
125 +
    {
 
126 +
    }
 
127 +

 
128 +
    /** Move constructor.
 
129 +

 
130 +
        Transfers ownership of the file resources.
 
131 +
    */
 
132 +
    stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
 
133 +

 
134 +
    /** Move assignment operator.
 
135 +

 
136 +
        Closes any existing file and transfers ownership.
 
137 +
    */
 
138 +
    stream_file& operator=(stream_file&& other) noexcept
 
139 +
    {
 
140 +
        if (this != &other)
 
141 +
        {
 
142 +
            close();
 
143 +
            h_ = std::move(other.h_);
 
144 +
        }
 
145 +
        return *this;
 
146 +
    }
 
147 +

 
148 +
    stream_file(stream_file const&)            = delete;
 
149 +
    stream_file& operator=(stream_file const&) = delete;
 
150 +

 
151 +
    // read_some() inherited from io_read_stream
 
152 +
    // write_some() inherited from io_write_stream
 
153 +

 
154 +
    /** Open a file.
 
155 +

 
156 +
        @param path The filesystem path to open.
 
157 +
        @param mode Bitmask of @ref file_base::flags specifying
 
158 +
            access mode and creation behavior.
 
159 +

 
160 +
        @throws std::system_error on failure.
 
161 +
    */
 
162 +
    void open(
 
163 +
        std::filesystem::path const& path,
 
164 +
        file_base::flags mode = file_base::read_only);
 
165 +

 
166 +
    /** Close the file.
 
167 +

 
168 +
        Releases file resources. Any pending operations complete
 
169 +
        with `errc::operation_canceled`.
 
170 +
    */
 
171 +
    void close();
 
172 +

 
173 +
    /** Check if the file is open.
 
174 +

 
175 +
        @return `true` if the file is open and ready for I/O.
 
176 +
    */
 
177 +
    bool is_open() const noexcept
 
178 +
    {
 
179 +
#if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
 
180 +
        return h_ && get().native_handle() != ~native_handle_type(0);
 
181 +
#else
 
182 +
        return h_ && get().native_handle() >= 0;
 
183 +
#endif
 
184 +
    }
 
185 +

 
186 +
    /** Cancel pending asynchronous operations.
 
187 +

 
188 +
        All outstanding operations complete with
 
189 +
        `errc::operation_canceled`.
 
190 +
    */
 
191 +
    void cancel();
 
192 +

 
193 +
    /** Get the native file descriptor or handle.
 
194 +

 
195 +
        @return The native handle, or -1/INVALID_HANDLE_VALUE
 
196 +
            if not open.
 
197 +
    */
 
198 +
    native_handle_type native_handle() const noexcept;
 
199 +

 
200 +
    /** Return the file size in bytes.
 
201 +

 
202 +
        @throws std::system_error on failure.
 
203 +
    */
 
204 +
    std::uint64_t size() const;
 
205 +

 
206 +
    /** Resize the file to @p new_size bytes.
 
207 +

 
208 +
        @param new_size The new file size.
 
209 +
        @throws std::system_error on failure.
 
210 +
    */
 
211 +
    void resize(std::uint64_t new_size);
 
212 +

 
213 +
    /** Synchronize file data to stable storage.
 
214 +

 
215 +
        @throws std::system_error on failure.
 
216 +
    */
 
217 +
    void sync_data();
 
218 +

 
219 +
    /** Synchronize file data and metadata to stable storage.
 
220 +

 
221 +
        @throws std::system_error on failure.
 
222 +
    */
 
223 +
    void sync_all();
 
224 +

 
225 +
    /** Release ownership of the native handle.
 
226 +

 
227 +
        The file object becomes not-open. The caller is
 
228 +
        responsible for closing the returned handle.
 
229 +

 
230 +
        @return The native file descriptor or handle.
 
231 +
    */
 
232 +
    native_handle_type release();
 
233 +

 
234 +
    /** Adopt an existing native handle.
 
235 +

 
236 +
        Closes any currently open file before adopting.
 
237 +
        The file object takes ownership of the handle.
 
238 +

 
239 +
        @param handle The native file descriptor or handle.
 
240 +
        @throws std::system_error on failure.
 
241 +
    */
 
242 +
    void assign(native_handle_type handle);
 
243 +

 
244 +
    /** Move the file position.
 
245 +

 
246 +
        @param offset Signed offset from @p origin.
 
247 +
        @param origin The reference point for the seek.
 
248 +
        @return The new absolute position.
 
249 +
        @throws std::system_error on failure.
 
250 +
    */
 
251 +
    std::uint64_t
 
252 +
    seek(std::int64_t offset,
 
253 +
         file_base::seek_basis origin = file_base::seek_set);
 
254 +

 
255 +
private:
 
256 +
    inline implementation& get() const noexcept
 
257 +
    {
 
258 +
        return *static_cast<implementation*>(h_.get());
 
259 +
    }
 
260 +
};
 
261 +

 
262 +
} // namespace boost::corosio
 
263 +

 
264 +
#endif // BOOST_COROSIO_STREAM_FILE_HPP