99.22% Lines (127/128) 100.00% Functions (17/17)
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_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_HPP 10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_HPP 11   #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_HPP
12   12  
13   #include <boost/corosio/detail/platform.hpp> 13   #include <boost/corosio/detail/platform.hpp>
14   14  
15   #if BOOST_COROSIO_POSIX 15   #if BOOST_COROSIO_POSIX
16   16  
17   #include <boost/corosio/detail/config.hpp> 17   #include <boost/corosio/detail/config.hpp>
18   #include <boost/corosio/stream_file.hpp> 18   #include <boost/corosio/stream_file.hpp>
19   #include <boost/corosio/file_base.hpp> 19   #include <boost/corosio/file_base.hpp>
20   #include <boost/corosio/detail/intrusive.hpp> 20   #include <boost/corosio/detail/intrusive.hpp>
21   #include <boost/corosio/detail/dispatch_coro.hpp> 21   #include <boost/corosio/detail/dispatch_coro.hpp>
22   #include <boost/corosio/detail/scheduler_op.hpp> 22   #include <boost/corosio/detail/scheduler_op.hpp>
23   #include <boost/corosio/detail/thread_pool.hpp> 23   #include <boost/corosio/detail/thread_pool.hpp>
24   #include <boost/corosio/detail/scheduler.hpp> 24   #include <boost/corosio/detail/scheduler.hpp>
25   #include <boost/corosio/detail/buffer_param.hpp> 25   #include <boost/corosio/detail/buffer_param.hpp>
26   #include <boost/corosio/native/detail/coro_op.hpp> 26   #include <boost/corosio/native/detail/coro_op.hpp>
27   #include <boost/corosio/native/detail/coro_op_complete.hpp> 27   #include <boost/corosio/native/detail/coro_op_complete.hpp>
28   #include <boost/corosio/native/detail/make_err.hpp> 28   #include <boost/corosio/native/detail/make_err.hpp>
  29 + #include <boost/corosio/native/detail/posix/large_file.hpp>
  30 + #include <boost/corosio/native/detail/validate_fd.hpp>
29   #include <boost/capy/ex/executor_ref.hpp> 31   #include <boost/capy/ex/executor_ref.hpp>
30   #include <boost/capy/error.hpp> 32   #include <boost/capy/error.hpp>
31   #include <boost/capy/buffers.hpp> 33   #include <boost/capy/buffers.hpp>
32   34  
33   #include <atomic> 35   #include <atomic>
34   #include <coroutine> 36   #include <coroutine>
35   #include <cstddef> 37   #include <cstddef>
36   #include <cstdint> 38   #include <cstdint>
37   #include <filesystem> 39   #include <filesystem>
38   #include <limits> 40   #include <limits>
39   #include <memory> 41   #include <memory>
  42 + #include <mutex>
40   #include <optional> 43   #include <optional>
41   #include <stop_token> 44   #include <stop_token>
42   #include <system_error> 45   #include <system_error>
43   46  
44   #include <errno.h> 47   #include <errno.h>
45   #include <fcntl.h> 48   #include <fcntl.h>
46   #include <sys/stat.h> 49   #include <sys/stat.h>
47   #include <sys/uio.h> 50   #include <sys/uio.h>
48   #include <unistd.h> 51   #include <unistd.h>
49   52  
50   /* 53   /*
51   POSIX Stream File Implementation 54   POSIX Stream File Implementation
52   ================================= 55   =================================
53   56  
54   Regular files cannot be monitored by epoll/kqueue/select — the kernel 57   Regular files cannot be monitored by epoll/kqueue/select — the kernel
55   always reports them as ready. Blocking I/O (pread/pwrite) is dispatched 58   always reports them as ready. Blocking I/O (pread/pwrite) is dispatched
56   to a shared thread pool, with completion posted back to the scheduler. 59   to a shared thread pool, with completion posted back to the scheduler.
57   60  
58   This follows the same pattern as posix_resolver: pool_work_item for 61   This follows the same pattern as posix_resolver: pool_work_item for
59   dispatch, scheduler_op for completion, shared_from_this for lifetime. 62   dispatch, scheduler_op for completion, shared_from_this for lifetime.
60   63  
61   Completion Flow 64   Completion Flow
62   --------------- 65   ---------------
63   1. read_some() sets up file_read_op, posts to thread pool 66   1. read_some() sets up file_read_op, posts to thread pool
64   2. Pool thread runs preadv() (blocking) 67   2. Pool thread runs preadv() (blocking)
65   3. Pool thread stores results, posts scheduler_op to scheduler 68   3. Pool thread stores results, posts scheduler_op to scheduler
66   4. Scheduler invokes op() which resumes the coroutine 69   4. Scheduler invokes op() which resumes the coroutine
67   70  
68   Single-Inflight Constraint 71   Single-Inflight Constraint
69   -------------------------- 72   --------------------------
70   Only one asynchronous operation may be in flight at a time on a 73   Only one asynchronous operation may be in flight at a time on a
71   given file object. Concurrent read and write is not supported 74   given file object. Concurrent read and write is not supported
72   because both share offset_ without synchronization. 75   because both share offset_ without synchronization.
73   */ 76   */
74   77  
75   namespace boost::corosio::detail { 78   namespace boost::corosio::detail {
76   79  
77   struct scheduler; 80   struct scheduler;
78   class posix_stream_file_service; 81   class posix_stream_file_service;
79   82  
80   /** Stream file implementation for POSIX backends. 83   /** Stream file implementation for POSIX backends.
81   84  
82   Each instance contains embedded operation objects (read_op_, write_op_) 85   Each instance contains embedded operation objects (read_op_, write_op_)
83   that are reused across calls. This avoids per-operation heap allocation. 86   that are reused across calls. This avoids per-operation heap allocation.
84   */ 87   */
85   class posix_stream_file final 88   class posix_stream_file final
86   : public stream_file::implementation 89   : public stream_file::implementation
87   , public std::enable_shared_from_this<posix_stream_file> 90   , public std::enable_shared_from_this<posix_stream_file>
88   , public intrusive_list<posix_stream_file>::node 91   , public intrusive_list<posix_stream_file>::node
89   { 92   {
90   friend class posix_stream_file_service; 93   friend class posix_stream_file_service;
91   94  
92   public: 95   public:
93   static constexpr std::size_t max_buffers = 16; 96   static constexpr std::size_t max_buffers = 16;
94   97  
95   /** Operation state for a single file read or write. 98   /** Operation state for a single file read or write.
96   99  
97   The coroutine, cancellation and keepalive machinery is inherited 100   The coroutine, cancellation and keepalive machinery is inherited
98   from `coro_op`; only the pool-path result state lives here. 101   from `coro_op`; only the pool-path result state lives here.
99   */ 102   */
100   struct file_op : coro_op 103   struct file_op : coro_op
101   { 104   {
102   // Buffer data (copied from buffer_param at submission time) 105   // Buffer data (copied from buffer_param at submission time)
103   iovec iovecs[max_buffers]; 106   iovec iovecs[max_buffers];
104   int iovec_count = 0; 107   int iovec_count = 0;
105   108  
  109 + // Snapshotted at submission: assign() may replace fd_ and
  110 + // offset_ while the worker runs.
  111 + int fd = -1;
  112 + std::uint64_t offset = 0;
  113 + std::uint64_t generation = 0;
  114 +
106   // Result storage (populated by worker thread) 115   // Result storage (populated by worker thread)
107   int errn = 0; 116   int errn = 0;
108   std::size_t bytes_transferred = 0; 117   std::size_t bytes_transferred = 0;
109   118  
HITCBC 110   532 file_op() = default; 119   1354 file_op() = default;
111   120  
HITCBC 112   248 void reset() noexcept 121   650 void reset() noexcept
113   { 122   {
HITCBC 114   248 iovec_count = 0; 123   650 iovec_count = 0;
HITCBC 115   248 errn = 0; 124   650 errn = 0;
HITCBC 116   248 bytes_transferred = 0; 125   650 bytes_transferred = 0;
HITCBC 117   248 is_read = false; 126   650 is_read = false;
HITCBC 118   248 cancelled.store(false, std::memory_order_relaxed); 127   650 cancelled.store(false, std::memory_order_relaxed);
HITCBC 119   248 stop_cb.reset(); 128   650 stop_cb.reset();
HITCBC 120   248 impl_ptr.reset(); 129   650 impl_ptr.reset();
HITCBC 121   248 ec_out = nullptr; 130   650 ec_out = nullptr;
HITCBC 122   248 bytes_out = nullptr; 131   650 bytes_out = nullptr;
HITCBC 123   248 } 132   650 }
124   133  
125   void operator()() override; 134   void operator()() override;
126   void destroy() override; 135   void destroy() override;
127   }; 136   };
128   137  
129   /** Pool work item for thread pool dispatch. */ 138   /** Pool work item for thread pool dispatch. */
130   struct pool_op : pool_work_item 139   struct pool_op : pool_work_item
131   { 140   {
132   posix_stream_file* file_ = nullptr; 141   posix_stream_file* file_ = nullptr;
133   std::shared_ptr<posix_stream_file> ref_; 142   std::shared_ptr<posix_stream_file> ref_;
134   }; 143   };
135   144  
136   explicit posix_stream_file(posix_stream_file_service& svc) noexcept; 145   explicit posix_stream_file(posix_stream_file_service& svc) noexcept;
137   146  
138   // -- io_stream::implementation -- 147   // -- io_stream::implementation --
139   148  
140   std::coroutine_handle<> read_some( 149   std::coroutine_handle<> read_some(
141   std::coroutine_handle<>, 150   std::coroutine_handle<>,
142   capy::executor_ref, 151   capy::executor_ref,
143   buffer_param, 152   buffer_param,
144   std::stop_token, 153   std::stop_token,
145   std::error_code*, 154   std::error_code*,
146   std::size_t*) override; 155   std::size_t*) override;
147   156  
148   std::coroutine_handle<> write_some( 157   std::coroutine_handle<> write_some(
149   std::coroutine_handle<>, 158   std::coroutine_handle<>,
150   capy::executor_ref, 159   capy::executor_ref,
151   buffer_param, 160   buffer_param,
152   std::stop_token, 161   std::stop_token,
153   std::error_code*, 162   std::error_code*,
154   std::size_t*) override; 163   std::size_t*) override;
155   164  
156   // -- stream_file::implementation -- 165   // -- stream_file::implementation --
157   166  
HITCBC 158   795 native_handle_type native_handle() const noexcept override 167   2843 native_handle_type native_handle() const noexcept override
159   { 168   {
HITCBC 160   795 return fd_; 169   2843 return fd_;
161   } 170   }
162   171  
HITCBC 163   767 void cancel() noexcept override 172   2401 void cancel() noexcept override
164   { 173   {
HITCBC 165   767 read_op_.request_cancel(); 174   2401 read_op_.request_cancel();
HITCBC 166   767 write_op_.request_cancel(); 175   2401 write_op_.request_cancel();
HITCBC 167   767 } 176   2401 }
168   177  
169   std::uint64_t size() const override; 178   std::uint64_t size() const override;
170   std::error_code resize(std::uint64_t new_size) noexcept override; 179   std::error_code resize(std::uint64_t new_size) noexcept override;
171   std::error_code sync_data() noexcept override; 180   std::error_code sync_data() noexcept override;
172   std::error_code sync_all() noexcept override; 181   std::error_code sync_all() noexcept override;
173   native_handle_type release() override; 182   native_handle_type release() override;
174   std::error_code assign(native_handle_type handle) noexcept override; 183   std::error_code assign(native_handle_type handle) noexcept override;
175   capy::io_result<std::uint64_t> 184   capy::io_result<std::uint64_t>
176   seek(std::int64_t offset, file_base::seek_basis origin) noexcept override; 185   seek(std::int64_t offset, file_base::seek_basis origin) noexcept override;
177   186  
178   // -- Internal -- 187   // -- Internal --
179   188  
180   /** Open the file and store the fd. */ 189   /** Open the file and store the fd. */
181   std::error_code 190   std::error_code
182   open_file(std::filesystem::path const& path, file_base::flags mode); 191   open_file(std::filesystem::path const& path, file_base::flags mode);
183   192  
184   /** Close the file descriptor. */ 193   /** Close the file descriptor. */
185   void close_file() noexcept; 194   void close_file() noexcept;
186   195  
187   private: 196   private:
188   posix_stream_file_service& svc_; 197   posix_stream_file_service& svc_;
189   int fd_ = -1; 198   int fd_ = -1;
190   std::uint64_t offset_ = 0; 199   std::uint64_t offset_ = 0;
191   200  
  201 + // Bumped whenever the held file changes. A worker advances offset_
  202 + // only if this still matches its op's snapshot; the mutex makes the
  203 + // check and the advance one step against a concurrent assign().
  204 + std::mutex offset_mutex_;
  205 + std::uint64_t generation_ = 0;
  206 +
192   file_op read_op_; 207   file_op read_op_;
193   file_op write_op_; 208   file_op write_op_;
194   pool_op read_pool_op_; 209   pool_op read_pool_op_;
195   pool_op write_pool_op_; 210   pool_op write_pool_op_;
196   211  
  212 + void bump_generation() noexcept;
  213 +
197   static void do_read_work(pool_work_item*) noexcept; 214   static void do_read_work(pool_work_item*) noexcept;
198   static void do_write_work(pool_work_item*) noexcept; 215   static void do_write_work(pool_work_item*) noexcept;
199   }; 216   };
200   217  
201   // --------------------------------------------------------------------------- 218   // ---------------------------------------------------------------------------
202   // Inline implementation 219   // Inline implementation
203   // --------------------------------------------------------------------------- 220   // ---------------------------------------------------------------------------
204   221  
HITCBC 205   266 inline posix_stream_file::posix_stream_file( 222   677 inline posix_stream_file::posix_stream_file(
HITCBC 206   266 posix_stream_file_service& svc) noexcept 223   677 posix_stream_file_service& svc) noexcept
HITCBC 207   266 : svc_(svc) 224   677 : svc_(svc)
208   { 225   {
HITCBC 209   266 } 226   677 }
210   227  
211   inline std::error_code 228   inline std::error_code
HITCBC 212   247 posix_stream_file::open_file( 229   656 posix_stream_file::open_file(
213   std::filesystem::path const& path, file_base::flags mode) 230   std::filesystem::path const& path, file_base::flags mode)
214   { 231   {
HITCBC 215   247 close_file(); 232   656 close_file();
216   233  
HITCBC 217   247 int oflags = 0; 234   656 int oflags = 0;
218   235  
219   // Access mode 236   // Access mode
HITCBC 220   247 unsigned access = static_cast<unsigned>(mode) & 3u; 237   656 unsigned access = static_cast<unsigned>(mode) & 3u;
HITCBC 221   247 if (access == static_cast<unsigned>(file_base::read_write)) 238   656 if (access == static_cast<unsigned>(file_base::read_write))
HITCBC 222   21 oflags |= O_RDWR; 239   23 oflags |= O_RDWR;
HITCBC 223   226 else if (access == static_cast<unsigned>(file_base::write_only)) 240   633 else if (access == static_cast<unsigned>(file_base::write_only))
HITCBC 224   81 oflags |= O_WRONLY; 241   81 oflags |= O_WRONLY;
225   else 242   else
HITCBC 226   145 oflags |= O_RDONLY; 243   552 oflags |= O_RDONLY;
227   244  
228   // Creation flags 245   // Creation flags
HITCBC 229   247 if ((mode & file_base::create) != file_base::flags(0)) 246   656 if ((mode & file_base::create) != file_base::flags(0))
HITCBC 230   40 oflags |= O_CREAT; 247   40 oflags |= O_CREAT;
HITCBC 231   247 if ((mode & file_base::exclusive) != file_base::flags(0)) 248   656 if ((mode & file_base::exclusive) != file_base::flags(0))
HITCBC 232   2 oflags |= O_EXCL; 249   2 oflags |= O_EXCL;
HITCBC 233   247 if ((mode & file_base::truncate) != file_base::flags(0)) 250   656 if ((mode & file_base::truncate) != file_base::flags(0))
HITCBC 234   17 oflags |= O_TRUNC; 251   17 oflags |= O_TRUNC;
HITCBC 235   247 if ((mode & file_base::append) != file_base::flags(0)) 252   656 if ((mode & file_base::append) != file_base::flags(0))
HITCBC 236   8 oflags |= O_APPEND; 253   8 oflags |= O_APPEND;
HITCBC 237   247 if ((mode & file_base::sync_all_on_write) != file_base::flags(0)) 254   656 if ((mode & file_base::sync_all_on_write) != file_base::flags(0))
HITCBC 238   2 oflags |= O_SYNC; 255   2 oflags |= O_SYNC;
239   256  
HITCBC 240 - 247 int fd = ::open(path.c_str(), oflags, 0666); 257 + 656 int fd = ::open(path.c_str(), oflags | large_file_open_flag, 0666);
HITCBC 241   247 if (fd < 0) 258   656 if (fd < 0)
HITCBC 242   9 return make_err(errno); 259   9 return make_err(errno);
243   260  
HITCBC 244   238 fd_ = fd; 261   647 fd_ = fd;
HITCBC 245   238 offset_ = 0; 262   647 offset_ = 0;
246   263  
247   // Append mode: position at end-of-file (preadv/pwritev use 264   // Append mode: position at end-of-file (preadv/pwritev use
248   // explicit offsets, so O_APPEND alone is not sufficient). 265   // explicit offsets, so O_APPEND alone is not sufficient).
HITCBC 249   238 if ((mode & file_base::append) != file_base::flags(0)) 266   647 if ((mode & file_base::append) != file_base::flags(0))
250   { 267   {
251 - struct stat st; 268 + file_stat_t st;
HITCBC 252 - 8 if (::fstat(fd, &st) < 0) 269 + 8 if (file_fstat(fd, &st) < 0)
253   { 270   {
HITCBC 254   5 int err = errno; 271   5 int err = errno;
HITCBC 255   5 ::close(fd); 272   5 ::close(fd);
HITCBC 256   5 fd_ = -1; 273   5 fd_ = -1;
HITCBC 257   5 return make_err(err); 274   5 return make_err(err);
258   } 275   }
HITCBC 259   3 offset_ = static_cast<std::uint64_t>(st.st_size); 276   3 offset_ = static_cast<std::uint64_t>(st.st_size);
260   } 277   }
261   278  
262   #ifdef POSIX_FADV_SEQUENTIAL 279   #ifdef POSIX_FADV_SEQUENTIAL
HITCBC 263   233 ::posix_fadvise(fd_, 0, 0, POSIX_FADV_SEQUENTIAL); 280   642 ::posix_fadvise(fd_, 0, 0, POSIX_FADV_SEQUENTIAL);
264   #endif 281   #endif
265   282  
HITCBC 266   233 return {}; 283   642 return {};
267   } 284   }
268   285  
269   inline void 286   inline void
HITGNC   287 + 3055 posix_stream_file::bump_generation() noexcept
  288 + {
HITGNC   289 + 3055 std::lock_guard<std::mutex> lock(offset_mutex_);
HITGNC   290 + 3055 ++generation_;
HITGNC   291 + 3055 }
  292 +
  293 + inline void
HITCBC 270   1018 posix_stream_file::close_file() noexcept 294   3051 posix_stream_file::close_file() noexcept
271   { 295   {
HITGNC   296 + 3051 bump_generation();
HITCBC 272   1018 if (fd_ >= 0) 297   3051 if (fd_ >= 0)
273   { 298   {
HITCBC 274   237 ::close(fd_); 299   1045 ::close(fd_);
HITCBC 275   237 fd_ = -1; 300   1045 fd_ = -1;
276   } 301   }
HITCBC 277   1018 } 302   3051 }
278   303  
279   inline std::uint64_t 304   inline std::uint64_t
HITCBC 280   17 posix_stream_file::size() const 305   15 posix_stream_file::size() const
281   { 306   {
282 - struct stat st; 307 + file_stat_t st;
HITCBC 283 - 17 if (::fstat(fd_, &st) < 0) 308 + 15 if (file_fstat(fd_, &st) < 0)
HITCBC 284   5 throw_system_error(make_err(errno), "stream_file::size"); 309   5 throw_system_error(make_err(errno), "stream_file::size");
HITCBC 285   12 return static_cast<std::uint64_t>(st.st_size); 310   10 return static_cast<std::uint64_t>(st.st_size);
286   } 311   }
287   312  
288   inline std::error_code 313   inline std::error_code
HITCBC 289   12 posix_stream_file::resize(std::uint64_t new_size) noexcept 314   12 posix_stream_file::resize(std::uint64_t new_size) noexcept
290   { 315   {
HITCBC 291   12 if (new_size > 316   12 if (new_size >
HITCBC 292 - 12 static_cast<std::uint64_t>((std::numeric_limits<off_t>::max)())) 317 + 12 static_cast<std::uint64_t>((std::numeric_limits<file_off_t>::max)()))
HITCBC 293   2 return make_err(EOVERFLOW); 318   2 return make_err(EOVERFLOW);
HITCBC 294 - 10 if (::ftruncate(fd_, static_cast<off_t>(new_size)) < 0) 319 + 10 if (file_ftruncate(fd_, static_cast<file_off_t>(new_size)) < 0)
HITCBC 295   7 return make_err(errno); 320   7 return make_err(errno);
HITCBC 296   3 return {}; 321   3 return {};
297   } 322   }
298   323  
299   inline std::error_code 324   inline std::error_code
HITCBC 300   10 posix_stream_file::sync_data() noexcept 325   8 posix_stream_file::sync_data() noexcept
301   { 326   {
302   #if BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO 327   #if BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO
HITCBC 303   10 if (::fdatasync(fd_) < 0) 328   8 if (::fdatasync(fd_) < 0)
304   #else // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO 329   #else // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO
305   if (::fsync(fd_) < 0) 330   if (::fsync(fd_) < 0)
306   #endif // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO 331   #endif // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO
HITCBC 307   7 return make_err(errno); 332   5 return make_err(errno);
HITCBC 308   3 return {}; 333   3 return {};
309   } 334   }
310   335  
311   inline std::error_code 336   inline std::error_code
HITCBC 312   10 posix_stream_file::sync_all() noexcept 337   8 posix_stream_file::sync_all() noexcept
313   { 338   {
HITCBC 314   10 if (::fsync(fd_) < 0) 339   8 if (::fsync(fd_) < 0)
HITCBC 315   7 return make_err(errno); 340   5 return make_err(errno);
HITCBC 316   3 return {}; 341   3 return {};
317   } 342   }
318   343  
319   inline native_handle_type 344   inline native_handle_type
HITCBC 320   2 posix_stream_file::release() 345   4 posix_stream_file::release()
321   { 346   {
  347 + // A queued op has already copied the fd number; it must not run
  348 + // after the caller closes it and the number is recycled.
HITGNC   349 + 4 cancel();
HITGNC   350 + 4 bump_generation();
HITCBC 322   2 int fd = fd_; 351   4 int fd = fd_;
HITCBC 323   2 fd_ = -1; 352   4 fd_ = -1;
HITCBC 324   2 offset_ = 0; 353   4 offset_ = 0;
HITCBC 325   2 return fd; 354   4 return fd;
326   } 355   }
327   356  
328   inline std::error_code 357   inline std::error_code
HITCBC 329   6 posix_stream_file::assign(native_handle_type handle) noexcept 358   411 posix_stream_file::assign(native_handle_type handle) noexcept
330   { 359   {
ECB 331 - 6 close_file(); 360 + // The public assign() guarantees the object is closed.
HITGNC   361 + 411 if (auto ec = validate_file_fd(handle))
HITGNC   362 + 4 return ec;
  363 +
HITCBC 332   6 fd_ = handle; 364   407 fd_ = handle;
HITCBC 333   6 offset_ = 0; 365   407 offset_ = 0;
HITCBC 334   6 return {}; 366   407 return {};
335   } 367   }
336   368  
337   inline capy::io_result<std::uint64_t> 369   inline capy::io_result<std::uint64_t>
HITCBC 338   30 posix_stream_file::seek( 370   435 posix_stream_file::seek(
339   std::int64_t offset, file_base::seek_basis origin) noexcept 371   std::int64_t offset, file_base::seek_basis origin) noexcept
340   { 372   {
341   // We track offset_ ourselves (not the kernel fd offset) 373   // We track offset_ ourselves (not the kernel fd offset)
342   // because preadv/pwritev use explicit offsets. 374   // because preadv/pwritev use explicit offsets.
343   std::int64_t new_pos; 375   std::int64_t new_pos;
344   376  
HITCBC 345   30 if (origin == file_base::seek_set) 377   435 if (origin == file_base::seek_set)
346   { 378   {
HITCBC 347   14 new_pos = offset; 379   14 new_pos = offset;
348   } 380   }
HITCBC 349   16 else if (origin == file_base::seek_cur) 381   421 else if (origin == file_base::seek_cur)
350   { 382   {
HITCBC 351   5 new_pos = static_cast<std::int64_t>(offset_) + offset; 383   410 new_pos = static_cast<std::int64_t>(offset_) + offset;
352   } 384   }
353   else 385   else
354   { 386   {
355 - struct stat st; 387 + file_stat_t st;
HITCBC 356 - 11 if (::fstat(fd_, &st) < 0) 388 + 11 if (file_fstat(fd_, &st) < 0)
HITCBC 357   5 return {make_err(errno), 0}; 389   5 return {make_err(errno), 0};
HITCBC 358   6 new_pos = st.st_size + offset; 390   6 new_pos = st.st_size + offset;
359   } 391   }
360   392  
HITCBC 361   25 if (new_pos < 0) 393   430 if (new_pos < 0)
HITCBC 362   6 return {make_err(EINVAL), 0}; 394   6 return {make_err(EINVAL), 0};
HITCBC 363   19 if (new_pos > 395   424 if (new_pos >
HITCBC 364 - 19 static_cast<std::int64_t>((std::numeric_limits<off_t>::max)())) 396 + 424 static_cast<std::int64_t>((std::numeric_limits<file_off_t>::max)()))
MISUBC 365   ✗ return {make_err(EOVERFLOW), 0}; 397   ✗ return {make_err(EOVERFLOW), 0};
366   398  
HITCBC 367   19 offset_ = static_cast<std::uint64_t>(new_pos); 399   424 offset_ = static_cast<std::uint64_t>(new_pos);
368   400  
HITCBC 369   19 return {std::error_code{}, offset_}; 401   424 return {std::error_code{}, offset_};
370   } 402   }
371   403  
372   // -- file_op completion handler -- 404   // -- file_op completion handler --
373   // (read_some, write_some, do_read_work, do_write_work are 405   // (read_some, write_some, do_read_work, do_write_work are
374   // defined in posix_stream_file_service.hpp after the service) 406   // defined in posix_stream_file_service.hpp after the service)
375   407  
376   inline void 408   inline void
HITCBC 377   222 posix_stream_file::file_op::operator()() 409   624 posix_stream_file::file_op::operator()()
378   { 410   {
HITCBC 379   222 stop_cb.reset(); 411   624 stop_cb.reset();
380   412  
381   // Empty buffers never reach the pool (diverted at initiation), so 413   // Empty buffers never reach the pool (diverted at initiation), so
382   // empty_buffer stays false and a 0-byte read is a genuine EOF. 414   // empty_buffer stays false and a 0-byte read is a genuine EOF.
HITCBC 383   430 decode_io_result( 415   1234 decode_io_result(
HITCBC 384   222 ec_out, bytes_out, cancelled.load(std::memory_order_acquire), 416   624 ec_out, bytes_out, cancelled.load(std::memory_order_acquire),
HITCBC 385   222 errn != 0 ? make_err(errn) : std::error_code{}, is_read, 417   624 errn != 0 ? make_err(errn) : std::error_code{}, is_read,
386   bytes_transferred, /*empty_buffer=*/false); 418   bytes_transferred, /*empty_buffer=*/false);
387   419  
388   // Move impl_ptr to a local so members remain valid through 420   // Move impl_ptr to a local so members remain valid through
389   // dispatch — impl_ptr may be the last shared_ptr keeping 421   // dispatch — impl_ptr may be the last shared_ptr keeping
390   // the parent posix_stream_file (which embeds this file_op) alive. 422   // the parent posix_stream_file (which embeds this file_op) alive.
HITCBC 391   222 auto prevent_destroy = std::move(impl_ptr); 423   624 auto prevent_destroy = std::move(impl_ptr);
HITCBC 392   222 ex.on_work_finished(); 424   624 ex.on_work_finished();
HITCBC 393   222 cont.h = h; 425   624 cont.h = h;
HITCBC 394   222 dispatch_coro(ex, cont).resume(); 426   624 dispatch_coro(ex, cont).resume();
HITCBC 395   222 } 427   624 }
396   428  
397   inline void 429   inline void
HITCBC 398   2 posix_stream_file::file_op::destroy() 430   2 posix_stream_file::file_op::destroy()
399   { 431   {
HITCBC 400   2 stop_cb.reset(); 432   2 stop_cb.reset();
HITCBC 401   2 auto local_ex = ex; 433   2 auto local_ex = ex;
HITCBC 402   2 impl_ptr.reset(); 434   2 impl_ptr.reset();
HITCBC 403   2 local_ex.on_work_finished(); 435   2 local_ex.on_work_finished();
HITCBC 404   2 } 436   2 }
405   437  
406   } // namespace boost::corosio::detail 438   } // namespace boost::corosio::detail
407   439  
408   #endif // BOOST_COROSIO_POSIX 440   #endif // BOOST_COROSIO_POSIX
409   441  
410   #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_HPP 442   #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_HPP