diff --git a/include/boost/corosio/detail/buffer_param.hpp b/include/boost/corosio/detail/buffer_param.hpp index 574d722a5..65514569f 100644 --- a/include/boost/corosio/detail/buffer_param.hpp +++ b/include/boost/corosio/detail/buffer_param.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -73,14 +74,27 @@ namespace boost::corosio { to the buffers obtained from `copy_to`. The const-cast exists solely to provide a uniform interface for platform I/O calls. + @note Do NOT `reinterpret_cast` the `mutable_buffer` array to + `iovec*`/`WSABUF*` and pass it to the OS, even when the layouts + match: no object of the target type exists in that storage, so the + access is undefined behavior (and `mutable_buffer` is not an + implicit-lifetime type, so `std::start_lifetime_as_array` cannot + rescue it). Copy field by field into a real platform array instead. + @code // For write operations (const buffers): void submit_write(buffer_param p) { capy::mutable_buffer bufs[8]; auto n = p.copy_to(bufs, 8); - // bufs[] may reference const data - DO NOT WRITE - writev(fd, reinterpret_cast(bufs), n); // OK: read-only + iovec iov[8]; + for (std::size_t i = 0; i < n; ++i) + { + // bufs[] may reference const data - DO NOT WRITE through iov + iov[i].iov_base = bufs[i].data(); + iov[i].iov_len = bufs[i].size(); + } + writev(fd, iov, n); // read-only } // For read operations (mutable buffers): @@ -88,8 +102,14 @@ namespace boost::corosio { { capy::mutable_buffer bufs[8]; auto n = p.copy_to(bufs, 8); - // bufs[] references mutable data - safe to write - readv(fd, reinterpret_cast(bufs), n); // OK: writing + iovec iov[8]; + for (std::size_t i = 0; i < n; ++i) + { + // bufs[] references mutable data - safe to write + iov[i].iov_base = bufs[i].data(); + iov[i].iov_len = bufs[i].size(); + } + readv(fd, iov, n); // writing } @endcode @@ -131,10 +151,17 @@ namespace boost::corosio { buffer_param p, std::coroutine_handle<> h) { - // CORRECT: Unroll immediately into platform structure + // CORRECT: Unroll immediately into platform structure. + // Copy field by field; do NOT reinterpret_cast the + // mutable_buffer array to iovec* (see the @note above). + capy::mutable_buffer bufs[16]; + std::size_t n = p.copy_to(bufs, 16); iovec vecs[16]; - std::size_t n = p.copy_to( - reinterpret_cast(vecs), 16); + for (std::size_t i = 0; i < n; ++i) + { + vecs[i].iov_base = bufs[i].data(); + vecs[i].iov_len = bufs[i].size(); + } // CORRECT: Use unrolled buffers for system call now submit_to_io_uring(vecs, n, h); diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_dgram_ops.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_dgram_ops.hpp index 94e5770a8..4324b2767 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_dgram_ops.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_dgram_ops.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -89,10 +90,7 @@ struct uring_dgram_send_op : io_uring_op cqe_flags = 0; msg_flags = flags; - iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + iovec_count = copy_to_iovec(buffers, iovecs); msg = {}; msg.msg_iov = iovecs; @@ -231,10 +229,7 @@ struct uring_dgram_recv_op : io_uring_op cqe_flags = 0; msg_flags = flags; - iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + iovec_count = copy_to_iovec(buffers, iovecs); msg = {}; // For the zero-iovec bypass path the caller pushes the slot diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_file_ops.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_file_ops.hpp index 80bf318af..e9ea08f4a 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_file_ops.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_file_ops.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -84,10 +85,7 @@ struct uring_file_read_op_base : io_uring_op impl_ptr = std::move(impl); res = 0; cqe_flags = 0; - iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + iovec_count = copy_to_iovec(buffers, iovecs); empty_buffer = (iovec_count == 0); start(token); } @@ -225,10 +223,7 @@ struct uring_file_write_op_base : io_uring_op impl_ptr = std::move(impl); res = 0; cqe_flags = 0; - iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + iovec_count = copy_to_iovec(buffers, iovecs); empty_buffer = (iovec_count == 0); start(token); } diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_socket_ops.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_socket_ops.hpp index 2373a09cc..7c4f4e65e 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_socket_ops.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_socket_ops.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -47,6 +48,35 @@ namespace boost::corosio::detail { /// per-op memory. inline constexpr std::size_t io_uring_max_iov = 16; +/** Fill an `iovec` array from a buffer sequence without type-punning. + + `buffer_param::copy_to` writes `capy::mutable_buffer` objects. Those + cannot legally alias `iovec` storage: no `mutable_buffer` lives at + that address, and `mutable_buffer` is not an implicit-lifetime type, + so its lifetime cannot be started in place (which also rules out + `std::start_lifetime_as_array`). We copy into a real `mutable_buffer` + scratch array, then translate field by field into the caller's + `iovec` array. Zero-size buffers are already skipped by `copy_to`. + + @param buffers The source buffer sequence. + @param iovecs Destination array, filled with the non-zero buffers. + @return The number of `iovec` entries written. +*/ +inline int +copy_to_iovec( + buffer_param const& buffers, + iovec (&iovecs)[io_uring_max_iov]) noexcept +{ + capy::mutable_buffer bufs[io_uring_max_iov]; + std::size_t const n = buffers.copy_to(bufs, io_uring_max_iov); + for (std::size_t i = 0; i < n; ++i) + { + iovecs[i].iov_base = bufs[i].data(); + iovecs[i].iov_len = bufs[i].size(); + } + return static_cast(n); +} + /** Resolve ec_out/bytes_out from a CQE result for a completed I/O op. Shared by read, write, and connect handlers. For reads, `res == 0` @@ -117,10 +147,7 @@ struct uring_read_op : io_uring_op spec_state = spec; res = 0; cqe_flags = 0; - iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + iovec_count = copy_to_iovec(buffers, iovecs); empty_buffer = (iovec_count == 0); start(token); } @@ -225,10 +252,7 @@ struct uring_write_op : io_uring_op spec_state = spec; res = 0; cqe_flags = 0; - iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + iovec_count = copy_to_iovec(buffers, iovecs); empty_buffer = (iovec_count == 0); if (!empty_buffer) { diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp index 9a34cc720..81dc7927a 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -152,10 +153,7 @@ class BOOST_COROSIO_DECL io_uring_tcp_socket final std::size_t* bytes) override { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -232,10 +230,7 @@ class BOOST_COROSIO_DECL io_uring_tcp_socket final std::size_t* bytes) override { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -907,10 +902,7 @@ class BOOST_COROSIO_DECL io_uring_local_stream_socket final std::size_t* bytes) override { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -987,10 +979,7 @@ class BOOST_COROSIO_DECL io_uring_local_stream_socket final std::size_t* bytes) override { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -1811,10 +1800,7 @@ class BOOST_COROSIO_DECL io_uring_udp_socket final std::size_t* bytes) { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -1899,10 +1885,7 @@ class BOOST_COROSIO_DECL io_uring_udp_socket final std::size_t* bytes) { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -2365,10 +2348,7 @@ class BOOST_COROSIO_DECL io_uring_local_datagram_socket final std::size_t* bytes) { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0); @@ -2453,10 +2433,7 @@ class BOOST_COROSIO_DECL io_uring_local_datagram_socket final std::size_t* bytes) { iovec iovecs[io_uring_max_iov]; - int iovec_count = static_cast( - buffers.copy_to( - reinterpret_cast(iovecs), - io_uring_max_iov)); + int iovec_count = copy_to_iovec(buffers, iovecs); bool stop_now = token.stop_possible() && token.stop_requested(); bool empty_buf = (iovec_count == 0);