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