include/boost/corosio/stream_file.hpp

100.0% Lines (13 / 13) 100.0% Functions (6 / 6)
stream_file.hpp
f(x) Functions (6)
Line TLA Hits 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 /** Reads and writes a file sequentially, from a coroutine.
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 @par !example stream_file
51 */
52 class BOOST_COROSIO_DECL stream_file : public io_stream
53 {
54 public:
55 /** Defines the file operations a platform backend implements.
56
57 Backends derive from this to provide file I/O.
58 `read_some` and `write_some` are inherited from
59 @ref io_stream::implementation.
60 */
61 struct implementation : io_stream::implementation
62 {
63 /// Return the platform file descriptor or handle.
64 virtual native_handle_type native_handle() const noexcept = 0;
65
66 /// Cancel pending asynchronous operations.
67 virtual void cancel() noexcept = 0;
68
69 /** Return the file size in bytes.
70
71 @return The current size of the file, in bytes.
72
73 @throws std::system_error if the underlying size query fails.
74 */
75 virtual std::uint64_t size() const = 0;
76
77 /** Resize the file to @p new_size bytes.
78
79 @param new_size The requested size in bytes.
80
81 @return The error code, empty on success.
82 */
83 virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
84
85 /** Synchronize file data to stable storage.
86
87 @return The error code, empty on success.
88 */
89 virtual std::error_code sync_data() noexcept = 0;
90
91 /** Synchronize file data and metadata to stable storage.
92
93 @return The error code, empty on success.
94 */
95 virtual std::error_code sync_all() noexcept = 0;
96
97 /** Release ownership of the native handle.
98
99 @return The native handle, which the caller now owns.
100
101 @throws std::system_error if the file is not open.
102 */
103 virtual native_handle_type release() = 0;
104
105 /** Adopt an existing native handle.
106
107 @param handle The native handle to adopt. The implementation takes
108 ownership and closes it.
109
110 @return The error code, empty on success.
111 */
112 virtual std::error_code assign(native_handle_type handle) noexcept = 0;
113
114 /** Move the file position.
115
116 @param offset Signed offset from @p origin.
117 @param origin The reference point for the seek.
118 @return The error code and new absolute position.
119 */
120 virtual capy::io_result<std::uint64_t>
121 seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
122 };
123
124 /** Closes the file if open, cancelling any pending operations.
125 */
126 ~stream_file() override;
127
128 /** Construct from an execution context.
129
130 @param ctx The execution context that owns this file.
131 */
132 explicit stream_file(capy::execution_context& ctx);
133
134 /** Construct from an executor.
135
136 @param ex The executor whose context owns this file.
137 */
138 template<class Ex>
139 requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
140 capy::Executor<Ex>
141 2x explicit stream_file(Ex const& ex) : stream_file(ex.context())
142 {
143 2x }
144
145 /** Transfers ownership of the file resources.
146 */
147 2x stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
148
149 /** Closes any existing file and transfers ownership.
150
151 @return Reference to this object.
152 */
153 2x stream_file& operator=(stream_file&& other) noexcept
154 {
155 2x if (this != &other)
156 {
157 2x close();
158 2x h_ = std::move(other.h_);
159 }
160 2x return *this;
161 }
162
163 /// Copy construction is disabled; the handle is uniquely owned.
164 stream_file(stream_file const&) = delete;
165 /// Copy assignment is disabled; the handle is uniquely owned.
166 stream_file& operator=(stream_file const&) = delete;
167
168 // read_some() inherited from io_read_stream
169 // write_some() inherited from io_write_stream
170
171 /** Open a file.
172
173 Failures such as a missing file or insufficient permissions
174 are expected runtime conditions and are reported through the
175 returned error code. If the file is already open, it is
176 closed first.
177
178 @param path The filesystem path to open.
179 @param mode Bitmask of @ref file_base::flags specifying
180 access mode and creation behavior.
181
182 @return The error code, empty on success.
183 */
184 [[nodiscard]] std::error_code open(
185 std::filesystem::path const& path,
186 file_base::flags mode = file_base::read_only) noexcept;
187
188 /** Close the file.
189
190 Releases file resources. Pending operations complete through the
191 same path as @ref cancel: one still in flight completes with
192 `errc::operation_canceled`. An operation whose result is already
193 decided reports that result.
194 */
195 void close() noexcept;
196
197 /** Check if the file is open.
198
199 @return `true` if the file is open and ready for I/O.
200 */
201 809x bool is_open() const noexcept
202 {
203 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
204 return h_ && get().native_handle() != ~native_handle_type(0);
205 #else
206 809x return h_ && get().native_handle() >= 0;
207 #endif
208 }
209
210 /** Cancel pending asynchronous operations.
211
212 Operations still in flight complete with
213 `errc::operation_canceled`; an operation whose result is
214 already decided reports that result.
215 */
216 void cancel() noexcept;
217
218 /** Get the native file descriptor or handle.
219
220 @return The native handle, or -1/INVALID_HANDLE_VALUE
221 if not open.
222 */
223 native_handle_type native_handle() const noexcept;
224
225 /** Return the file size in bytes.
226
227 @return The file size in bytes.
228
229 @throws std::system_error If the file is not open, or if the
230 underlying size query fails.
231 */
232 std::uint64_t size() const;
233
234 /** Resize the file to @p new_size bytes.
235
236 Failures such as insufficient disk space are reported
237 through the returned error code. A closed file reports
238 `errc::bad_file_descriptor`.
239
240 @param new_size The new file size.
241
242 @return The error code, empty on success.
243 */
244 [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
245
246 /** Synchronize file data to stable storage.
247
248 Write-back failures such as device I/O errors surface here
249 and are reported through the returned error code. A closed
250 file reports `errc::bad_file_descriptor`.
251
252 @return The error code, empty on success.
253 */
254 [[nodiscard]] std::error_code sync_data() noexcept;
255
256 /** Synchronize file data and metadata to stable storage.
257
258 Write-back failures such as device I/O errors surface here
259 and are reported through the returned error code. A closed
260 file reports `errc::bad_file_descriptor`.
261
262 @return The error code, empty on success.
263 */
264 [[nodiscard]] std::error_code sync_all() noexcept;
265
266 /** Release ownership of the native handle.
267
268 The file object becomes not-open. The caller is
269 responsible for closing the returned handle.
270
271 @return The native file descriptor or handle.
272
273 @throws std::system_error `errc::bad_file_descriptor` if the
274 file is not open.
275 */
276 native_handle_type release();
277
278 /** Adopt an existing native handle.
279
280 Validation runs before anything is mutated or closed. On
281 error the object still holds whatever file it held before,
282 and the caller still owns @p handle. On success the object
283 takes ownership of @p handle and closes any file it
284 previously held. Handles created elsewhere may be unsuitable
285 for asynchronous I/O; such failures are reported through the
286 returned error code.
287
288 @param handle The native file descriptor or handle.
289
290 @return An error code describing the outcome. The codes
291 that follow are those of the POSIX and io_uring
292 backends. `errc::invalid_argument` if @p handle is the
293 one this object already holds.
294 `errc::bad_file_descriptor` if it is invalid.
295 `errc::operation_not_supported` if it names something a
296 file object cannot position. Otherwise, the `errno`
297 reported by the kernel, or an empty code. The Windows
298 backend validates nothing and reports the Win32 error
299 from IOCP registration.
300
301 @par Exception Safety
302 Throws nothing. Strong guarantee.
303
304 @note On POSIX, "something a file object cannot position"
305 means, in practice, a pipe, a socket, or any other
306 anonymous inode. Adopt those into a @ref posix_descriptor
307 instead.
308
309 @note The strong guarantee above holds on the POSIX and
310 io_uring backends. On Windows (IOCP), a failed @p handle
311 can still close the file this object held. That backend's
312 file services are unified in a later stage, which is
313 where this gap is closed.
314
315 @see release
316 */
317 [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
318
319 /** Move the file position.
320
321 Positions beyond the end of the file are allowed. A
322 resulting negative position is reported through the error
323 code, as offsets often originate from file contents. A
324 closed file reports `errc::bad_file_descriptor`.
325
326 @param offset Signed offset from @p origin.
327 @param origin The reference point for the seek.
328
329 @return The error code and new absolute position.
330 */
331 [[nodiscard]] capy::io_result<std::uint64_t> seek(
332 std::int64_t offset,
333 file_base::seek_basis origin = file_base::seek_set) noexcept;
334
335 protected:
336 /// Default-construct (for derived types that initialize `io_object` directly).
337 16x stream_file() noexcept = default;
338
339 /** Construct from a pre-built handle (for `native_stream_file`).
340
341 @param h The pre-built handle to adopt.
342 */
343 explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
344
345 private:
346 1161x inline implementation& get() const noexcept
347 {
348 1161x return *static_cast<implementation*>(h_.get());
349 }
350 };
351
352 } // namespace boost::corosio
353
354 #endif // BOOST_COROSIO_STREAM_FILE_HPP
355