include/boost/corosio/stream_file.hpp

100.0% Lines (13/0/13) 100.0% List of functions (6/0/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 /** 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 2x explicit stream_file(Ex const& ex) : stream_file(ex.context())
133 {
134 2x }
135
136 /** Move constructor.
137
138 Transfers ownership of the file resources.
139 */
140 2x 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 2x stream_file& operator=(stream_file&& other) noexcept
147 {
148 2x if (this != &other)
149 {
150 2x close();
151 2x h_ = std::move(other.h_);
152 }
153 2x 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 304x 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 304x 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 12x 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 436x inline implementation& get() const noexcept
302 {
303 436x return *static_cast<implementation*>(h_.get());
304 }
305 };
306
307 } // namespace boost::corosio
308
309 #endif // BOOST_COROSIO_STREAM_FILE_HPP
310