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 : /** 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 HIT 2 : explicit stream_file(Ex const& ex) : stream_file(ex.context())
117 : {
118 2 : }
119 :
120 : /** Move constructor.
121 :
122 : Transfers ownership of the file resources.
123 : */
124 2 : 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 2 : stream_file& operator=(stream_file&& other) noexcept
131 : {
132 2 : if (this != &other)
133 : {
134 2 : close();
135 2 : h_ = std::move(other.h_);
136 : }
137 2 : 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 795 : 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 795 : 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 16 : 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 1131 : inline implementation& get() const noexcept
287 : {
288 1131 : return *static_cast<implementation*>(h_.get());
289 : }
290 : };
291 :
292 : } // namespace boost::corosio
293 :
294 : #endif // BOOST_COROSIO_STREAM_FILE_HPP
|