100.00% Lines (40/40) 100.00% Functions (11/11)
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_RANDOM_ACCESS_FILE_HPP 10   #ifndef BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
11   #define BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP 11   #define BOOST_COROSIO_RANDOM_ACCESS_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/detail/buffer_param.hpp> 17   #include <boost/corosio/detail/buffer_param.hpp>
18   #include <boost/corosio/detail/op_base.hpp> 18   #include <boost/corosio/detail/op_base.hpp>
  19 + #include <boost/corosio/error.hpp>
19   #include <boost/corosio/file_base.hpp> 20   #include <boost/corosio/file_base.hpp>
20   #include <boost/corosio/io/io_object.hpp> 21   #include <boost/corosio/io/io_object.hpp>
  22 + #include <boost/capy/continuation.hpp>
21   #include <boost/capy/io_result.hpp> 23   #include <boost/capy/io_result.hpp>
22   #include <boost/capy/ex/executor_ref.hpp> 24   #include <boost/capy/ex/executor_ref.hpp>
23   #include <boost/capy/ex/execution_context.hpp> 25   #include <boost/capy/ex/execution_context.hpp>
24   #include <boost/capy/ex/io_env.hpp> 26   #include <boost/capy/ex/io_env.hpp>
25   #include <boost/capy/concept/executor.hpp> 27   #include <boost/capy/concept/executor.hpp>
26   #include <boost/capy/buffers.hpp> 28   #include <boost/capy/buffers.hpp>
27   29  
28   #include <concepts> 30   #include <concepts>
29   #include <coroutine> 31   #include <coroutine>
30   #include <cstddef> 32   #include <cstddef>
31   #include <cstdint> 33   #include <cstdint>
32   #include <type_traits> 34   #include <type_traits>
33   #include <filesystem> 35   #include <filesystem>
34   #include <stop_token> 36   #include <stop_token>
35   #include <system_error> 37   #include <system_error>
36   38  
37   namespace boost::corosio { 39   namespace boost::corosio {
38   40  
39   /** Reads and writes a file at arbitrary offsets, from a coroutine. 41   /** Reads and writes a file at arbitrary offsets, from a coroutine.
40   42  
41   Provides asynchronous read and write operations at explicit 43   Provides asynchronous read and write operations at explicit
42   byte offsets, without maintaining an implicit file position. 44   byte offsets, without maintaining an implicit file position.
43   45  
44   On POSIX platforms, file I/O is dispatched to a thread pool 46   On POSIX platforms, file I/O is dispatched to a thread pool
45   (blocking `preadv`/`pwritev`) with completion posted back to 47   (blocking `preadv`/`pwritev`) with completion posted back to
46   the scheduler. On Windows, true overlapped I/O is used via IOCP. 48   the scheduler. On Windows, true overlapped I/O is used via IOCP.
47   49  
  50 + On Windows, while the file is open, its handle is bound to the
  51 + execution context's completion port. Every overlapped call on the
  52 + handle queues a packet to that port. Do not issue your own
  53 + overlapped I/O on `native_handle()` (`DeviceIoControl`,
  54 + `ReadFile`) unless the `OVERLAPPED`'s `hEvent` has its low-order
  55 + bit set, which suppresses the packet.
  56 +
48   @par Thread Safety 57   @par Thread Safety
49   Distinct objects: Safe.@n 58   Distinct objects: Safe.@n
50   Shared objects: Unsafe. Coroutines sharing the same file object may 59   Shared objects: Unsafe. Coroutines sharing the same file object may
51   run multiple concurrent reads and writes. Non-async operations such as open, close, size, and resize require external synchronization. 60   run multiple concurrent reads and writes. Non-async operations such as open, close, size, and resize require external synchronization.
52   61  
53   @par Example 62   @par Example
54   @par !example random_access_file 63   @par !example random_access_file
55   */ 64   */
56   class BOOST_COROSIO_DECL random_access_file : public io_object 65   class BOOST_COROSIO_DECL random_access_file : public io_object
57   { 66   {
58   public: 67   public:
59   /** Declares the offset-based file operations a platform backend 68   /** Declares the offset-based file operations a platform backend
60   must implement. 69   must implement.
61   70  
62   Backends derive from this to provide offset-based file I/O. 71   Backends derive from this to provide offset-based file I/O.
63   */ 72   */
64   struct implementation : io_object::implementation 73   struct implementation : io_object::implementation
65   { 74   {
66   /** Initiate a read at the given offset. 75   /** Initiate a read at the given offset.
67   76  
68   @param offset Byte offset into the file. 77   @param offset Byte offset into the file.
69 - @param h Coroutine handle to resume on completion. 78 + @param cont The awaiting coroutine's continuation. It must
  79 + stay valid until `cont.h` is resumed through @p ex.
70   @param ex Executor for dispatching the completion. 80   @param ex Executor for dispatching the completion.
71   @param buf The buffer to read into. 81   @param buf The buffer to read into.
72   @param token Stop token for cancellation. 82   @param token Stop token for cancellation.
73   @param ec Output error code. 83   @param ec Output error code.
74   @param bytes_out Output bytes transferred. 84   @param bytes_out Output bytes transferred.
75   @return Coroutine handle to resume immediately. 85   @return Coroutine handle to resume immediately.
76   */ 86   */
77   virtual std::coroutine_handle<> read_some_at( 87   virtual std::coroutine_handle<> read_some_at(
78   std::uint64_t offset, 88   std::uint64_t offset,
79 - std::coroutine_handle<> h, 89 + capy::continuation& cont,
80   capy::executor_ref ex, 90   capy::executor_ref ex,
81   buffer_param buf, 91   buffer_param buf,
82   std::stop_token token, 92   std::stop_token token,
83   std::error_code* ec, 93   std::error_code* ec,
84   std::size_t* bytes_out) = 0; 94   std::size_t* bytes_out) = 0;
85   95  
86   /** Initiate a write at the given offset. 96   /** Initiate a write at the given offset.
87   97  
88   @param offset Byte offset into the file. 98   @param offset Byte offset into the file.
89 - @param h Coroutine handle to resume on completion. 99 + @param cont The awaiting coroutine's continuation. It must
  100 + stay valid until `cont.h` is resumed through @p ex.
90   @param ex Executor for dispatching the completion. 101   @param ex Executor for dispatching the completion.
91   @param buf The buffer to write from. 102   @param buf The buffer to write from.
92   @param token Stop token for cancellation. 103   @param token Stop token for cancellation.
93   @param ec Output error code. 104   @param ec Output error code.
94   @param bytes_out Output bytes transferred. 105   @param bytes_out Output bytes transferred.
95   @return Coroutine handle to resume immediately. 106   @return Coroutine handle to resume immediately.
96   */ 107   */
97   virtual std::coroutine_handle<> write_some_at( 108   virtual std::coroutine_handle<> write_some_at(
98   std::uint64_t offset, 109   std::uint64_t offset,
99 - std::coroutine_handle<> h, 110 + capy::continuation& cont,
100   capy::executor_ref ex, 111   capy::executor_ref ex,
101   buffer_param buf, 112   buffer_param buf,
102   std::stop_token token, 113   std::stop_token token,
103   std::error_code* ec, 114   std::error_code* ec,
104   std::size_t* bytes_out) = 0; 115   std::size_t* bytes_out) = 0;
105   116  
106   /// Return the platform file descriptor or handle. 117   /// Return the platform file descriptor or handle.
107   virtual native_handle_type native_handle() const noexcept = 0; 118   virtual native_handle_type native_handle() const noexcept = 0;
108   119  
109   /// Cancel pending asynchronous operations. 120   /// Cancel pending asynchronous operations.
110   virtual void cancel() noexcept = 0; 121   virtual void cancel() noexcept = 0;
111   122  
112   /// Return the file size in bytes. 123   /// Return the file size in bytes.
113   virtual std::uint64_t size() const = 0; 124   virtual std::uint64_t size() const = 0;
114   125  
115   /** Resize the file to @p new_size bytes. 126   /** Resize the file to @p new_size bytes.
116   127  
117   @param new_size The requested size in bytes. 128   @param new_size The requested size in bytes.
118   129  
119   @return The error code, empty on success. 130   @return The error code, empty on success.
120   */ 131   */
121   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; 132   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
122   133  
123   /** Synchronize file data to stable storage. 134   /** Synchronize file data to stable storage.
124   135  
125   @return The error code, empty on success. 136   @return The error code, empty on success.
126   */ 137   */
127   virtual std::error_code sync_data() noexcept = 0; 138   virtual std::error_code sync_data() noexcept = 0;
128   139  
129   /** Synchronize file data and metadata to stable storage. 140   /** Synchronize file data and metadata to stable storage.
130   141  
131   @return The error code, empty on success. 142   @return The error code, empty on success.
132   */ 143   */
133   virtual std::error_code sync_all() noexcept = 0; 144   virtual std::error_code sync_all() noexcept = 0;
134   145  
135   /// Release ownership of the native handle. 146   /// Release ownership of the native handle.
136   virtual native_handle_type release() = 0; 147   virtual native_handle_type release() = 0;
137   148  
138   /** Adopt an existing native handle. 149   /** Adopt an existing native handle.
139   150  
140   @param handle The native handle to adopt. The implementation takes 151   @param handle The native handle to adopt. The implementation takes
141   ownership and closes it. 152   ownership and closes it.
142   153  
143   @return The error code, empty on success. 154   @return The error code, empty on success.
144   */ 155   */
145   virtual std::error_code assign(native_handle_type handle) noexcept = 0; 156   virtual std::error_code assign(native_handle_type handle) noexcept = 0;
146   }; 157   };
147   158  
148   /** Awaitable for async read-at operations. */ 159   /** Awaitable for async read-at operations. */
149   template<class MutableBufferSequence> 160   template<class MutableBufferSequence>
150   struct read_some_at_awaitable 161   struct read_some_at_awaitable
151   : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>> 162   : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>>
152   { 163   {
153   private: 164   private:
154   friend random_access_file; 165   friend random_access_file;
155   friend detail::bytes_op_base< 166   friend detail::bytes_op_base<
156   read_some_at_awaitable<MutableBufferSequence>>; 167   read_some_at_awaitable<MutableBufferSequence>>;
157   168  
158   random_access_file& f_; 169   random_access_file& f_;
159   std::uint64_t offset_; 170   std::uint64_t offset_;
160   MutableBufferSequence buffers_; 171   MutableBufferSequence buffers_;
  172 + mutable capy::continuation cont_;
161   173  
HITCBC 162   343 read_some_at_awaitable( 174   349 read_some_at_awaitable(
163   random_access_file& f, 175   random_access_file& f,
164   std::uint64_t offset, 176   std::uint64_t offset,
165   MutableBufferSequence 177   MutableBufferSequence
166   buffers) noexcept(std:: 178   buffers) noexcept(std::
167   is_nothrow_move_constructible_v< 179   is_nothrow_move_constructible_v<
168   MutableBufferSequence>) 180   MutableBufferSequence>)
HITCBC 169   343 : f_(f) 181   349 : f_(f)
HITCBC 170   343 , offset_(offset) 182   349 , offset_(offset)
HITCBC 171   343 , buffers_(std::move(buffers)) 183   349 , buffers_(std::move(buffers))
172   { 184   {
HITCBC 173   343 } 185   349 }
174   186  
175   std::coroutine_handle<> 187   std::coroutine_handle<>
HITCBC 176   337 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const 188   343 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
177   { 189   {
  190 + // The continuation lives in the awaiting frame, which stays
  191 + // put until resumption -- unlike the per-call op, which is
  192 + // freed before the coroutine runs.
HITGNC   193 + 343 cont_.h = h;
HITCBC 178   674 return f_.get().read_some_at( 194   686 return f_.get().read_some_at(
HITCBC 179 - 337 offset_, h, ex, buffers_, this->token_, &this->ec_, 195 + 343 offset_, cont_, ex, buffers_, this->token_, &this->ec_,
HITCBC 180   674 &this->bytes_); 196   686 &this->bytes_);
181   } 197   }
182   }; 198   };
183   199  
184   /** Awaitable for async write-at operations. */ 200   /** Awaitable for async write-at operations. */
185   template<class ConstBufferSequence> 201   template<class ConstBufferSequence>
186   struct write_some_at_awaitable 202   struct write_some_at_awaitable
187   : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>> 203   : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>>
188   { 204   {
189   private: 205   private:
190   friend random_access_file; 206   friend random_access_file;
191   friend detail::bytes_op_base< 207   friend detail::bytes_op_base<
192   write_some_at_awaitable<ConstBufferSequence>>; 208   write_some_at_awaitable<ConstBufferSequence>>;
193   209  
194   random_access_file& f_; 210   random_access_file& f_;
195   std::uint64_t offset_; 211   std::uint64_t offset_;
196   ConstBufferSequence buffers_; 212   ConstBufferSequence buffers_;
  213 + mutable capy::continuation cont_;
197   214  
HITCBC 198   93 write_some_at_awaitable( 215   97 write_some_at_awaitable(
199   random_access_file& f, 216   random_access_file& f,
200   std::uint64_t offset, 217   std::uint64_t offset,
201   ConstBufferSequence 218   ConstBufferSequence
202   buffers) noexcept(std:: 219   buffers) noexcept(std::
203   is_nothrow_move_constructible_v< 220   is_nothrow_move_constructible_v<
204   ConstBufferSequence>) 221   ConstBufferSequence>)
HITCBC 205   93 : f_(f) 222   97 : f_(f)
HITCBC 206   93 , offset_(offset) 223   97 , offset_(offset)
HITCBC 207   93 , buffers_(std::move(buffers)) 224   97 , buffers_(std::move(buffers))
208   { 225   {
HITCBC 209   93 } 226   97 }
210   227  
211   std::coroutine_handle<> 228   std::coroutine_handle<>
HITCBC 212   89 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const 229   93 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
213   { 230   {
HITGNC   231 + 93 cont_.h = h;
HITCBC 214   178 return f_.get().write_some_at( 232   186 return f_.get().write_some_at(
HITCBC 215 - 89 offset_, h, ex, buffers_, this->token_, &this->ec_, 233 + 93 offset_, cont_, ex, buffers_, this->token_, &this->ec_,
HITCBC 216   178 &this->bytes_); 234   186 &this->bytes_);
217   } 235   }
218   }; 236   };
219   237  
220   public: 238   public:
221   /** Destructor. 239   /** Destructor.
222   240  
223   Closes the file if open, cancelling any pending operations. 241   Closes the file if open, cancelling any pending operations.
224   */ 242   */
225   ~random_access_file() override; 243   ~random_access_file() override;
226   244  
227   /** Construct from an execution context. 245   /** Construct from an execution context.
228   246  
229   @param ctx The execution context that owns this file. 247   @param ctx The execution context that owns this file.
230   */ 248   */
231   explicit random_access_file(capy::execution_context& ctx); 249   explicit random_access_file(capy::execution_context& ctx);
232   250  
233   /** Construct from an executor. 251   /** Construct from an executor.
234   252  
235   @param ex The executor whose context owns this file. 253   @param ex The executor whose context owns this file.
236   */ 254   */
237   template<class Ex> 255   template<class Ex>
238   requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) && 256   requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
239   capy::Executor<Ex> 257   capy::Executor<Ex>
HITCBC 240   2 explicit random_access_file(Ex const& ex) : random_access_file(ex.context()) 258   2 explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
241   { 259   {
HITCBC 242   2 } 260   2 }
243   261  
244   /** Move constructor. */ 262   /** Move constructor. */
HITCBC 245   2 random_access_file(random_access_file&& other) noexcept 263   2 random_access_file(random_access_file&& other) noexcept
HITCBC 246   2 : io_object(std::move(other)) 264   2 : io_object(std::move(other))
247   { 265   {
HITCBC 248   2 } 266   2 }
249   267  
250   /** Move assignment operator. */ 268   /** Move assignment operator. */
251   random_access_file& operator=(random_access_file&& other) noexcept 269   random_access_file& operator=(random_access_file&& other) noexcept
252   { 270   {
253   if (this != &other) 271   if (this != &other)
254   { 272   {
255   close(); 273   close();
256   h_ = std::move(other.h_); 274   h_ = std::move(other.h_);
257   } 275   }
258   return *this; 276   return *this;
259   } 277   }
260   278  
261   /// Copy construction is disabled; the handle is uniquely owned. 279   /// Copy construction is disabled; the handle is uniquely owned.
262   random_access_file(random_access_file const&) = delete; 280   random_access_file(random_access_file const&) = delete;
263   /// Copy assignment is disabled; the handle is uniquely owned. 281   /// Copy assignment is disabled; the handle is uniquely owned.
264   random_access_file& operator=(random_access_file const&) = delete; 282   random_access_file& operator=(random_access_file const&) = delete;
265   283  
266   /** Open a file. 284   /** Open a file.
267   285  
268   Failures such as a missing file or insufficient permissions 286   Failures such as a missing file or insufficient permissions
269   are expected runtime conditions and are reported through the 287   are expected runtime conditions and are reported through the
270   returned error code. If the file is already open, it is 288   returned error code. If the file is already open, it is
271   closed first. 289   closed first.
272   290  
273   @param path The filesystem path to open. 291   @param path The filesystem path to open.
274   @param mode Bitmask of @ref file_base::flags specifying 292   @param mode Bitmask of @ref file_base::flags specifying
275   access mode and creation behavior. 293   access mode and creation behavior.
276   294  
277   @return The error code, empty on success. 295   @return The error code, empty on success.
278   */ 296   */
279   [[nodiscard]] std::error_code open( 297   [[nodiscard]] std::error_code open(
280   std::filesystem::path const& path, 298   std::filesystem::path const& path,
281   file_base::flags mode = file_base::read_only) noexcept; 299   file_base::flags mode = file_base::read_only) noexcept;
282   300  
283   /** Close the file. 301   /** Close the file.
284   302  
285   Releases file resources. Pending operations complete through the 303   Releases file resources. Pending operations complete through the
286   same path as @ref cancel: one still in flight completes with 304   same path as @ref cancel: one still in flight completes with
287   `errc::operation_canceled`. An operation whose result is already 305   `errc::operation_canceled`. An operation whose result is already
288   decided reports that result. 306   decided reports that result.
289   */ 307   */
290   void close() noexcept; 308   void close() noexcept;
291   309  
292   /** Check if the file is open. 310   /** Check if the file is open.
293   311  
294   @return `true` if the file holds an open handle. 312   @return `true` if the file holds an open handle.
295   */ 313   */
HITCBC 296   1084 bool is_open() const noexcept 314   1116 bool is_open() const noexcept
297   { 315   {
298   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 316   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
299   return h_ && get().native_handle() != ~native_handle_type(0); 317   return h_ && get().native_handle() != ~native_handle_type(0);
300   #else 318   #else
HITCBC 301   1084 return h_ && get().native_handle() >= 0; 319   1116 return h_ && get().native_handle() >= 0;
302   #endif 320   #endif
303   } 321   }
304   322  
305   /** Read data at the given offset. 323   /** Read data at the given offset.
306   324  
307   @param offset Byte offset into the file. 325   @param offset Byte offset into the file.
308   @param buffers The buffer sequence to read into. 326   @param buffers The buffer sequence to read into.
309   327  
310   @return An awaitable yielding `(error_code, std::size_t)`. 328   @return An awaitable yielding `(error_code, std::size_t)`.
311   329  
312   A closed file reports `errc::bad_file_descriptor`. 330   A closed file reports `errc::bad_file_descriptor`.
313   */ 331   */
314   template<capy::MutableBufferSequence MB> 332   template<capy::MutableBufferSequence MB>
HITCBC 315   343 [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers) 333   349 [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
316   { 334   {
HITCBC 317   343 read_some_at_awaitable<MB> aw(*this, offset, buffers); 335   349 read_some_at_awaitable<MB> aw(*this, offset, buffers);
HITCBC 318   343 if (!is_open()) 336   349 if (!is_open())
HITCBC 319   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor); 337   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 320   343 return aw; 338   349 return aw;
321   } 339   }
322   340  
323   /** Write data at the given offset. 341   /** Write data at the given offset.
324   342  
325   @param offset Byte offset into the file. 343   @param offset Byte offset into the file.
326   @param buffers The buffer sequence to write from. 344   @param buffers The buffer sequence to write from.
327   345  
328   @return An awaitable yielding `(error_code, std::size_t)`. 346   @return An awaitable yielding `(error_code, std::size_t)`.
329   347  
330   A closed file reports `errc::bad_file_descriptor`. 348   A closed file reports `errc::bad_file_descriptor`.
331   */ 349   */
332   template<capy::ConstBufferSequence CB> 350   template<capy::ConstBufferSequence CB>
HITCBC 333   93 [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers) 351   97 [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
334   { 352   {
HITCBC 335   93 write_some_at_awaitable<CB> aw(*this, offset, buffers); 353   97 write_some_at_awaitable<CB> aw(*this, offset, buffers);
HITCBC 336   93 if (!is_open()) 354   97 if (!is_open())
HITCBC 337   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor); 355   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 338   93 return aw; 356   97 return aw;
339   } 357   }
340   358  
341   /** Cancel pending asynchronous operations. */ 359   /** Cancel pending asynchronous operations. */
342   void cancel() noexcept; 360   void cancel() noexcept;
343   361  
344   /** Get the native file descriptor or handle. */ 362   /** Get the native file descriptor or handle. */
345   native_handle_type native_handle() const noexcept; 363   native_handle_type native_handle() const noexcept;
346   364  
347   /** Return the file size in bytes. 365   /** Return the file size in bytes.
348   366  
349   @return The current size of the file, in bytes. 367   @return The current size of the file, in bytes.
350   368  
351   @throws std::system_error If the file is not open, or if the 369   @throws std::system_error If the file is not open, or if the
352   underlying size query fails. 370   underlying size query fails.
353   */ 371   */
354   std::uint64_t size() const; 372   std::uint64_t size() const;
355   373  
356   /** Resize the file to @p new_size bytes. 374   /** Resize the file to @p new_size bytes.
357   375  
358   Failures such as insufficient disk space are reported 376   Failures such as insufficient disk space are reported
359   through the returned error code. A closed file reports 377   through the returned error code. A closed file reports
360   `errc::bad_file_descriptor`. 378   `errc::bad_file_descriptor`.
361   379  
362   @param new_size The new file size. 380   @param new_size The new file size.
363   381  
364   @return The error code, empty on success. 382   @return The error code, empty on success.
365   */ 383   */
366   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; 384   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
367   385  
368   /** Synchronize file data to stable storage. 386   /** Synchronize file data to stable storage.
369   387  
370   Write-back failures such as device I/O errors surface here 388   Write-back failures such as device I/O errors surface here
371   and are reported through the returned error code. A closed 389   and are reported through the returned error code. A closed
372   file reports `errc::bad_file_descriptor`. 390   file reports `errc::bad_file_descriptor`.
373   391  
374   @return The error code, empty on success. 392   @return The error code, empty on success.
375   */ 393   */
376   [[nodiscard]] std::error_code sync_data() noexcept; 394   [[nodiscard]] std::error_code sync_data() noexcept;
377   395  
378   /** Synchronize file data and metadata to stable storage. 396   /** Synchronize file data and metadata to stable storage.
379   397  
380   Write-back failures such as device I/O errors surface here 398   Write-back failures such as device I/O errors surface here
381   and are reported through the returned error code. A closed 399   and are reported through the returned error code. A closed
382   file reports `errc::bad_file_descriptor`. 400   file reports `errc::bad_file_descriptor`.
383   401  
384   @return The error code, empty on success. 402   @return The error code, empty on success.
385   */ 403   */
386   [[nodiscard]] std::error_code sync_all() noexcept; 404   [[nodiscard]] std::error_code sync_all() noexcept;
387   405  
388   /** Release ownership of the native handle. 406   /** Release ownership of the native handle.
389   407  
390   The file object becomes not-open. The caller is 408   The file object becomes not-open. The caller is
391   responsible for closing the returned handle. 409   responsible for closing the returned handle.
392   410  
  411 + `release()` cancels pending operations first. On Windows, the
  412 + object keeps the handle and this throws if one is still in
  413 + flight. It does the same if Windows refuses to detach the
  414 + handle from the execution context's completion port. Call
  415 + `release()` again once the cancelled operations have
  416 + completed. Detaching requires Windows 8.1 or later.
  417 +
393   @return The native file descriptor or handle. 418   @return The native file descriptor or handle.
394   419  
395   @throws std::system_error `errc::bad_file_descriptor` if the 420   @throws std::system_error `errc::bad_file_descriptor` if the
396 - file is not open. 421 + file is not open. On Windows,
  422 + `errc::device_or_resource_busy` if an operation is still in
  423 + flight, or `errc::operation_not_supported` if the handle
  424 + cannot be detached.
397   */ 425   */
398   native_handle_type release(); 426   native_handle_type release();
399   427  
400   /** Adopt an existing native handle. 428   /** Adopt an existing native handle.
401   429  
402 - Closes any currently open file before adopting. 430 + The object must be closed. To replace a held file, `close()`
403 - The file object takes ownership of the handle. Handles 431 + or `release()` it first. On success the object takes
404 - created elsewhere may be unsuitable for asynchronous I/O; 432 + ownership of @p handle. Handles created elsewhere may be
405 - such failures are reported through the returned error code. 433 + unsuitable for asynchronous I/O. `assign()` reports most such
  434 + failures through the returned error code.
406   435  
407   @param handle The native file descriptor or handle. 436   @param handle The native file descriptor or handle.
408   437  
409 - @return The error code, empty on success. 438 + @return An error code describing the outcome.
  439 + `error::already_open` if this object is open.
  440 + `errc::bad_file_descriptor` if @p handle is invalid.
  441 + `errc::operation_not_supported` if a file object cannot
  442 + use it. On Windows, the rejected handles are a pipe, a
  443 + socket, a console, a directory, a handle opened without
  444 + `FILE_FLAG_OVERLAPPED`, or one
  445 + already in skip-completion-port-on-success mode. On
  446 + Windows, `errc::invalid_argument` when @p handle is
  447 + bound to another completion port. Any other failure is
  448 + the code reported by the system. Otherwise, the code is
  449 + empty.
  450 +
  451 + @par Exception Safety
  452 + Throws nothing. On failure the object is unchanged and the
  453 + caller still owns @p handle.
  454 +
  455 + @note On POSIX, the rejected descriptors are, in practice, a
  456 + directory, a pipe, a socket, or any other anonymous inode.
  457 + Adopt a pipe, a socket, or an anonymous inode into a
  458 + @ref posix_stream_descriptor instead. `assign()` accepts a
  459 + non-seekable character device such as a tty. Its first
  460 + read or write then fails with `ESPIPE` on the epoll,
  461 + kqueue, and select I/O backends.
  462 +
  463 + @see release
410   */ 464   */
411   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; 465   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
412   466  
413   protected: 467   protected:
414 - /// Construct from a pre-built handle (for native_random_access_file). 468 + /** Construct from a pre-built handle (for `native_random_access_file`).
  469 +
  470 + @param h The pre-built handle to adopt.
  471 + */
HITCBC 415   16 explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {} 472   16 explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
416   473  
417   private: 474   private:
HITCBC 418   1767 inline implementation& get() const noexcept 475   1817 inline implementation& get() const noexcept
419   { 476   {
HITCBC 420   1767 return *static_cast<implementation*>(h_.get()); 477   1817 return *static_cast<implementation*>(h_.get());
421   } 478   }
422   }; 479   };
423   480  
424   } // namespace boost::corosio 481   } // namespace boost::corosio
425   482  
426   #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP 483   #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP