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