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