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 /** 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 @par !example stream_file
51 */
52 class BOOST_COROSIO_DECL stream_file : public io_stream
53 {
54 public:
55 /** Platform-specific file implementation interface.
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 virtual std::uint64_t size() const = 0;
71
72 /// Resize the file to @p new_size bytes.
73 virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
74
75 /// Synchronize file data to stable storage.
76 virtual std::error_code sync_data() noexcept = 0;
77
78 /// Synchronize file data and metadata to stable storage.
79 virtual std::error_code sync_all() noexcept = 0;
80
81 /// Release ownership of the native handle.
82 virtual native_handle_type release() = 0;
83
84 /// Adopt an existing native handle.
85 virtual std::error_code assign(native_handle_type handle) noexcept = 0;
86
87 /** Move the file position.
88
89 @param offset Signed offset from @p origin.
90 @param origin The reference point for the seek.
91 @return The error code and new absolute position.
92 */
93 virtual capy::io_result<std::uint64_t>
94 seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
95 };
96
97 /** Destructor.
98
99 Closes the file if open, cancelling any pending operations.
100 */
101 ~stream_file() override;
102
103 /** Construct from an execution context.
104
105 @param ctx The execution context that will own this file.
106 */
107 explicit stream_file(capy::execution_context& ctx);
108
109 /** Construct from an executor.
110
111 @param ex The executor whose context will own this file.
112 */
113 template<class Ex>
114 requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
115 capy::Executor<Ex>
116 2x explicit stream_file(Ex const& ex) : stream_file(ex.context())
117 {
118 2x }
119
120 /** Move constructor.
121
122 Transfers ownership of the file resources.
123 */
124 2x stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
125
126 /** Move assignment operator.
127
128 Closes any existing file and transfers ownership.
129 */
130 2x stream_file& operator=(stream_file&& other) noexcept
131 {
132 2x if (this != &other)
133 {
134 2x close();
135 2x h_ = std::move(other.h_);
136 }
137 2x return *this;
138 }
139
140 stream_file(stream_file const&) = delete;
141 stream_file& operator=(stream_file const&) = delete;
142
143 // read_some() inherited from io_read_stream
144 // write_some() inherited from io_write_stream
145
146 /** Open a file.
147
148 Failures such as a missing file or insufficient permissions
149 are expected runtime conditions and are reported through the
150 returned error code. If the file is already open, it is
151 closed first.
152
153 @param path The filesystem path to open.
154 @param mode Bitmask of @ref file_base::flags specifying
155 access mode and creation behavior.
156
157 @return The error code, empty on success.
158 */
159 [[nodiscard]] std::error_code open(
160 std::filesystem::path const& path,
161 file_base::flags mode = file_base::read_only) noexcept;
162
163 /** Close the file.
164
165 Releases file resources. Any pending operations complete
166 with `errc::operation_canceled`.
167 */
168 void close() noexcept;
169
170 /** Check if the file is open.
171
172 @return `true` if the file is open and ready for I/O.
173 */
174 795x bool is_open() const noexcept
175 {
176 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
177 return h_ && get().native_handle() != ~native_handle_type(0);
178 #else
179 795x return h_ && get().native_handle() >= 0;
180 #endif
181 }
182
183 /** Cancel pending asynchronous operations.
184
185 Operations still in flight complete with
186 `errc::operation_canceled`; an operation whose result is
187 already decided reports that result.
188 */
189 void cancel() noexcept;
190
191 /** Get the native file descriptor or handle.
192
193 @return The native handle, or -1/INVALID_HANDLE_VALUE
194 if not open.
195 */
196 native_handle_type native_handle() const noexcept;
197
198 /** Return the file size in bytes.
199
200 @throws std::system_error If the file is not open, or if the
201 underlying size query fails.
202 */
203 std::uint64_t size() const;
204
205 /** Resize the file to @p new_size bytes.
206
207 Failures such as insufficient disk space are reported
208 through the returned error code. A closed file reports
209 `errc::bad_file_descriptor`.
210
211 @param new_size The new file size.
212
213 @return The error code, empty on success.
214 */
215 [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
216
217 /** Synchronize file data to stable storage.
218
219 Write-back failures such as device I/O errors surface here
220 and are reported through the returned error code. A closed
221 file reports `errc::bad_file_descriptor`.
222
223 @return The error code, empty on success.
224 */
225 [[nodiscard]] std::error_code sync_data() noexcept;
226
227 /** Synchronize file data and metadata to stable storage.
228
229 Write-back failures such as device I/O errors surface here
230 and are reported through the returned error code. A closed
231 file reports `errc::bad_file_descriptor`.
232
233 @return The error code, empty on success.
234 */
235 [[nodiscard]] std::error_code sync_all() noexcept;
236
237 /** Release ownership of the native handle.
238
239 The file object becomes not-open. The caller is
240 responsible for closing the returned handle.
241
242 @return The native file descriptor or handle.
243
244 @throws std::system_error `errc::bad_file_descriptor` if the
245 file is not open.
246 */
247 native_handle_type release();
248
249 /** Adopt an existing native handle.
250
251 Closes any currently open file before adopting.
252 The file object takes ownership of the handle. Handles
253 created elsewhere may be unsuitable for asynchronous I/O;
254 such failures are reported through the returned error code.
255
256 @param handle The native file descriptor or handle.
257
258 @return The error code, empty on success.
259 */
260 [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
261
262 /** Move the file position.
263
264 Positions beyond the end of the file are allowed. A
265 resulting negative position is reported through the error
266 code, as offsets often originate from file contents. A
267 closed file reports `errc::bad_file_descriptor`.
268
269 @param offset Signed offset from @p origin.
270 @param origin The reference point for the seek.
271
272 @return The error code and new absolute position.
273 */
274 [[nodiscard]] capy::io_result<std::uint64_t> seek(
275 std::int64_t offset,
276 file_base::seek_basis origin = file_base::seek_set) noexcept;
277
278 protected:
279 /// Default-construct (for derived types that initialize io_object directly).
280 16x stream_file() noexcept = default;
281
282 /// Construct from a pre-built handle (for native_stream_file).
283 explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
284
285 private:
286 1131x inline implementation& get() const noexcept
287 {
288 1131x return *static_cast<implementation*>(h_.get());
289 }
290 };
291
292 } // namespace boost::corosio
293
294 #endif // BOOST_COROSIO_STREAM_FILE_HPP
295