100.00% Lines (13/13) 100.00% Functions (6/6)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_STREAM_FILE_HPP 10   #ifndef BOOST_COROSIO_STREAM_FILE_HPP
11   #define BOOST_COROSIO_STREAM_FILE_HPP 11   #define BOOST_COROSIO_STREAM_FILE_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
16   #include <boost/corosio/detail/native_handle.hpp> 16   #include <boost/corosio/detail/native_handle.hpp>
17   #include <boost/corosio/file_base.hpp> 17   #include <boost/corosio/file_base.hpp>
18   #include <boost/corosio/io/io_stream.hpp> 18   #include <boost/corosio/io/io_stream.hpp>
19   #include <boost/capy/ex/execution_context.hpp> 19   #include <boost/capy/ex/execution_context.hpp>
20   #include <boost/capy/concept/executor.hpp> 20   #include <boost/capy/concept/executor.hpp>
21   #include <boost/capy/io_result.hpp> 21   #include <boost/capy/io_result.hpp>
22   22  
23   #include <concepts> 23   #include <concepts>
24   #include <cstdint> 24   #include <cstdint>
25   #include <filesystem> 25   #include <filesystem>
26   #include <system_error> 26   #include <system_error>
27   27  
28   namespace boost::corosio { 28   namespace boost::corosio {
29   29  
30 - /** An asynchronous sequential file for coroutine I/O. 30 + /** Reads and writes a file sequentially, from a coroutine.
31   31  
32   Provides asynchronous read and write operations on a regular 32   Provides asynchronous read and write operations on a regular
33   file with an implicit position that advances after each 33   file with an implicit position that advances after each
34   operation. 34   operation.
35   35  
36   Inherits from @ref io_stream, so `read_some` and `write_some` 36   Inherits from @ref io_stream, so `read_some` and `write_some`
37   are available and work with any algorithm that accepts an 37   are available and work with any algorithm that accepts an
38   `io_stream&`. 38   `io_stream&`.
39   39  
40   On POSIX platforms, file I/O is dispatched to a thread pool 40   On POSIX platforms, file I/O is dispatched to a thread pool
41   (blocking `preadv`/`pwritev`) with completion posted back to 41   (blocking `preadv`/`pwritev`) with completion posted back to
42   the scheduler. On Windows, true overlapped I/O is used via IOCP. 42   the scheduler. On Windows, true overlapped I/O is used via IOCP.
43   43  
44   @par Thread Safety 44   @par Thread Safety
45   Distinct objects: Safe.@n 45   Distinct objects: Safe.@n
46   Shared objects: Unsafe. Only one asynchronous operation 46   Shared objects: Unsafe. Only one asynchronous operation
47   may be in flight at a time. 47   may be in flight at a time.
48   48  
49   @par Example 49   @par Example
50   @par !example stream_file 50   @par !example stream_file
51   */ 51   */
52   class BOOST_COROSIO_DECL stream_file : public io_stream 52   class BOOST_COROSIO_DECL stream_file : public io_stream
53   { 53   {
54   public: 54   public:
55 - /** Platform-specific file implementation interface. 55 + /** Defines the file operations a platform backend implements.
56   56  
57   Backends derive from this to provide file I/O. 57   Backends derive from this to provide file I/O.
58   `read_some` and `write_some` are inherited from 58   `read_some` and `write_some` are inherited from
59   @ref io_stream::implementation. 59   @ref io_stream::implementation.
60   */ 60   */
61   struct implementation : io_stream::implementation 61   struct implementation : io_stream::implementation
62   { 62   {
63   /// Return the platform file descriptor or handle. 63   /// Return the platform file descriptor or handle.
64   virtual native_handle_type native_handle() const noexcept = 0; 64   virtual native_handle_type native_handle() const noexcept = 0;
65   65  
66   /// Cancel pending asynchronous operations. 66   /// Cancel pending asynchronous operations.
67   virtual void cancel() noexcept = 0; 67   virtual void cancel() noexcept = 0;
68   68  
69 - /// Return the file size in bytes. 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 + */
70   virtual std::uint64_t size() const = 0; 75   virtual std::uint64_t size() const = 0;
71   76  
72 - /// Resize the file to @p new_size bytes. 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 + */
73   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; 83   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
74   84  
75 - /// Synchronize file data to stable storage. 85 + /** Synchronize file data to stable storage.
  86 +
  87 + @return The error code, empty on success.
  88 + */
76   virtual std::error_code sync_data() noexcept = 0; 89   virtual std::error_code sync_data() noexcept = 0;
77   90  
78 - /// Synchronize file data and metadata to stable storage. 91 + /** Synchronize file data and metadata to stable storage.
  92 +
  93 + @return The error code, empty on success.
  94 + */
79   virtual std::error_code sync_all() noexcept = 0; 95   virtual std::error_code sync_all() noexcept = 0;
80   96  
81 - /// Release ownership of the native handle. 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 + */
82   virtual native_handle_type release() = 0; 103   virtual native_handle_type release() = 0;
83   104  
84 - /// Adopt an existing native handle. 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 + */
85   virtual std::error_code assign(native_handle_type handle) noexcept = 0; 112   virtual std::error_code assign(native_handle_type handle) noexcept = 0;
86   113  
87   /** Move the file position. 114   /** Move the file position.
88   115  
89   @param offset Signed offset from @p origin. 116   @param offset Signed offset from @p origin.
90   @param origin The reference point for the seek. 117   @param origin The reference point for the seek.
91   @return The error code and new absolute position. 118   @return The error code and new absolute position.
92   */ 119   */
93   virtual capy::io_result<std::uint64_t> 120   virtual capy::io_result<std::uint64_t>
94   seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0; 121   seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
95   }; 122   };
96   123  
97 - /** Destructor. 124 + /** Closes the file if open, cancelling any pending operations.
98 -  
99 - Closes the file if open, cancelling any pending operations.  
100   */ 125   */
101   ~stream_file() override; 126   ~stream_file() override;
102   127  
103   /** Construct from an execution context. 128   /** Construct from an execution context.
104   129  
105 - @param ctx The execution context that will own this file. 130 + @param ctx The execution context that owns this file.
106   */ 131   */
107   explicit stream_file(capy::execution_context& ctx); 132   explicit stream_file(capy::execution_context& ctx);
108   133  
109   /** Construct from an executor. 134   /** Construct from an executor.
110   135  
111 - @param ex The executor whose context will own this file. 136 + @param ex The executor whose context owns this file.
112   */ 137   */
113   template<class Ex> 138   template<class Ex>
114   requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) && 139   requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
115   capy::Executor<Ex> 140   capy::Executor<Ex>
HITCBC 116   2 explicit stream_file(Ex const& ex) : stream_file(ex.context()) 141   2 explicit stream_file(Ex const& ex) : stream_file(ex.context())
117   { 142   {
HITCBC 118   2 } 143   2 }
119   144  
120 - /** Move constructor. 145 + /** Transfers ownership of the file resources.
121 -  
122 - Transfers ownership of the file resources.  
123   */ 146   */
HITCBC 124   2 stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {} 147   2 stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
125   148  
126 - /** Move assignment operator. 149 + /** Closes any existing file and transfers ownership.
127   150  
128 - Closes any existing file and transfers ownership. 151 + @return Reference to this object.
129   */ 152   */
HITCBC 130   2 stream_file& operator=(stream_file&& other) noexcept 153   2 stream_file& operator=(stream_file&& other) noexcept
131   { 154   {
HITCBC 132   2 if (this != &other) 155   2 if (this != &other)
133   { 156   {
HITCBC 134   2 close(); 157   2 close();
HITCBC 135   2 h_ = std::move(other.h_); 158   2 h_ = std::move(other.h_);
136   } 159   }
HITCBC 137   2 return *this; 160   2 return *this;
138   } 161   }
139   162  
140 - stream_file(stream_file const&) = delete; 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.
141   stream_file& operator=(stream_file const&) = delete; 166   stream_file& operator=(stream_file const&) = delete;
142   167  
143   // read_some() inherited from io_read_stream 168   // read_some() inherited from io_read_stream
144   // write_some() inherited from io_write_stream 169   // write_some() inherited from io_write_stream
145   170  
146   /** Open a file. 171   /** Open a file.
147   172  
148   Failures such as a missing file or insufficient permissions 173   Failures such as a missing file or insufficient permissions
149   are expected runtime conditions and are reported through the 174   are expected runtime conditions and are reported through the
150   returned error code. If the file is already open, it is 175   returned error code. If the file is already open, it is
151   closed first. 176   closed first.
152   177  
153   @param path The filesystem path to open. 178   @param path The filesystem path to open.
154   @param mode Bitmask of @ref file_base::flags specifying 179   @param mode Bitmask of @ref file_base::flags specifying
155   access mode and creation behavior. 180   access mode and creation behavior.
156   181  
157   @return The error code, empty on success. 182   @return The error code, empty on success.
158   */ 183   */
159   [[nodiscard]] std::error_code open( 184   [[nodiscard]] std::error_code open(
160   std::filesystem::path const& path, 185   std::filesystem::path const& path,
161   file_base::flags mode = file_base::read_only) noexcept; 186   file_base::flags mode = file_base::read_only) noexcept;
162   187  
163   /** Close the file. 188   /** Close the file.
164   189  
165 - Releases file resources. Any pending operations complete 190 + Releases file resources. Pending operations complete through the
166 - with `errc::operation_canceled`. 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.
167   */ 194   */
168   void close() noexcept; 195   void close() noexcept;
169   196  
170   /** Check if the file is open. 197   /** Check if the file is open.
171   198  
172   @return `true` if the file is open and ready for I/O. 199   @return `true` if the file is open and ready for I/O.
173   */ 200   */
HITCBC 174   795 bool is_open() const noexcept 201   795 bool is_open() const noexcept
175   { 202   {
176   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 203   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
177   return h_ && get().native_handle() != ~native_handle_type(0); 204   return h_ && get().native_handle() != ~native_handle_type(0);
178   #else 205   #else
HITCBC 179   795 return h_ && get().native_handle() >= 0; 206   795 return h_ && get().native_handle() >= 0;
180   #endif 207   #endif
181   } 208   }
182   209  
183   /** Cancel pending asynchronous operations. 210   /** Cancel pending asynchronous operations.
184   211  
185   Operations still in flight complete with 212   Operations still in flight complete with
186   `errc::operation_canceled`; an operation whose result is 213   `errc::operation_canceled`; an operation whose result is
187   already decided reports that result. 214   already decided reports that result.
188   */ 215   */
189   void cancel() noexcept; 216   void cancel() noexcept;
190   217  
191   /** Get the native file descriptor or handle. 218   /** Get the native file descriptor or handle.
192   219  
193   @return The native handle, or -1/INVALID_HANDLE_VALUE 220   @return The native handle, or -1/INVALID_HANDLE_VALUE
194   if not open. 221   if not open.
195   */ 222   */
196   native_handle_type native_handle() const noexcept; 223   native_handle_type native_handle() const noexcept;
197   224  
198   /** Return the file size in bytes. 225   /** Return the file size in bytes.
199   226  
  227 + @return The file size in bytes.
  228 +
200   @throws std::system_error If the file is not open, or if the 229   @throws std::system_error If the file is not open, or if the
201   underlying size query fails. 230   underlying size query fails.
202   */ 231   */
203   std::uint64_t size() const; 232   std::uint64_t size() const;
204   233  
205   /** Resize the file to @p new_size bytes. 234   /** Resize the file to @p new_size bytes.
206   235  
207   Failures such as insufficient disk space are reported 236   Failures such as insufficient disk space are reported
208   through the returned error code. A closed file reports 237   through the returned error code. A closed file reports
209   `errc::bad_file_descriptor`. 238   `errc::bad_file_descriptor`.
210   239  
211   @param new_size The new file size. 240   @param new_size The new file size.
212   241  
213   @return The error code, empty on success. 242   @return The error code, empty on success.
214   */ 243   */
215   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; 244   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
216   245  
217   /** Synchronize file data to stable storage. 246   /** Synchronize file data to stable storage.
218   247  
219   Write-back failures such as device I/O errors surface here 248   Write-back failures such as device I/O errors surface here
220   and are reported through the returned error code. A closed 249   and are reported through the returned error code. A closed
221   file reports `errc::bad_file_descriptor`. 250   file reports `errc::bad_file_descriptor`.
222   251  
223   @return The error code, empty on success. 252   @return The error code, empty on success.
224   */ 253   */
225   [[nodiscard]] std::error_code sync_data() noexcept; 254   [[nodiscard]] std::error_code sync_data() noexcept;
226   255  
227   /** Synchronize file data and metadata to stable storage. 256   /** Synchronize file data and metadata to stable storage.
228   257  
229   Write-back failures such as device I/O errors surface here 258   Write-back failures such as device I/O errors surface here
230   and are reported through the returned error code. A closed 259   and are reported through the returned error code. A closed
231   file reports `errc::bad_file_descriptor`. 260   file reports `errc::bad_file_descriptor`.
232   261  
233   @return The error code, empty on success. 262   @return The error code, empty on success.
234   */ 263   */
235   [[nodiscard]] std::error_code sync_all() noexcept; 264   [[nodiscard]] std::error_code sync_all() noexcept;
236   265  
237   /** Release ownership of the native handle. 266   /** Release ownership of the native handle.
238   267  
239   The file object becomes not-open. The caller is 268   The file object becomes not-open. The caller is
240   responsible for closing the returned handle. 269   responsible for closing the returned handle.
241   270  
242   @return The native file descriptor or handle. 271   @return The native file descriptor or handle.
243   272  
244   @throws std::system_error `errc::bad_file_descriptor` if the 273   @throws std::system_error `errc::bad_file_descriptor` if the
245   file is not open. 274   file is not open.
246   */ 275   */
247   native_handle_type release(); 276   native_handle_type release();
248   277  
249   /** Adopt an existing native handle. 278   /** Adopt an existing native handle.
250   279  
251   Closes any currently open file before adopting. 280   Closes any currently open file before adopting.
252   The file object takes ownership of the handle. Handles 281   The file object takes ownership of the handle. Handles
253 - created elsewhere may be unsuitable for asynchronous I/O; 282 + created elsewhere may be unsuitable for asynchronous I/O.
254 - such failures are reported through the returned error code. 283 + Such failures are reported through the returned error code.
255   284  
256   @param handle The native file descriptor or handle. 285   @param handle The native file descriptor or handle.
257   286  
258   @return The error code, empty on success. 287   @return The error code, empty on success.
259   */ 288   */
260   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; 289   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
261   290  
262   /** Move the file position. 291   /** Move the file position.
263   292  
264   Positions beyond the end of the file are allowed. A 293   Positions beyond the end of the file are allowed. A
265   resulting negative position is reported through the error 294   resulting negative position is reported through the error
266   code, as offsets often originate from file contents. A 295   code, as offsets often originate from file contents. A
267   closed file reports `errc::bad_file_descriptor`. 296   closed file reports `errc::bad_file_descriptor`.
268   297  
269   @param offset Signed offset from @p origin. 298   @param offset Signed offset from @p origin.
270   @param origin The reference point for the seek. 299   @param origin The reference point for the seek.
271   300  
272   @return The error code and new absolute position. 301   @return The error code and new absolute position.
273   */ 302   */
274   [[nodiscard]] capy::io_result<std::uint64_t> seek( 303   [[nodiscard]] capy::io_result<std::uint64_t> seek(
275   std::int64_t offset, 304   std::int64_t offset,
276   file_base::seek_basis origin = file_base::seek_set) noexcept; 305   file_base::seek_basis origin = file_base::seek_set) noexcept;
277   306  
278   protected: 307   protected:
279   /// Default-construct (for derived types that initialize io_object directly). 308   /// Default-construct (for derived types that initialize io_object directly).
HITCBC 280   16 stream_file() noexcept = default; 309   16 stream_file() noexcept = default;
281   310  
282 - /// Construct from a pre-built handle (for native_stream_file). 311 + /** Construct from a pre-built handle (for native_stream_file).
  312 +
  313 + @param h The pre-built handle to adopt.
  314 + */
283   explicit stream_file(handle h) noexcept : io_object(std::move(h)) {} 315   explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
284   316  
285   private: 317   private:
HITCBC 286   1131 inline implementation& get() const noexcept 318   1131 inline implementation& get() const noexcept
287   { 319   {
HITCBC 288   1131 return *static_cast<implementation*>(h_.get()); 320   1131 return *static_cast<implementation*>(h_.get());
289   } 321   }
290   }; 322   };
291   323  
292   } // namespace boost::corosio 324   } // namespace boost::corosio
293   325  
294   #endif // BOOST_COROSIO_STREAM_FILE_HPP 326   #endif // BOOST_COROSIO_STREAM_FILE_HPP