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