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_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 : /** Reads and writes a file sequentially, from a coroutine.
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 : /** Defines the file operations a platform backend implements.
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 :
71 : @return The current size of the file, in bytes.
72 :
73 : @throws std::system_error if the underlying size query fails.
74 : */
75 : virtual std::uint64_t size() const = 0;
76 :
77 : /** Resize the file to @p new_size bytes.
78 :
79 : @param new_size The requested size in bytes.
80 :
81 : @return The error code, empty on success.
82 : */
83 : virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
84 :
85 : /** Synchronize file data to stable storage.
86 :
87 : @return The error code, empty on success.
88 : */
89 : virtual std::error_code sync_data() noexcept = 0;
90 :
91 : /** Synchronize file data and metadata to stable storage.
92 :
93 : @return The error code, empty on success.
94 : */
95 : virtual std::error_code sync_all() noexcept = 0;
96 :
97 : /** Release ownership of the native handle.
98 :
99 : @return The native handle, which the caller now owns.
100 :
101 : @throws std::system_error if the file is not open.
102 : */
103 : virtual native_handle_type release() = 0;
104 :
105 : /** Adopt an existing native handle.
106 :
107 : @param handle The native handle to adopt. The implementation takes
108 : ownership and closes it.
109 :
110 : @return The error code, empty on success.
111 : */
112 : virtual std::error_code assign(native_handle_type handle) noexcept = 0;
113 :
114 : /** Move the file position.
115 :
116 : @param offset Signed offset from @p origin.
117 : @param origin The reference point for the seek.
118 : @return The error code and new absolute position.
119 : */
120 : virtual capy::io_result<std::uint64_t>
121 : seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
122 : };
123 :
124 : /** Closes the file if open, cancelling any pending operations.
125 : */
126 : ~stream_file() override;
127 :
128 : /** Construct from an execution context.
129 :
130 : @param ctx The execution context that owns this file.
131 : */
132 : explicit stream_file(capy::execution_context& ctx);
133 :
134 : /** Construct from an executor.
135 :
136 : @param ex The executor whose context owns this file.
137 : */
138 : template<class Ex>
139 : requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
140 : capy::Executor<Ex>
141 HIT 2 : explicit stream_file(Ex const& ex) : stream_file(ex.context())
142 : {
143 2 : }
144 :
145 : /** Transfers ownership of the file resources.
146 : */
147 2 : stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
148 :
149 : /** Closes any existing file and transfers ownership.
150 :
151 : @return Reference to this object.
152 : */
153 2 : stream_file& operator=(stream_file&& other) noexcept
154 : {
155 2 : if (this != &other)
156 : {
157 2 : close();
158 2 : h_ = std::move(other.h_);
159 : }
160 2 : return *this;
161 : }
162 :
163 : /// Copy construction is disabled; the handle is uniquely owned.
164 : stream_file(stream_file const&) = delete;
165 : /// Copy assignment is disabled; the handle is uniquely owned.
166 : stream_file& operator=(stream_file const&) = delete;
167 :
168 : // read_some() inherited from io_read_stream
169 : // write_some() inherited from io_write_stream
170 :
171 : /** Open a file.
172 :
173 : Failures such as a missing file or insufficient permissions
174 : are expected runtime conditions and are reported through the
175 : returned error code. If the file is already open, it is
176 : closed first.
177 :
178 : @param path The filesystem path to open.
179 : @param mode Bitmask of @ref file_base::flags specifying
180 : access mode and creation behavior.
181 :
182 : @return The error code, empty on success.
183 : */
184 : [[nodiscard]] std::error_code open(
185 : std::filesystem::path const& path,
186 : file_base::flags mode = file_base::read_only) noexcept;
187 :
188 : /** Close the file.
189 :
190 : Releases file resources. Pending operations complete through the
191 : same path as @ref cancel: one still in flight completes with
192 : `errc::operation_canceled`. An operation whose result is already
193 : decided reports that result.
194 : */
195 : void close() noexcept;
196 :
197 : /** Check if the file is open.
198 :
199 : @return `true` if the file is open and ready for I/O.
200 : */
201 809 : bool is_open() const noexcept
202 : {
203 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
204 : return h_ && get().native_handle() != ~native_handle_type(0);
205 : #else
206 809 : return h_ && get().native_handle() >= 0;
207 : #endif
208 : }
209 :
210 : /** Cancel pending asynchronous operations.
211 :
212 : Operations still in flight complete with
213 : `errc::operation_canceled`; an operation whose result is
214 : already decided reports that result.
215 : */
216 : void cancel() noexcept;
217 :
218 : /** Get the native file descriptor or handle.
219 :
220 : @return The native handle, or -1/INVALID_HANDLE_VALUE
221 : if not open.
222 : */
223 : native_handle_type native_handle() const noexcept;
224 :
225 : /** Return the file size in bytes.
226 :
227 : @return The file size in bytes.
228 :
229 : @throws std::system_error If the file is not open, or if the
230 : underlying size query fails.
231 : */
232 : std::uint64_t size() const;
233 :
234 : /** Resize the file to @p new_size bytes.
235 :
236 : Failures such as insufficient disk space are reported
237 : through the returned error code. A closed file reports
238 : `errc::bad_file_descriptor`.
239 :
240 : @param new_size The new file size.
241 :
242 : @return The error code, empty on success.
243 : */
244 : [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
245 :
246 : /** Synchronize file data to stable storage.
247 :
248 : Write-back failures such as device I/O errors surface here
249 : and are reported through the returned error code. A closed
250 : file reports `errc::bad_file_descriptor`.
251 :
252 : @return The error code, empty on success.
253 : */
254 : [[nodiscard]] std::error_code sync_data() noexcept;
255 :
256 : /** Synchronize file data and metadata to stable storage.
257 :
258 : Write-back failures such as device I/O errors surface here
259 : and are reported through the returned error code. A closed
260 : file reports `errc::bad_file_descriptor`.
261 :
262 : @return The error code, empty on success.
263 : */
264 : [[nodiscard]] std::error_code sync_all() noexcept;
265 :
266 : /** Release ownership of the native handle.
267 :
268 : The file object becomes not-open. The caller is
269 : responsible for closing the returned handle.
270 :
271 : @return The native file descriptor or handle.
272 :
273 : @throws std::system_error `errc::bad_file_descriptor` if the
274 : file is not open.
275 : */
276 : native_handle_type release();
277 :
278 : /** Adopt an existing native handle.
279 :
280 : Validation runs before anything is mutated or closed. On
281 : error the object still holds whatever file it held before,
282 : and the caller still owns @p handle. On success the object
283 : takes ownership of @p handle and closes any file it
284 : previously held. Handles created elsewhere may be unsuitable
285 : for asynchronous I/O; such failures are reported through the
286 : returned error code.
287 :
288 : @param handle The native file descriptor or handle.
289 :
290 : @return An error code describing the outcome. The codes
291 : that follow are those of the POSIX and io_uring
292 : backends. `errc::invalid_argument` if @p handle is the
293 : one this object already holds.
294 : `errc::bad_file_descriptor` if it is invalid.
295 : `errc::operation_not_supported` if it names something a
296 : file object cannot position. Otherwise, the `errno`
297 : reported by the kernel, or an empty code. The Windows
298 : backend validates nothing and reports the Win32 error
299 : from IOCP registration.
300 :
301 : @par Exception Safety
302 : Throws nothing. Strong guarantee.
303 :
304 : @note On POSIX, "something a file object cannot position"
305 : means, in practice, a pipe, a socket, or any other
306 : anonymous inode. Adopt those into a @ref posix_descriptor
307 : instead.
308 :
309 : @note The strong guarantee above holds on the POSIX and
310 : io_uring backends. On Windows (IOCP), a failed @p handle
311 : can still close the file this object held. That backend's
312 : file services are unified in a later stage, which is
313 : where this gap is closed.
314 :
315 : @see release
316 : */
317 : [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
318 :
319 : /** Move the file position.
320 :
321 : Positions beyond the end of the file are allowed. A
322 : resulting negative position is reported through the error
323 : code, as offsets often originate from file contents. A
324 : closed file reports `errc::bad_file_descriptor`.
325 :
326 : @param offset Signed offset from @p origin.
327 : @param origin The reference point for the seek.
328 :
329 : @return The error code and new absolute position.
330 : */
331 : [[nodiscard]] capy::io_result<std::uint64_t> seek(
332 : std::int64_t offset,
333 : file_base::seek_basis origin = file_base::seek_set) noexcept;
334 :
335 : protected:
336 : /// Default-construct (for derived types that initialize `io_object` directly).
337 16 : stream_file() noexcept = default;
338 :
339 : /** Construct from a pre-built handle (for `native_stream_file`).
340 :
341 : @param h The pre-built handle to adopt.
342 : */
343 : explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
344 :
345 : private:
346 1161 : inline implementation& get() const noexcept
347 : {
348 1161 : return *static_cast<implementation*>(h_.get());
349 : }
350 : };
351 :
352 : } // namespace boost::corosio
353 :
354 : #endif // BOOST_COROSIO_STREAM_FILE_HPP
|