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 @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 495x 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 495x return h_ && get().native_handle() >= 0;
180 #endif
181 }
182
183 /** Cancel pending asynchronous operations.
184
185 All outstanding operations complete with
186 `errc::operation_canceled`.
187 */
188 void cancel() noexcept;
189
190 /** Get the native file descriptor or handle.
191
192 @return The native handle, or -1/INVALID_HANDLE_VALUE
193 if not open.
194 */
195 native_handle_type native_handle() const noexcept;
196
197 /** Return the file size in bytes.
198
199 @throws std::system_error If the file is not open, or if the
200 underlying size query fails.
201 */
202 std::uint64_t size() const;
203
204 /** Resize the file to @p new_size bytes.
205
206 Failures such as insufficient disk space are reported
207 through the returned error code. A closed file reports
208 `errc::bad_file_descriptor`.
209
210 @param new_size The new file size.
211
212 @return The error code, empty on success.
213 */
214 [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
215
216 /** Synchronize file data to stable storage.
217
218 Write-back failures such as device I/O errors surface here
219 and are reported through the returned error code. A closed
220 file reports `errc::bad_file_descriptor`.
221
222 @return The error code, empty on success.
223 */
224 [[nodiscard]] std::error_code sync_data() noexcept;
225
226 /** Synchronize file data and metadata to stable storage.
227
228 Write-back failures such as device I/O errors surface here
229 and are reported through the returned error code. A closed
230 file reports `errc::bad_file_descriptor`.
231
232 @return The error code, empty on success.
233 */
234 [[nodiscard]] std::error_code sync_all() noexcept;
235
236 /** Release ownership of the native handle.
237
238 The file object becomes not-open. The caller is
239 responsible for closing the returned handle.
240
241 @return The native file descriptor or handle.
242
243 @throws std::system_error `errc::bad_file_descriptor` if the
244 file is not open.
245 */
246 native_handle_type release();
247
248 /** Adopt an existing native handle.
249
250 Closes any currently open file before adopting.
251 The file object takes ownership of the handle. Handles
252 created elsewhere may be unsuitable for asynchronous I/O;
253 such failures are reported through the returned error code.
254
255 @param handle The native file descriptor or handle.
256
257 @return The error code, empty on success.
258 */
259 [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
260
261 /** Move the file position.
262
263 Positions beyond the end of the file are allowed. A
264 resulting negative position is reported through the error
265 code, as offsets often originate from file contents. A
266 closed file reports `errc::bad_file_descriptor`.
267
268 @param offset Signed offset from @p origin.
269 @param origin The reference point for the seek.
270
271 @return The error code and new absolute position.
272 */
273 [[nodiscard]] capy::io_result<std::uint64_t> seek(
274 std::int64_t offset,
275 file_base::seek_basis origin = file_base::seek_set) noexcept;
276
277 protected:
278 /// Default-construct (for derived types that initialize io_object directly).
279 16x stream_file() noexcept = default;
280
281 /// Construct from a pre-built handle (for native_stream_file).
282 explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
283
284 private:
285 731x inline implementation& get() const noexcept
286 {
287 731x return *static_cast<implementation*>(h_.get());
288 }
289 };
290
291 } // namespace boost::corosio
292
293 #endif // BOOST_COROSIO_STREAM_FILE_HPP
294