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_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 : /** An asynchronous random-access file for coroutine I/O.
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. Multiple concurrent reads and writes
51 : are supported from coroutines sharing the same file object,
52 : but external synchronization is required for non-async
53 : operations (open, close, size, resize, etc.).
54 :
55 : @par Example
56 : @par !example random_access_file
57 : */
58 : class BOOST_COROSIO_DECL random_access_file : public io_object
59 : {
60 : public:
61 : /** Platform-specific random-access file implementation interface.
62 :
63 : Backends derive from this to provide offset-based file I/O.
64 : */
65 : struct implementation : io_object::implementation
66 : {
67 : /** Initiate a read at the given offset.
68 :
69 : @param offset Byte offset into the file.
70 : @param h Coroutine handle to resume on completion.
71 : @param ex Executor for dispatching the completion.
72 : @param buf The buffer to read into.
73 : @param token Stop token for cancellation.
74 : @param ec Output error code.
75 : @param bytes_out Output bytes transferred.
76 : @return Coroutine handle to resume immediately.
77 : */
78 : virtual std::coroutine_handle<> read_some_at(
79 : std::uint64_t offset,
80 : std::coroutine_handle<> h,
81 : capy::executor_ref ex,
82 : buffer_param buf,
83 : std::stop_token token,
84 : std::error_code* ec,
85 : std::size_t* bytes_out) = 0;
86 :
87 : /** Initiate a write at the given offset.
88 :
89 : @param offset Byte offset into the file.
90 : @param h Coroutine handle to resume on completion.
91 : @param ex Executor for dispatching the completion.
92 : @param buf The buffer to write from.
93 : @param token Stop token for cancellation.
94 : @param ec Output error code.
95 : @param bytes_out Output bytes transferred.
96 : @return Coroutine handle to resume immediately.
97 : */
98 : virtual std::coroutine_handle<> write_some_at(
99 : std::uint64_t offset,
100 : std::coroutine_handle<> h,
101 : capy::executor_ref ex,
102 : buffer_param buf,
103 : std::stop_token token,
104 : std::error_code* ec,
105 : std::size_t* bytes_out) = 0;
106 :
107 : /// Return the platform file descriptor or handle.
108 : virtual native_handle_type native_handle() const noexcept = 0;
109 :
110 : /// Cancel pending asynchronous operations.
111 : virtual void cancel() noexcept = 0;
112 :
113 : /// Return the file size in bytes.
114 : virtual std::uint64_t size() const = 0;
115 :
116 : /// Resize the file to @p new_size bytes.
117 : virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
118 :
119 : /// Synchronize file data to stable storage.
120 : virtual std::error_code sync_data() noexcept = 0;
121 :
122 : /// Synchronize file data and metadata to stable storage.
123 : virtual std::error_code sync_all() noexcept = 0;
124 :
125 : /// Release ownership of the native handle.
126 : virtual native_handle_type release() = 0;
127 :
128 : /// Adopt an existing native handle.
129 : virtual std::error_code assign(native_handle_type handle) noexcept = 0;
130 : };
131 :
132 : /** Awaitable for async read-at operations. */
133 : template<class MutableBufferSequence>
134 : struct read_some_at_awaitable
135 : : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>>
136 : {
137 : random_access_file& f_;
138 : std::uint64_t offset_;
139 : MutableBufferSequence buffers_;
140 :
141 HIT 343 : read_some_at_awaitable(
142 : random_access_file& f,
143 : std::uint64_t offset,
144 : MutableBufferSequence
145 : buffers) noexcept(std::
146 : is_nothrow_move_constructible_v<
147 : MutableBufferSequence>)
148 343 : : f_(f)
149 343 : , offset_(offset)
150 343 : , buffers_(std::move(buffers))
151 : {
152 343 : }
153 :
154 : std::coroutine_handle<>
155 337 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
156 : {
157 674 : return f_.get().read_some_at(
158 337 : offset_, h, ex, buffers_, this->token_, &this->ec_,
159 674 : &this->bytes_);
160 : }
161 : };
162 :
163 : /** Awaitable for async write-at operations. */
164 : template<class ConstBufferSequence>
165 : struct write_some_at_awaitable
166 : : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>>
167 : {
168 : random_access_file& f_;
169 : std::uint64_t offset_;
170 : ConstBufferSequence buffers_;
171 :
172 93 : write_some_at_awaitable(
173 : random_access_file& f,
174 : std::uint64_t offset,
175 : ConstBufferSequence
176 : buffers) noexcept(std::
177 : is_nothrow_move_constructible_v<
178 : ConstBufferSequence>)
179 93 : : f_(f)
180 93 : , offset_(offset)
181 93 : , buffers_(std::move(buffers))
182 : {
183 93 : }
184 :
185 : std::coroutine_handle<>
186 89 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
187 : {
188 178 : return f_.get().write_some_at(
189 89 : offset_, h, ex, buffers_, this->token_, &this->ec_,
190 178 : &this->bytes_);
191 : }
192 : };
193 :
194 : public:
195 : /** Destructor.
196 :
197 : Closes the file if open, cancelling any pending operations.
198 : */
199 : ~random_access_file() override;
200 :
201 : /** Construct from an execution context.
202 :
203 : @param ctx The execution context that will own this file.
204 : */
205 : explicit random_access_file(capy::execution_context& ctx);
206 :
207 : /** Construct from an executor.
208 :
209 : @param ex The executor whose context will own this file.
210 : */
211 : template<class Ex>
212 : requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
213 : capy::Executor<Ex>
214 2 : explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
215 : {
216 2 : }
217 :
218 : /** Move constructor. */
219 2 : random_access_file(random_access_file&& other) noexcept
220 2 : : io_object(std::move(other))
221 : {
222 2 : }
223 :
224 : /** Move assignment operator. */
225 : random_access_file& operator=(random_access_file&& other) noexcept
226 : {
227 : if (this != &other)
228 : {
229 : close();
230 : h_ = std::move(other.h_);
231 : }
232 : return *this;
233 : }
234 :
235 : random_access_file(random_access_file const&) = delete;
236 : random_access_file& operator=(random_access_file const&) = delete;
237 :
238 : /** Open a file.
239 :
240 : Failures such as a missing file or insufficient permissions
241 : are expected runtime conditions and are reported through the
242 : returned error code. If the file is already open, it is
243 : closed first.
244 :
245 : @param path The filesystem path to open.
246 : @param mode Bitmask of @ref file_base::flags specifying
247 : access mode and creation behavior.
248 :
249 : @return The error code, empty on success.
250 : */
251 : [[nodiscard]] std::error_code open(
252 : std::filesystem::path const& path,
253 : file_base::flags mode = file_base::read_only) noexcept;
254 :
255 : /** Close the file.
256 :
257 : Releases file resources. Any pending operations complete
258 : with `errc::operation_canceled`.
259 : */
260 : void close() noexcept;
261 :
262 : /** Check if the file is open. */
263 1082 : bool is_open() const noexcept
264 : {
265 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
266 : return h_ && get().native_handle() != ~native_handle_type(0);
267 : #else
268 1082 : return h_ && get().native_handle() >= 0;
269 : #endif
270 : }
271 :
272 : /** Read data at the given offset.
273 :
274 : @param offset Byte offset into the file.
275 : @param buffers The buffer sequence to read into.
276 :
277 : @return An awaitable yielding `(error_code, std::size_t)`.
278 :
279 : A closed file reports `errc::bad_file_descriptor`.
280 : */
281 : template<capy::MutableBufferSequence MB>
282 343 : [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
283 : {
284 343 : read_some_at_awaitable<MB> aw(*this, offset, buffers);
285 343 : if (!is_open())
286 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
287 343 : return aw;
288 : }
289 :
290 : /** Write data at the given offset.
291 :
292 : @param offset Byte offset into the file.
293 : @param buffers The buffer sequence to write from.
294 :
295 : @return An awaitable yielding `(error_code, std::size_t)`.
296 :
297 : A closed file reports `errc::bad_file_descriptor`.
298 : */
299 : template<capy::ConstBufferSequence CB>
300 93 : [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
301 : {
302 93 : write_some_at_awaitable<CB> aw(*this, offset, buffers);
303 93 : if (!is_open())
304 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
305 93 : return aw;
306 : }
307 :
308 : /** Cancel pending asynchronous operations. */
309 : void cancel() noexcept;
310 :
311 : /** Get the native file descriptor or handle. */
312 : native_handle_type native_handle() const noexcept;
313 :
314 : /** Return the file size in bytes.
315 :
316 : @throws std::system_error If the file is not open, or if the
317 : underlying size query fails.
318 : */
319 : std::uint64_t size() const;
320 :
321 : /** Resize the file to @p new_size bytes.
322 :
323 : Failures such as insufficient disk space are reported
324 : through the returned error code. A closed file reports
325 : `errc::bad_file_descriptor`.
326 :
327 : @param new_size The new file size.
328 :
329 : @return The error code, empty on success.
330 : */
331 : [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
332 :
333 : /** Synchronize file data to stable storage.
334 :
335 : Write-back failures such as device I/O errors surface here
336 : and are reported through the returned error code. A closed
337 : file reports `errc::bad_file_descriptor`.
338 :
339 : @return The error code, empty on success.
340 : */
341 : [[nodiscard]] std::error_code sync_data() noexcept;
342 :
343 : /** Synchronize file data and metadata to stable storage.
344 :
345 : Write-back failures such as device I/O errors surface here
346 : and are reported through the returned error code. A closed
347 : file reports `errc::bad_file_descriptor`.
348 :
349 : @return The error code, empty on success.
350 : */
351 : [[nodiscard]] std::error_code sync_all() noexcept;
352 :
353 : /** Release ownership of the native handle.
354 :
355 : The file object becomes not-open. The caller is
356 : responsible for closing the returned handle.
357 :
358 : @return The native file descriptor or handle.
359 :
360 : @throws std::system_error `errc::bad_file_descriptor` if the
361 : file is not open.
362 : */
363 : native_handle_type release();
364 :
365 : /** Adopt an existing native handle.
366 :
367 : Closes any currently open file before adopting.
368 : The file object takes ownership of the handle. Handles
369 : created elsewhere may be unsuitable for asynchronous I/O;
370 : such failures are reported through the returned error code.
371 :
372 : @param handle The native file descriptor or handle.
373 :
374 : @return The error code, empty on success.
375 : */
376 : [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
377 :
378 : protected:
379 : /// Construct from a pre-built handle (for native_random_access_file).
380 16 : explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
381 :
382 : private:
383 1765 : inline implementation& get() const noexcept
384 : {
385 1765 : return *static_cast<implementation*>(h_.get());
386 : }
387 : };
388 :
389 : } // namespace boost::corosio
390 :
391 : #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
|