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