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   /** An asynchronous sequential file for coroutine I/O.
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   @code 50   @code
51   io_context ioc; 51   io_context ioc;
52   stream_file f(ioc); 52   stream_file f(ioc);
53   if (auto ec = f.open("data.bin", file_base::read_only)) 53   if (auto ec = f.open("data.bin", file_base::read_only))
54   co_return; // report the error 54   co_return; // report the error
55   55  
56   char buf[4096]; 56   char buf[4096];
57   for (;;) 57   for (;;)
58   { 58   {
59   auto [ec, n] = co_await f.read_some( 59   auto [ec, n] = co_await f.read_some(
60   capy::mutable_buffer(buf, sizeof(buf))); 60   capy::mutable_buffer(buf, sizeof(buf)));
61   if (ec == capy::cond::eof) 61   if (ec == capy::cond::eof)
62   break; 62   break;
63   if (ec) 63   if (ec)
64   co_return; 64   co_return;
65   } 65   }
66   @endcode 66   @endcode
67   */ 67   */
68   class BOOST_COROSIO_DECL stream_file : public io_stream 68   class BOOST_COROSIO_DECL stream_file : public io_stream
69   { 69   {
70   public: 70   public:
71   /** Platform-specific file implementation interface. 71   /** Platform-specific file implementation interface.
72   72  
73   Backends derive from this to provide file I/O. 73   Backends derive from this to provide file I/O.
74   `read_some` and `write_some` are inherited from 74   `read_some` and `write_some` are inherited from
75   @ref io_stream::implementation. 75   @ref io_stream::implementation.
76   */ 76   */
77   struct implementation : io_stream::implementation 77   struct implementation : io_stream::implementation
78   { 78   {
79   /// Return the platform file descriptor or handle. 79   /// Return the platform file descriptor or handle.
80   virtual native_handle_type native_handle() const noexcept = 0; 80   virtual native_handle_type native_handle() const noexcept = 0;
81   81  
82   /// Cancel pending asynchronous operations. 82   /// Cancel pending asynchronous operations.
83   virtual void cancel() noexcept = 0; 83   virtual void cancel() noexcept = 0;
84   84  
85   /// Return the file size in bytes. 85   /// Return the file size in bytes.
86   virtual std::uint64_t size() const = 0; 86   virtual std::uint64_t size() const = 0;
87   87  
88   /// Resize the file to @p new_size bytes. 88   /// Resize the file to @p new_size bytes.
89   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; 89   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
90   90  
91   /// Synchronize file data to stable storage. 91   /// Synchronize file data to stable storage.
92   virtual std::error_code sync_data() noexcept = 0; 92   virtual std::error_code sync_data() noexcept = 0;
93   93  
94   /// Synchronize file data and metadata to stable storage. 94   /// Synchronize file data and metadata to stable storage.
95   virtual std::error_code sync_all() noexcept = 0; 95   virtual std::error_code sync_all() noexcept = 0;
96   96  
97   /// Release ownership of the native handle. 97   /// Release ownership of the native handle.
98   virtual native_handle_type release() = 0; 98   virtual native_handle_type release() = 0;
99   99  
100   /// Adopt an existing native handle. 100   /// Adopt an existing native handle.
101   virtual std::error_code assign(native_handle_type handle) noexcept = 0; 101   virtual std::error_code assign(native_handle_type handle) noexcept = 0;
102   102  
103   /** Move the file position. 103   /** Move the file position.
104   104  
105   @param offset Signed offset from @p origin. 105   @param offset Signed offset from @p origin.
106   @param origin The reference point for the seek. 106   @param origin The reference point for the seek.
107   @return The error code and new absolute position. 107   @return The error code and new absolute position.
108   */ 108   */
109   virtual capy::io_result<std::uint64_t> 109   virtual capy::io_result<std::uint64_t>
110   seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0; 110   seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
111   }; 111   };
112   112  
113   /** Destructor. 113   /** Destructor.
114   114  
115   Closes the file if open, cancelling any pending operations. 115   Closes the file if open, cancelling any pending operations.
116   */ 116   */
117   ~stream_file() override; 117   ~stream_file() override;
118   118  
119   /** Construct from an execution context. 119   /** Construct from an execution context.
120   120  
121   @param ctx The execution context that will own this file. 121   @param ctx The execution context that will own this file.
122   */ 122   */
123   explicit stream_file(capy::execution_context& ctx); 123   explicit stream_file(capy::execution_context& ctx);
124   124  
125   /** Construct from an executor. 125   /** Construct from an executor.
126   126  
127   @param ex The executor whose context will own this file. 127   @param ex The executor whose context will own this file.
128   */ 128   */
129   template<class Ex> 129   template<class Ex>
130   requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) && 130   requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
131   capy::Executor<Ex> 131   capy::Executor<Ex>
HITCBC 132   2 explicit stream_file(Ex const& ex) : stream_file(ex.context()) 132   2 explicit stream_file(Ex const& ex) : stream_file(ex.context())
133   { 133   {
HITCBC 134   2 } 134   2 }
135   135  
136   /** Move constructor. 136   /** Move constructor.
137   137  
138   Transfers ownership of the file resources. 138   Transfers ownership of the file resources.
139   */ 139   */
HITCBC 140   2 stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {} 140   2 stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
141   141  
142   /** Move assignment operator. 142   /** Move assignment operator.
143   143  
144   Closes any existing file and transfers ownership. 144   Closes any existing file and transfers ownership.
145   */ 145   */
HITCBC 146   2 stream_file& operator=(stream_file&& other) noexcept 146   2 stream_file& operator=(stream_file&& other) noexcept
147   { 147   {
HITCBC 148   2 if (this != &other) 148   2 if (this != &other)
149   { 149   {
HITCBC 150   2 close(); 150   2 close();
HITCBC 151   2 h_ = std::move(other.h_); 151   2 h_ = std::move(other.h_);
152   } 152   }
HITCBC 153   2 return *this; 153   2 return *this;
154   } 154   }
155   155  
156   stream_file(stream_file const&) = delete; 156   stream_file(stream_file const&) = delete;
157   stream_file& operator=(stream_file const&) = delete; 157   stream_file& operator=(stream_file const&) = delete;
158   158  
159   // read_some() inherited from io_read_stream 159   // read_some() inherited from io_read_stream
160   // write_some() inherited from io_write_stream 160   // write_some() inherited from io_write_stream
161   161  
162   /** Open a file. 162   /** Open a file.
163   163  
164   Failures such as a missing file or insufficient permissions 164   Failures such as a missing file or insufficient permissions
165   are expected runtime conditions and are reported through the 165   are expected runtime conditions and are reported through the
166   returned error code. If the file is already open, it is 166   returned error code. If the file is already open, it is
167   closed first. 167   closed first.
168   168  
169   @param path The filesystem path to open. 169   @param path The filesystem path to open.
170   @param mode Bitmask of @ref file_base::flags specifying 170   @param mode Bitmask of @ref file_base::flags specifying
171   access mode and creation behavior. 171   access mode and creation behavior.
172   172  
173   @return The error code, empty on success. 173   @return The error code, empty on success.
174   */ 174   */
175   [[nodiscard]] std::error_code open( 175   [[nodiscard]] std::error_code open(
176   std::filesystem::path const& path, 176   std::filesystem::path const& path,
177   file_base::flags mode = file_base::read_only) noexcept; 177   file_base::flags mode = file_base::read_only) noexcept;
178   178  
179   /** Close the file. 179   /** Close the file.
180   180  
181   Releases file resources. Any pending operations complete 181   Releases file resources. Any pending operations complete
182   with `errc::operation_canceled`. 182   with `errc::operation_canceled`.
183   */ 183   */
184   void close() noexcept; 184   void close() noexcept;
185   185  
186   /** Check if the file is open. 186   /** Check if the file is open.
187   187  
188   @return `true` if the file is open and ready for I/O. 188   @return `true` if the file is open and ready for I/O.
189   */ 189   */
HITCBC 190   304 bool is_open() const noexcept 190   395 bool is_open() const noexcept
191   { 191   {
192   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 192   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
193   return h_ && get().native_handle() != ~native_handle_type(0); 193   return h_ && get().native_handle() != ~native_handle_type(0);
194   #else 194   #else
HITCBC 195   304 return h_ && get().native_handle() >= 0; 195   395 return h_ && get().native_handle() >= 0;
196   #endif 196   #endif
197   } 197   }
198   198  
199   /** Cancel pending asynchronous operations. 199   /** Cancel pending asynchronous operations.
200   200  
201   All outstanding operations complete with 201   All outstanding operations complete with
202   `errc::operation_canceled`. 202   `errc::operation_canceled`.
203   */ 203   */
204   void cancel() noexcept; 204   void cancel() noexcept;
205   205  
206   /** Get the native file descriptor or handle. 206   /** Get the native file descriptor or handle.
207   207  
208   @return The native handle, or -1/INVALID_HANDLE_VALUE 208   @return The native handle, or -1/INVALID_HANDLE_VALUE
209   if not open. 209   if not open.
210   */ 210   */
211   native_handle_type native_handle() const noexcept; 211   native_handle_type native_handle() const noexcept;
212   212  
213   /** Return the file size in bytes. 213   /** Return the file size in bytes.
214   214  
215   @throws std::system_error If the file is not open, or if the 215   @throws std::system_error If the file is not open, or if the
216   underlying size query fails. 216   underlying size query fails.
217   */ 217   */
218   std::uint64_t size() const; 218   std::uint64_t size() const;
219   219  
220   /** Resize the file to @p new_size bytes. 220   /** Resize the file to @p new_size bytes.
221   221  
222   Failures such as insufficient disk space are reported 222   Failures such as insufficient disk space are reported
223   through the returned error code. A closed file reports 223   through the returned error code. A closed file reports
224   `errc::bad_file_descriptor`. 224   `errc::bad_file_descriptor`.
225   225  
226   @param new_size The new file size. 226   @param new_size The new file size.
227   227  
228   @return The error code, empty on success. 228   @return The error code, empty on success.
229   */ 229   */
230   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; 230   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
231   231  
232   /** Synchronize file data to stable storage. 232   /** Synchronize file data to stable storage.
233   233  
234   Write-back failures such as device I/O errors surface here 234   Write-back failures such as device I/O errors surface here
235   and are reported through the returned error code. A closed 235   and are reported through the returned error code. A closed
236   file reports `errc::bad_file_descriptor`. 236   file reports `errc::bad_file_descriptor`.
237   237  
238   @return The error code, empty on success. 238   @return The error code, empty on success.
239   */ 239   */
240   [[nodiscard]] std::error_code sync_data() noexcept; 240   [[nodiscard]] std::error_code sync_data() noexcept;
241   241  
242   /** Synchronize file data and metadata to stable storage. 242   /** Synchronize file data and metadata to stable storage.
243   243  
244   Write-back failures such as device I/O errors surface here 244   Write-back failures such as device I/O errors surface here
245   and are reported through the returned error code. A closed 245   and are reported through the returned error code. A closed
246   file reports `errc::bad_file_descriptor`. 246   file reports `errc::bad_file_descriptor`.
247   247  
248   @return The error code, empty on success. 248   @return The error code, empty on success.
249   */ 249   */
250   [[nodiscard]] std::error_code sync_all() noexcept; 250   [[nodiscard]] std::error_code sync_all() noexcept;
251   251  
252   /** Release ownership of the native handle. 252   /** Release ownership of the native handle.
253   253  
254   The file object becomes not-open. The caller is 254   The file object becomes not-open. The caller is
255   responsible for closing the returned handle. 255   responsible for closing the returned handle.
256   256  
257   @return The native file descriptor or handle. 257   @return The native file descriptor or handle.
258   258  
259   @throws std::system_error `errc::bad_file_descriptor` if the 259   @throws std::system_error `errc::bad_file_descriptor` if the
260   file is not open. 260   file is not open.
261   */ 261   */
262   native_handle_type release(); 262   native_handle_type release();
263   263  
264   /** Adopt an existing native handle. 264   /** Adopt an existing native handle.
265   265  
266   Closes any currently open file before adopting. 266   Closes any currently open file before adopting.
267   The file object takes ownership of the handle. Handles 267   The file object takes ownership of the handle. Handles
268   created elsewhere may be unsuitable for asynchronous I/O; 268   created elsewhere may be unsuitable for asynchronous I/O;
269   such failures are reported through the returned error code. 269   such failures are reported through the returned error code.
270   270  
271   @param handle The native file descriptor or handle. 271   @param handle The native file descriptor or handle.
272   272  
273   @return The error code, empty on success. 273   @return The error code, empty on success.
274   */ 274   */
275   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; 275   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
276   276  
277   /** Move the file position. 277   /** Move the file position.
278   278  
279   Positions beyond the end of the file are allowed. A 279   Positions beyond the end of the file are allowed. A
280   resulting negative position is reported through the error 280   resulting negative position is reported through the error
281   code, as offsets often originate from file contents. A 281   code, as offsets often originate from file contents. A
282   closed file reports `errc::bad_file_descriptor`. 282   closed file reports `errc::bad_file_descriptor`.
283   283  
284   @param offset Signed offset from @p origin. 284   @param offset Signed offset from @p origin.
285   @param origin The reference point for the seek. 285   @param origin The reference point for the seek.
286   286  
287   @return The error code and new absolute position. 287   @return The error code and new absolute position.
288   */ 288   */
289   [[nodiscard]] capy::io_result<std::uint64_t> 289   [[nodiscard]] capy::io_result<std::uint64_t>
290   seek(std::int64_t offset, 290   seek(std::int64_t offset,
291   file_base::seek_basis origin = file_base::seek_set) noexcept; 291   file_base::seek_basis origin = file_base::seek_set) noexcept;
292   292  
293   protected: 293   protected:
294   /// Default-construct (for derived types that initialize io_object directly). 294   /// Default-construct (for derived types that initialize io_object directly).
HITCBC 295   12 stream_file() noexcept = default; 295   12 stream_file() noexcept = default;
296   296  
297   /// Construct from a pre-built handle (for native_stream_file). 297   /// Construct from a pre-built handle (for native_stream_file).
298   explicit stream_file(handle h) noexcept : io_object(std::move(h)) {} 298   explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
299   299  
300   private: 300   private:
HITCBC 301   436 inline implementation& get() const noexcept 301   581 inline implementation& get() const noexcept
302   { 302   {
HITCBC 303   436 return *static_cast<implementation*>(h_.get()); 303   581 return *static_cast<implementation*>(h_.get());
304   } 304   }
305   }; 305   };
306   306  
307   } // namespace boost::corosio 307   } // namespace boost::corosio
308   308  
309   #endif // BOOST_COROSIO_STREAM_FILE_HPP 309   #endif // BOOST_COROSIO_STREAM_FILE_HPP