100.00% Lines (83/83) 100.00% Functions (14/14)
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_SELECT_SELECT_TRAITS_HPP 10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 11   #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
12   12  
13   #include <boost/corosio/detail/platform.hpp> 13   #include <boost/corosio/detail/platform.hpp>
14   14  
15   #if BOOST_COROSIO_HAS_SELECT 15   #if BOOST_COROSIO_HAS_SELECT
16   16  
17   #include <boost/corosio/native/detail/make_err.hpp> 17   #include <boost/corosio/native/detail/make_err.hpp>
18   #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp> 18   #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19   19  
20   #include <system_error> 20   #include <system_error>
21   #include <tuple> 21   #include <tuple>
22   22  
23   #include <errno.h> 23   #include <errno.h>
24   #include <fcntl.h> 24   #include <fcntl.h>
25   #include <netinet/in.h> 25   #include <netinet/in.h>
26   #include <sys/select.h> 26   #include <sys/select.h>
27   #include <sys/socket.h> 27   #include <sys/socket.h>
  28 + #include <sys/uio.h>
28   #include <unistd.h> 29   #include <unistd.h>
29   30  
30   /* select backend traits. 31   /* select backend traits.
31   32  
32   Captures the platform-specific behavior of the portable select() backend: 33   Captures the platform-specific behavior of the portable select() backend:
33   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation, 34   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation,
34   mandatory SO_NOSIGPIPE where the platform defines it, 35   mandatory SO_NOSIGPIPE where the platform defines it,
35   sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for 36   sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for
36   accepted connections. 37   accepted connections.
37   */ 38   */
38   39  
39   namespace boost::corosio::detail { 40   namespace boost::corosio::detail {
40   41  
41   class select_scheduler; 42   class select_scheduler;
42   43  
43   struct select_traits 44   struct select_traits
44   { 45   {
45   using scheduler_type = select_scheduler; 46   using scheduler_type = select_scheduler;
46   using desc_state_type = reactor_descriptor_state; 47   using desc_state_type = reactor_descriptor_state;
47   48  
48   static constexpr bool needs_write_notification = true; 49   static constexpr bool needs_write_notification = true;
49   50  
50   // No extra per-socket state or lifecycle hooks needed for select. 51   // No extra per-socket state or lifecycle hooks needed for select.
51   struct stream_socket_hook 52   struct stream_socket_hook
52   { 53   {
HITCBC 53   107 std::error_code on_set_option( 54   107 std::error_code on_set_option(
54   int fd, 55   int fd,
55   int level, 56   int level,
56   int optname, 57   int optname,
57   void const* data, 58   void const* data,
58   std::size_t size) noexcept 59   std::size_t size) noexcept
59   { 60   {
HITCBC 60   107 if (::setsockopt( 61   107 if (::setsockopt(
HITCBC 61   107 fd, level, optname, data, static_cast<socklen_t>(size)) != 62   107 fd, level, optname, data, static_cast<socklen_t>(size)) !=
62   0) 63   0)
HITCBC 63   4 return make_err(errno); 64   4 return make_err(errno);
HITCBC 64   103 return {}; 65   103 return {};
65   } 66   }
HITCBC 66   19100 static void pre_shutdown(int) noexcept {} 67   19276 static void pre_shutdown(int) noexcept {}
HITCBC 67   6201 static void pre_destroy(int) noexcept {} 68   6260 static void pre_destroy(int) noexcept {}
68   }; 69   };
69   70  
70   struct write_policy 71   struct write_policy
71   { 72   {
HITCBC 72   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept 73   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
73   { 74   {
HITCBC 74   72 msghdr msg{}; 75   72 msghdr msg{};
HITCBC 75   72 msg.msg_iov = iovecs; 76   72 msg.msg_iov = iovecs;
HITCBC 76   72 msg.msg_iovlen = static_cast<std::size_t>(count); 77   72 msg.msg_iovlen = static_cast<std::size_t>(count);
77   78  
78   #ifdef MSG_NOSIGNAL 79   #ifdef MSG_NOSIGNAL
HITCBC 79   72 constexpr int send_flags = MSG_NOSIGNAL; 80   72 constexpr int send_flags = MSG_NOSIGNAL;
80   #else 81   #else
81   constexpr int send_flags = 0; 82   constexpr int send_flags = 0;
82   #endif 83   #endif
83   84  
84   ssize_t n; 85   ssize_t n;
85   do 86   do
86   { 87   {
HITCBC 87   73 n = ::sendmsg(fd, &msg, send_flags); 88   73 n = ::sendmsg(fd, &msg, send_flags);
88   } 89   }
HITCBC 89   73 while (n < 0 && errno == EINTR); 90   73 while (n < 0 && errno == EINTR);
HITCBC 90   72 return n; 91   72 return n;
91   } 92   }
92   93  
93   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use 94   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use
94   // send() to suppress SIGPIPE inline; otherwise fall back to 95   // send() to suppress SIGPIPE inline; otherwise fall back to
95   // write() and rely on the SO_NOSIGPIPE set in accept_policy 96   // write() and rely on the SO_NOSIGPIPE set in accept_policy
96   // and set_fd_options. 97   // and set_fd_options.
97   static ssize_t 98   static ssize_t
HITCBC 98   107681 write_one(int fd, void const* data, std::size_t size) noexcept 99   103468 write_one(int fd, void const* data, std::size_t size) noexcept
99   { 100   {
100   ssize_t n; 101   ssize_t n;
101   do 102   do
102   { 103   {
103   #ifdef MSG_NOSIGNAL 104   #ifdef MSG_NOSIGNAL
HITCBC 104   107682 n = ::send(fd, data, size, MSG_NOSIGNAL); 105   103469 n = ::send(fd, data, size, MSG_NOSIGNAL);
105   #else 106   #else
106   n = ::write(fd, data, size); 107   n = ::write(fd, data, size);
107   #endif 108   #endif
  109 + }
HITGNC   110 + 103469 while (n < 0 && errno == EINTR);
HITGNC   111 + 103468 return n;
  112 + }
  113 + };
  114 +
  115 + // Descriptors are not sockets: sendmsg() fails with ENOTSOCK on a
  116 + // pipe or character device, so the write path is writev()/write()
  117 + // and SIGPIPE suppression is structurally unavailable -- MSG_NOSIGNAL
  118 + // is a send() flag and SO_NOSIGPIPE a socket option. A write to a
  119 + // pipe whose read end has closed raises SIGPIPE, exactly as a plain
  120 + // write(2) would; callers install SIG_IGN.
  121 + struct descriptor_write_policy
  122 + {
HITGNC   123 + 2 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
  124 + {
  125 + ssize_t n;
  126 + do
  127 + {
HITGNC   128 + 2 n = ::writev(fd, iovecs, count);
  129 + }
HITGNC   130 + 2 while (n < 0 && errno == EINTR);
HITGNC   131 + 2 return n;
  132 + }
  133 +
  134 + // Single-buffer fast path: skips the kernel's iov_iter setup.
  135 + static ssize_t
HITGNC   136 + 2 write_one(int fd, void const* data, std::size_t size) noexcept
  137 + {
  138 + ssize_t n;
  139 + do
  140 + {
HITGNC   141 + 2 n = ::write(fd, data, size);
108   } 142   }
HITCBC 109   107682 while (n < 0 && errno == EINTR); 143   2 while (n < 0 && errno == EINTR);
HITCBC 110   107681 return n; 144   2 return n;
111   } 145   }
112   }; 146   };
113   147  
114   struct accept_policy 148   struct accept_policy
115   { 149   {
116   static int 150   static int
HITCBC 117   4072 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept 151   4112 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
118   { 152   {
HITCBC 119   4072 addrlen = sizeof(peer); 153   4112 addrlen = sizeof(peer);
120   int new_fd; 154   int new_fd;
121   do 155   do
122   { 156   {
123   new_fd = 157   new_fd =
HITCBC 124   4073 ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen); 158   4113 ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
125   } 159   }
HITCBC 126   4073 while (new_fd < 0 && errno == EINTR); 160   4113 while (new_fd < 0 && errno == EINTR);
127   161  
HITCBC 128   4072 if (new_fd < 0) 162   4112 if (new_fd < 0)
HITCBC 129   2041 return new_fd; 163   2061 return new_fd;
130   164  
HITCBC 131   2031 if (new_fd >= FD_SETSIZE) 165   2051 if (new_fd >= FD_SETSIZE)
132   { 166   {
HITCBC 133   1 ::close(new_fd); 167   1 ::close(new_fd);
HITCBC 134   1 errno = EMFILE; 168   1 errno = EMFILE;
HITCBC 135   1 return -1; 169   1 return -1;
136   } 170   }
137   171  
HITCBC 138   2030 int flags = ::fcntl(new_fd, F_GETFL, 0); 172   2050 int flags = ::fcntl(new_fd, F_GETFL, 0);
HITCBC 139   2030 if (flags == -1) 173   2050 if (flags == -1)
140   { 174   {
HITCBC 141   1 int err = errno; 175   1 int err = errno;
HITCBC 142   1 ::close(new_fd); 176   1 ::close(new_fd);
HITCBC 143   1 errno = err; 177   1 errno = err;
HITCBC 144   1 return -1; 178   1 return -1;
145   } 179   }
146   180  
HITCBC 147   2029 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1) 181   2049 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
148   { 182   {
HITCBC 149   1 int err = errno; 183   1 int err = errno;
HITCBC 150   1 ::close(new_fd); 184   1 ::close(new_fd);
HITCBC 151   1 errno = err; 185   1 errno = err;
HITCBC 152   1 return -1; 186   1 return -1;
153   } 187   }
154   188  
HITCBC 155   2028 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1) 189   2048 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
156   { 190   {
HITCBC 157   1 int err = errno; 191   1 int err = errno;
HITCBC 158   1 ::close(new_fd); 192   1 ::close(new_fd);
HITCBC 159   1 errno = err; 193   1 errno = err;
HITCBC 160   1 return -1; 194   1 return -1;
161   } 195   }
162   196  
163   #ifdef SO_NOSIGPIPE 197   #ifdef SO_NOSIGPIPE
164   // MSG_NOSIGNAL is not universal across the platforms this 198   // MSG_NOSIGNAL is not universal across the platforms this
165   // portable backend covers, and the write() the fast path 199   // portable backend covers, and the write() the fast path
166   // falls back to there takes no flag at all; SO_NOSIGPIPE is 200   // falls back to there takes no flag at all; SO_NOSIGPIPE is
167   // the per-descriptor guard that covers both. Treat failure 201   // the per-descriptor guard that covers both. Treat failure
168   // as fatal, matching the kqueue backend. 202   // as fatal, matching the kqueue backend.
169   int one = 1; 203   int one = 1;
170   if (::setsockopt( 204   if (::setsockopt(
171   new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0) 205   new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
172   { 206   {
173   int err = errno; 207   int err = errno;
174   ::close(new_fd); 208   ::close(new_fd);
175   errno = err; 209   errno = err;
176   return -1; 210   return -1;
177   } 211   }
178   #endif 212   #endif
179   213  
HITCBC 180   2027 return new_fd; 214   2047 return new_fd;
181   } 215   }
182   }; 216   };
183   217  
184   // Create a plain socket (no atomic flags -- select is POSIX-portable). 218   // Create a plain socket (no atomic flags -- select is POSIX-portable).
HITCBC 185   2637 static int create_socket(int family, int type, int protocol) noexcept 219   2656 static int create_socket(int family, int type, int protocol) noexcept
186   { 220   {
HITCBC 187   2637 return ::socket(family, type, protocol); 221   2656 return ::socket(family, type, protocol);
188   } 222   }
189   223  
190   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE. 224   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
191   // Caller is responsible for closing fd on error. 225   // Caller is responsible for closing fd on error.
HITCBC 192   2631 static std::error_code set_fd_options(int fd) noexcept 226   2650 static std::error_code set_fd_options(int fd) noexcept
193   { 227   {
HITCBC 194   2631 int flags = ::fcntl(fd, F_GETFL, 0); 228   2650 int flags = ::fcntl(fd, F_GETFL, 0);
HITCBC 195   2631 if (flags == -1) 229   2650 if (flags == -1)
HITCBC 196   2 return make_err(errno); 230   2 return make_err(errno);
HITCBC 197   2629 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) 231   2648 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
HITCBC 198   2 return make_err(errno); 232   2 return make_err(errno);
HITCBC 199   2627 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) 233   2646 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
HITCBC 200   2 return make_err(errno); 234   2 return make_err(errno);
201   235  
HITCBC 202   2625 if (fd >= FD_SETSIZE) 236   2644 if (fd >= FD_SETSIZE)
HITCBC 203   2 return make_err(EMFILE); 237   2 return make_err(EMFILE);
204   238  
205   #ifdef SO_NOSIGPIPE 239   #ifdef SO_NOSIGPIPE
206   // MSG_NOSIGNAL is not universal across the platforms this 240   // MSG_NOSIGNAL is not universal across the platforms this
207   // portable backend covers, and the write() the fast path falls 241   // portable backend covers, and the write() the fast path falls
208   // back to there takes no flag at all; SO_NOSIGPIPE is the 242   // back to there takes no flag at all; SO_NOSIGPIPE is the
209   // per-descriptor guard that covers both. Treat failure as fatal, 243   // per-descriptor guard that covers both. Treat failure as fatal,
210   // matching the kqueue backend. Caller closes fd on error. 244   // matching the kqueue backend. Caller closes fd on error.
211   { 245   {
212   int one = 1; 246   int one = 1;
213   if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 247   if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) !=
214   0) 248   0)
215   return make_err(errno); 249   return make_err(errno);
216   } 250   }
217   #endif 251   #endif
218   252  
HITCBC 219   2623 return {}; 253   2642 return {};
220   } 254   }
221   255  
222   // Apply protocol-specific options after socket creation. 256   // Apply protocol-specific options after socket creation.
223   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort). 257   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
HITCBC 224   2246 static std::error_code configure_ip_socket(int fd, int family) noexcept 258   2265 static std::error_code configure_ip_socket(int fd, int family) noexcept
225   { 259   {
HITCBC 226   2246 if (family == AF_INET6) 260   2265 if (family == AF_INET6)
227   { 261   {
HITCBC 228   23 int one = 1; 262   23 int one = 1;
229   std::ignore = 263   std::ignore =
HITCBC 230   23 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 264   23 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
231   } 265   }
232   266  
HITCBC 233   2246 return set_fd_options(fd); 267   2265 return set_fd_options(fd);
234   } 268   }
235   269  
236   // Apply protocol-specific options for acceptor sockets. 270   // Apply protocol-specific options for acceptor sockets.
237   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort). 271   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
HITCBC 238   276 static std::error_code configure_ip_acceptor(int fd, int family) noexcept 272   276 static std::error_code configure_ip_acceptor(int fd, int family) noexcept
239   { 273   {
HITCBC 240   276 if (family == AF_INET6) 274   276 if (family == AF_INET6)
241   { 275   {
HITCBC 242   11 int val = 0; 276   11 int val = 0;
243   std::ignore = 277   std::ignore =
HITCBC 244   11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); 278   11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
245   } 279   }
246   280  
HITCBC 247   276 return set_fd_options(fd); 281   276 return set_fd_options(fd);
248   } 282   }
249   283  
250   // Apply options for local (unix) sockets. 284   // Apply options for local (unix) sockets.
HITCBC 251   109 static std::error_code configure_local_socket(int fd) noexcept 285   109 static std::error_code configure_local_socket(int fd) noexcept
252   { 286   {
HITCBC 253   109 return set_fd_options(fd); 287   109 return set_fd_options(fd);
254   } 288   }
255   289  
256   // Non-mutating validation for fds adopted via assign(). Select's 290   // Non-mutating validation for fds adopted via assign(). Select's
257   // reactor cannot handle fds above FD_SETSIZE, so reject them up 291   // reactor cannot handle fds above FD_SETSIZE, so reject them up
258   // front instead of letting FD_SET clobber unrelated memory. 292   // front instead of letting FD_SET clobber unrelated memory.
HITCBC 259   155 static std::error_code validate_assigned_fd(int fd) noexcept 293   155 static std::error_code validate_assigned_fd(int fd) noexcept
260   { 294   {
HITCBC 261   155 if (fd >= FD_SETSIZE) 295   155 if (fd >= FD_SETSIZE)
HITCBC 262   2 return make_err(EMFILE); 296   2 return make_err(EMFILE);
HITCBC 263   153 return {}; 297   153 return {};
264   } 298   }
265   }; 299   };
266   300  
267   } // namespace boost::corosio::detail 301   } // namespace boost::corosio::detail
268   302  
269   #endif // BOOST_COROSIO_HAS_SELECT 303   #endif // BOOST_COROSIO_HAS_SELECT
270   304  
271   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 305   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP