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 495 : 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 495 : 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 16 : 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 731 : inline implementation& get() const noexcept
286 : {
287 731 : return *static_cast<implementation*>(h_.get());
288 : }
289 : };
290 :
291 : } // namespace boost::corosio
292 :
293 : #endif // BOOST_COROSIO_STREAM_FILE_HPP
|