TLA Line data Source code
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 : #include <boost/capy/io_result.hpp>
22 :
23 : #include <concepts>
24 : #include <cstdint>
25 : #include <filesystem>
26 : #include <system_error>
27 :
28 : namespace boost::corosio {
29 :
30 : /** An asynchronous sequential file for coroutine I/O.
31 :
32 : Provides asynchronous read and write operations on a regular
33 : file with an implicit position that advances after each
34 : operation.
35 :
36 : Inherits from @ref io_stream, so `read_some` and `write_some`
37 : are available and work with any algorithm that accepts an
38 : `io_stream&`.
39 :
40 : On POSIX platforms, file I/O is dispatched to a thread pool
41 : (blocking `preadv`/`pwritev`) with completion posted back to
42 : the scheduler. On Windows, true overlapped I/O is used via IOCP.
43 :
44 : @par Thread Safety
45 : Distinct objects: Safe.@n
46 : Shared objects: Unsafe. Only one asynchronous operation
47 : may be in flight at a time.
48 :
49 : @par Example
50 : @code
51 : io_context ioc;
52 : stream_file f(ioc);
53 : if (auto ec = f.open("data.bin", file_base::read_only))
54 : co_return; // report the error
55 :
56 : char buf[4096];
57 : for (;;)
58 : {
59 : auto [ec, n] = co_await f.read_some(
60 : capy::mutable_buffer(buf, sizeof(buf)));
61 : if (ec == capy::cond::eof)
62 : break;
63 : if (ec)
64 : co_return;
65 : }
66 : @endcode
67 : */
68 : class BOOST_COROSIO_DECL stream_file : public io_stream
69 : {
70 : public:
71 : /** Platform-specific file implementation interface.
72 :
73 : Backends derive from this to provide file I/O.
74 : `read_some` and `write_some` are inherited from
75 : @ref io_stream::implementation.
76 : */
77 : struct implementation : io_stream::implementation
78 : {
79 : /// Return the platform file descriptor or handle.
80 : virtual native_handle_type native_handle() const noexcept = 0;
81 :
82 : /// Cancel pending asynchronous operations.
83 : virtual void cancel() noexcept = 0;
84 :
85 : /// Return the file size in bytes.
86 : virtual std::uint64_t size() const = 0;
87 :
88 : /// Resize the file to @p new_size bytes.
89 : virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
90 :
91 : /// Synchronize file data to stable storage.
92 : virtual std::error_code sync_data() noexcept = 0;
93 :
94 : /// Synchronize file data and metadata to stable storage.
95 : virtual std::error_code sync_all() noexcept = 0;
96 :
97 : /// Release ownership of the native handle.
98 : virtual native_handle_type release() = 0;
99 :
100 : /// Adopt an existing native handle.
101 : virtual std::error_code assign(native_handle_type handle) noexcept = 0;
102 :
103 : /** Move the file position.
104 :
105 : @param offset Signed offset from @p origin.
106 : @param origin The reference point for the seek.
107 : @return The error code and new absolute position.
108 : */
109 : virtual capy::io_result<std::uint64_t>
110 : seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
111 : };
112 :
113 : /** Destructor.
114 :
115 : Closes the file if open, cancelling any pending operations.
116 : */
117 : ~stream_file() override;
118 :
119 : /** Construct from an execution context.
120 :
121 : @param ctx The execution context that will own this file.
122 : */
123 : explicit stream_file(capy::execution_context& ctx);
124 :
125 : /** Construct from an executor.
126 :
127 : @param ex The executor whose context will own this file.
128 : */
129 : template<class Ex>
130 : requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
131 : capy::Executor<Ex>
132 HIT 2 : explicit stream_file(Ex const& ex) : stream_file(ex.context())
133 : {
134 2 : }
135 :
136 : /** Move constructor.
137 :
138 : Transfers ownership of the file resources.
139 : */
140 2 : stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
141 :
142 : /** Move assignment operator.
143 :
144 : Closes any existing file and transfers ownership.
145 : */
146 2 : stream_file& operator=(stream_file&& other) noexcept
147 : {
148 2 : if (this != &other)
149 : {
150 2 : close();
151 2 : h_ = std::move(other.h_);
152 : }
153 2 : return *this;
154 : }
155 :
156 : stream_file(stream_file const&) = delete;
157 : stream_file& operator=(stream_file const&) = delete;
158 :
159 : // read_some() inherited from io_read_stream
160 : // write_some() inherited from io_write_stream
161 :
162 : /** Open a file.
163 :
164 : Failures such as a missing file or insufficient permissions
165 : are expected runtime conditions and are reported through the
166 : returned error code. If the file is already open, it is
167 : closed first.
168 :
169 : @param path The filesystem path to open.
170 : @param mode Bitmask of @ref file_base::flags specifying
171 : access mode and creation behavior.
172 :
173 : @return The error code, empty on success.
174 : */
175 : [[nodiscard]] std::error_code open(
176 : std::filesystem::path const& path,
177 : file_base::flags mode = file_base::read_only) noexcept;
178 :
179 : /** Close the file.
180 :
181 : Releases file resources. Any pending operations complete
182 : with `errc::operation_canceled`.
183 : */
184 : void close() noexcept;
185 :
186 : /** Check if the file is open.
187 :
188 : @return `true` if the file is open and ready for I/O.
189 : */
190 304 : bool is_open() const noexcept
191 : {
192 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
193 : return h_ && get().native_handle() != ~native_handle_type(0);
194 : #else
195 304 : return h_ && get().native_handle() >= 0;
196 : #endif
197 : }
198 :
199 : /** Cancel pending asynchronous operations.
200 :
201 : All outstanding operations complete with
202 : `errc::operation_canceled`.
203 : */
204 : void cancel() noexcept;
205 :
206 : /** Get the native file descriptor or handle.
207 :
208 : @return The native handle, or -1/INVALID_HANDLE_VALUE
209 : if not open.
210 : */
211 : native_handle_type native_handle() const noexcept;
212 :
213 : /** Return the file size in bytes.
214 :
215 : @throws std::system_error If the file is not open, or if the
216 : underlying size query fails.
217 : */
218 : std::uint64_t size() const;
219 :
220 : /** Resize the file to @p new_size bytes.
221 :
222 : Failures such as insufficient disk space are reported
223 : through the returned error code. A closed file reports
224 : `errc::bad_file_descriptor`.
225 :
226 : @param new_size The new file size.
227 :
228 : @return The error code, empty on success.
229 : */
230 : [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
231 :
232 : /** Synchronize file data to stable storage.
233 :
234 : Write-back failures such as device I/O errors surface here
235 : and are reported through the returned error code. A closed
236 : file reports `errc::bad_file_descriptor`.
237 :
238 : @return The error code, empty on success.
239 : */
240 : [[nodiscard]] std::error_code sync_data() noexcept;
241 :
242 : /** Synchronize file data and metadata to stable storage.
243 :
244 : Write-back failures such as device I/O errors surface here
245 : and are reported through the returned error code. A closed
246 : file reports `errc::bad_file_descriptor`.
247 :
248 : @return The error code, empty on success.
249 : */
250 : [[nodiscard]] std::error_code sync_all() noexcept;
251 :
252 : /** Release ownership of the native handle.
253 :
254 : The file object becomes not-open. The caller is
255 : responsible for closing the returned handle.
256 :
257 : @return The native file descriptor or handle.
258 :
259 : @throws std::system_error `errc::bad_file_descriptor` if the
260 : file is not open.
261 : */
262 : native_handle_type release();
263 :
264 : /** Adopt an existing native handle.
265 :
266 : Closes any currently open file before adopting.
267 : The file object takes ownership of the handle. Handles
268 : created elsewhere may be unsuitable for asynchronous I/O;
269 : such failures are reported through the returned error code.
270 :
271 : @param handle The native file descriptor or handle.
272 :
273 : @return The error code, empty on success.
274 : */
275 : [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
276 :
277 : /** Move the file position.
278 :
279 : Positions beyond the end of the file are allowed. A
280 : resulting negative position is reported through the error
281 : code, as offsets often originate from file contents. A
282 : closed file reports `errc::bad_file_descriptor`.
283 :
284 : @param offset Signed offset from @p origin.
285 : @param origin The reference point for the seek.
286 :
287 : @return The error code and new absolute position.
288 : */
289 : [[nodiscard]] capy::io_result<std::uint64_t>
290 : seek(std::int64_t offset,
291 : file_base::seek_basis origin = file_base::seek_set) noexcept;
292 :
293 : protected:
294 : /// Default-construct (for derived types that initialize io_object directly).
295 12 : stream_file() noexcept = default;
296 :
297 : /// Construct from a pre-built handle (for native_stream_file).
298 : explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
299 :
300 : private:
301 436 : inline implementation& get() const noexcept
302 : {
303 436 : return *static_cast<implementation*>(h_.get());
304 : }
305 : };
306 :
307 : } // namespace boost::corosio
308 :
309 : #endif // BOOST_COROSIO_STREAM_FILE_HPP
|