include/boost/corosio/random_access_file.hpp

100.0% Lines (38 / 38) 100.0% Functions (11 / 11)
random_access_file.hpp
f(x) Functions (11)
Function Calls Lines Blocks
boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::read_some_at_awaitable(boost::corosio::random_access_file&, unsigned long, boost::capy::mutable_buffer) :172 592x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :186 583x 100.0% 75.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::write_some_at_awaitable(boost::corosio::random_access_file&, unsigned long, boost::capy::const_buffer) :211 201x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :225 195x 100.0% 75.0% boost::corosio::random_access_file::random_access_file<boost::corosio::io_context::executor_type>(boost::corosio::io_context::executor_type const&) :253 3x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::random_access_file&&) :258 11x 100.0% 100.0% boost::corosio::random_access_file::is_open() const :309 1938x 100.0% 100.0% auto boost::corosio::random_access_file::read_some_at<boost::capy::mutable_buffer>(unsigned long, boost::capy::mutable_buffer const&) :328 592x 100.0% 100.0% auto boost::corosio::random_access_file::write_some_at<boost::capy::const_buffer>(unsigned long, boost::capy::const_buffer const&) :346 201x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::io_object::handle) :469 24x 100.0% 100.0% boost::corosio::random_access_file::get() const :472 3176x 100.0% 100.0%
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_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/native_handle.hpp>
16 #include <boost/corosio/detail/buffer_param.hpp>
17 #include <boost/corosio/detail/op_base.hpp>
18 #include <boost/corosio/error.hpp>
19 #include <boost/corosio/file_base.hpp>
20 #include <boost/corosio/io/io_object.hpp>
21 #include <boost/capy/continuation.hpp>
22 #include <boost/capy/results.hpp>
23 #include <boost/capy/ex/executor_ref.hpp>
24 #include <boost/capy/ex/execution_context.hpp>
25 #include <boost/capy/ex/await_context.hpp>
26 #include <boost/capy/concept/executor.hpp>
27 #include <boost/capy/buffers.hpp>
28
29 #include <concepts>
30 #include <coroutine>
31 #include <cstddef>
32 #include <cstdint>
33 #include <type_traits>
34 #include <filesystem>
35 #include <stop_token>
36 #include <system_error>
37
38 namespace boost::corosio {
39
40 /** Reads and writes a file at arbitrary offsets, from a coroutine.
41
42 Provides asynchronous read and write operations at explicit
43 byte offsets, without maintaining an implicit file position.
44
45 On POSIX platforms, file I/O is dispatched to a thread pool
46 (blocking `preadv`/`pwritev`) with completion posted back to
47 the scheduler. On Windows, true overlapped I/O is used via IOCP.
48
49 On Windows, while the file is open, its handle is bound to the
50 execution context's completion port. Every overlapped call on the
51 handle queues a packet to that port. Do not issue your own
52 overlapped I/O on `native_handle()` (`DeviceIoControl`,
53 `ReadFile`) unless the `OVERLAPPED`'s `hEvent` has its low-order
54 bit set, which suppresses the packet.
55
56 @par Thread Safety
57 Distinct objects: Safe.@n
58 Shared objects: Unsafe. Coroutines sharing the same file object may
59 run multiple concurrent reads and writes. Non-async operations such as open, close, size, and resize require external synchronization.
60
61 @par Example
62 @par !example random_access_file
63 */
64 class BOOST_COROSIO_DECL random_access_file : public io_object
65 {
66 public:
67 /** Declares the offset-based file operations a platform backend
68 must implement.
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 cont The awaiting coroutine's continuation. It must
78 stay valid until `cont.h` is resumed through @p ex.
79 @param ex Executor for dispatching the completion.
80 @param buf The buffer to read into.
81 @param token Stop token for cancellation.
82 @param ec Output error code.
83 @param bytes_out Output bytes transferred.
84 @return Coroutine handle to resume immediately.
85 */
86 virtual std::coroutine_handle<> read_some_at(
87 std::uint64_t offset,
88 capy::continuation& cont,
89 capy::executor_ref ex,
90 detail::buffer_param buf,
91 std::stop_token token,
92 std::error_code* ec,
93 std::size_t* bytes_out) = 0;
94
95 /** Initiate a write at the given offset.
96
97 @param offset Byte offset into the file.
98 @param cont The awaiting coroutine's continuation. It must
99 stay valid until `cont.h` is resumed through @p ex.
100 @param ex Executor for dispatching the completion.
101 @param buf The buffer to write from.
102 @param token Stop token for cancellation.
103 @param ec Output error code.
104 @param bytes_out Output bytes transferred.
105 @return Coroutine handle to resume immediately.
106 */
107 virtual std::coroutine_handle<> write_some_at(
108 std::uint64_t offset,
109 capy::continuation& cont,
110 capy::executor_ref ex,
111 detail::buffer_param buf,
112 std::stop_token token,
113 std::error_code* ec,
114 std::size_t* bytes_out) = 0;
115
116 /// Return the platform file descriptor or handle.
117 virtual native_handle_type native_handle() const noexcept = 0;
118
119 /// Cancel pending asynchronous operations.
120 virtual void cancel() noexcept = 0;
121
122 /// Return the file size in bytes.
123 virtual capy::results<std::uint64_t> size() const noexcept = 0;
124
125 /** Resize the file to @p new_size bytes.
126
127 @param new_size The requested size in bytes.
128
129 @return The error code, empty on success.
130 */
131 virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
132
133 /** Synchronize file data to stable storage.
134
135 @return The error code, empty on success.
136 */
137 virtual std::error_code sync_data() noexcept = 0;
138
139 /** Synchronize file data and metadata to stable storage.
140
141 @return The error code, empty on success.
142 */
143 virtual std::error_code sync_all() noexcept = 0;
144
145 /// Release ownership of the native handle.
146 virtual capy::results<native_handle_type> release() noexcept = 0;
147
148 /** Adopt an existing native handle.
149
150 @param handle The native handle to adopt. The implementation takes
151 ownership and closes it.
152
153 @return The error code, empty on success.
154 */
155 virtual std::error_code assign(native_handle_type handle) noexcept = 0;
156 };
157
158 /** Awaitable for async read-at operations. */
159 template<class MutableBufferSequence>
160 struct read_some_at_awaitable
161 : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>>
162 {
163 private:
164 friend random_access_file;
165 friend detail::bytes_op_base<
166 read_some_at_awaitable<MutableBufferSequence>>;
167
168 random_access_file& f_;
169 std::uint64_t offset_;
170 MutableBufferSequence buffers_;
171
172 592x read_some_at_awaitable(
173 random_access_file& f,
174 std::uint64_t offset,
175 MutableBufferSequence
176 buffers) noexcept(std::
177 is_nothrow_move_constructible_v<
178 MutableBufferSequence>)
179 592x : f_(f)
180 592x , offset_(offset)
181 592x , buffers_(std::move(buffers))
182 {
183 592x }
184
185 std::coroutine_handle<>
186 583x dispatch(capy::continuation& cont, capy::executor_ref ex) const
187 {
188 // The continuation lives in the awaiting frame, which stays
189 // put until resumption -- unlike the per-call op, which is
190 // freed before the coroutine runs.
191 1166x return f_.get().read_some_at(
192 583x offset_, cont, ex, buffers_, this->token_, &this->ec_,
193 1166x &this->bytes_);
194 }
195 };
196
197 /** Awaitable for async write-at operations. */
198 template<class ConstBufferSequence>
199 struct write_some_at_awaitable
200 : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>>
201 {
202 private:
203 friend random_access_file;
204 friend detail::bytes_op_base<
205 write_some_at_awaitable<ConstBufferSequence>>;
206
207 random_access_file& f_;
208 std::uint64_t offset_;
209 ConstBufferSequence buffers_;
210
211 201x write_some_at_awaitable(
212 random_access_file& f,
213 std::uint64_t offset,
214 ConstBufferSequence
215 buffers) noexcept(std::
216 is_nothrow_move_constructible_v<
217 ConstBufferSequence>)
218 201x : f_(f)
219 201x , offset_(offset)
220 201x , buffers_(std::move(buffers))
221 {
222 201x }
223
224 std::coroutine_handle<>
225 195x dispatch(capy::continuation& cont, capy::executor_ref ex) const
226 {
227 390x return f_.get().write_some_at(
228 195x offset_, cont, ex, buffers_, this->token_, &this->ec_,
229 390x &this->bytes_);
230 }
231 };
232
233 public:
234 /** Destructor.
235
236 Closes the file if open, cancelling any pending operations.
237 */
238 ~random_access_file() override;
239
240 /** Construct from an execution context.
241
242 @param ctx The execution context that owns this file.
243 */
244 explicit random_access_file(capy::execution_context& ctx);
245
246 /** Construct from an executor.
247
248 @param ex The executor whose context owns this file.
249 */
250 template<class Ex>
251 requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
252 capy::Executor<Ex>
253 3x explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
254 {
255 3x }
256
257 /** Move constructor. */
258 11x random_access_file(random_access_file&& other) noexcept
259 11x : io_object(std::move(other))
260 {
261 11x }
262
263 /** Move assignment operator. */
264 random_access_file& operator=(random_access_file&& other) noexcept
265 {
266 if (this != &other)
267 {
268 close();
269 h_ = std::move(other.h_);
270 }
271 return *this;
272 }
273
274 /// Copy construction is disabled; the handle is uniquely owned.
275 random_access_file(random_access_file const&) = delete;
276 /// Copy assignment is disabled; the handle is uniquely owned.
277 random_access_file& operator=(random_access_file const&) = delete;
278
279 /** Open a file.
280
281 Failures such as a missing file or insufficient permissions
282 are expected runtime conditions and are reported through the
283 returned error code. If the file is already open, it is
284 closed first.
285
286 @param path The filesystem path to open.
287 @param mode Bitmask of @ref file_base::flags specifying
288 access mode and creation behavior.
289
290 @return The error code, empty on success.
291 */
292 [[nodiscard]] std::error_code open(
293 std::filesystem::path const& path,
294 file_base::flags mode = file_base::read_only) noexcept;
295
296 /** Close the file.
297
298 Releases file resources. Pending operations complete through the
299 same path as @ref cancel: one still in flight completes with
300 `errc::operation_canceled`. An operation whose result is already
301 decided reports that result.
302 */
303 void close() noexcept;
304
305 /** Check if the file is open.
306
307 @return `true` if the file holds an open handle.
308 */
309 1938x bool is_open() const noexcept
310 {
311 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
312 return h_ && get().native_handle() != ~native_handle_type(0);
313 #else
314 1938x return h_ && get().native_handle() >= 0;
315 #endif
316 }
317
318 /** Read data at the given offset.
319
320 @param offset Byte offset into the file.
321 @param buffers The buffer sequence to read into.
322
323 @return An awaitable yielding `(error_code, std::size_t)`.
324
325 A closed file reports `errc::bad_file_descriptor`.
326 */
327 template<capy::MutableBufferSequence MB>
328 592x [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
329 {
330 592x read_some_at_awaitable<MB> aw(*this, offset, buffers);
331 592x if (!is_open())
332 3x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
333 592x return aw;
334 }
335
336 /** Write data at the given offset.
337
338 @param offset Byte offset into the file.
339 @param buffers The buffer sequence to write from.
340
341 @return An awaitable yielding `(error_code, std::size_t)`.
342
343 A closed file reports `errc::bad_file_descriptor`.
344 */
345 template<capy::ConstBufferSequence CB>
346 201x [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
347 {
348 201x write_some_at_awaitable<CB> aw(*this, offset, buffers);
349 201x if (!is_open())
350 3x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
351 201x return aw;
352 }
353
354 /** Cancel pending asynchronous operations. */
355 void cancel() noexcept;
356
357 /** Get the native file descriptor or handle. */
358 native_handle_type native_handle() const noexcept;
359
360 /** Return the file size in bytes.
361
362 A closed file reports `errc::bad_file_descriptor`. Other
363 failures carry the error code from the operating system. On
364 failure the size is zero.
365
366 @return The error code and the file size in bytes.
367 */
368 [[nodiscard]] capy::results<std::uint64_t> size() const noexcept;
369
370 /** Resize the file to @p new_size bytes.
371
372 Failures such as insufficient disk space are reported
373 through the returned error code. A closed file reports
374 `errc::bad_file_descriptor`.
375
376 @param new_size The new file size.
377
378 @return The error code, empty on success.
379 */
380 [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
381
382 /** Synchronize file data to stable storage.
383
384 Write-back failures such as device I/O errors surface here
385 and are reported through the returned error code. A closed
386 file reports `errc::bad_file_descriptor`.
387
388 @return The error code, empty on success.
389 */
390 [[nodiscard]] std::error_code sync_data() noexcept;
391
392 /** Synchronize file data and metadata to stable storage.
393
394 Write-back failures such as device I/O errors surface here
395 and are reported through the returned error code. A closed
396 file reports `errc::bad_file_descriptor`.
397
398 @return The error code, empty on success.
399 */
400 [[nodiscard]] std::error_code sync_all() noexcept;
401
402 /** Release ownership of the native handle.
403
404 On success the file object becomes not-open, and the caller
405 is responsible for closing the returned handle.
406
407 `release()` cancels pending operations first. It reports
408 `errc::device_or_resource_busy` if one is still in flight on
409 Windows. Where file I/O runs on a thread pool, it reports the
410 same while a worker thread is already performing one. Call
411 `release()` again once the cancelled operations have completed. On
412 Windows it reports `errc::operation_not_supported` if the
413 handle cannot be detached from the execution context's
414 completion port. Detaching requires Windows 8.1 or later.
415
416 A closed file reports `errc::bad_file_descriptor`. On
417 failure the object keeps the handle and the returned handle
418 is the invalid sentinel ( -1 or `INVALID_HANDLE_VALUE` ), never
419 zero.
420
421 @return The error code and the native file descriptor or handle.
422 */
423 [[nodiscard]] capy::results<native_handle_type> release() noexcept;
424
425 /** Adopt an existing native handle.
426
427 The object must be closed. To replace a held file, `close()`
428 or `release()` it first. On success the object takes
429 ownership of @p handle. Handles created elsewhere may be
430 unsuitable for asynchronous I/O. `assign()` reports most such
431 failures through the returned error code.
432
433 @param handle The native file descriptor or handle.
434
435 @return An error code describing the outcome.
436 `error::already_open` if this object is open.
437 `errc::bad_file_descriptor` if @p handle is invalid.
438 `errc::operation_not_supported` if a file object cannot
439 use it. On Windows, the rejected handles are a pipe, a
440 socket, a console, a directory, a handle opened without
441 `FILE_FLAG_OVERLAPPED`, or one
442 already in skip-completion-port-on-success mode. On
443 Windows, `errc::invalid_argument` when @p handle is
444 bound to another completion port. Any other failure is
445 the code reported by the system. Otherwise, the code is
446 empty.
447
448 @par Exception Safety
449 Throws nothing. On failure the object is unchanged and the
450 caller still owns @p handle.
451
452 @note On POSIX, the rejected descriptors are, in practice, a
453 directory, a pipe, a socket, or any other anonymous inode.
454 Adopt a pipe, a socket, or an anonymous inode into a
455 @ref posix_stream_descriptor instead. `assign()` accepts a
456 non-seekable character device such as a tty. Its first
457 read or write then fails with `ESPIPE` on the epoll,
458 kqueue, and select I/O backends.
459
460 @see @ref release
461 */
462 [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
463
464 protected:
465 /** Construct from a pre-built handle (for `native_random_access_file`).
466
467 @param h The pre-built handle to adopt.
468 */
469 24x explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
470
471 private:
472 3176x inline implementation& get() const noexcept
473 {
474 3176x return *static_cast<implementation*>(h_.get());
475 }
476 };
477
478 } // namespace boost::corosio
479
480 #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
481