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