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