100.00% Lines (77/77) 100.00% Functions (12/12)
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 <unistd.h> 28   #include <unistd.h>
29   29  
30   /* select backend traits. 30   /* select backend traits.
31   31  
32   Captures the platform-specific behavior of the portable select() backend: 32   Captures the platform-specific behavior of the portable select() backend:
33   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation, 33   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation,
34   mandatory SO_NOSIGPIPE where the platform defines it, 34   mandatory SO_NOSIGPIPE where the platform defines it,
35   sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for 35   sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for
36   accepted connections. 36   accepted connections.
37   */ 37   */
38   38  
39   namespace boost::corosio::detail { 39   namespace boost::corosio::detail {
40   40  
41   class select_scheduler; 41   class select_scheduler;
42   42  
43   struct select_traits 43   struct select_traits
44   { 44   {
45 - using scheduler_type = select_scheduler; 45 + using scheduler_type = select_scheduler;
46 - using desc_state_type = reactor_descriptor_state; 46 + using desc_state_type = reactor_descriptor_state;
47   47  
48   static constexpr bool needs_write_notification = true; 48   static constexpr bool needs_write_notification = true;
49   49  
50   // No extra per-socket state or lifecycle hooks needed for select. 50   // No extra per-socket state or lifecycle hooks needed for select.
51   struct stream_socket_hook 51   struct stream_socket_hook
52   { 52   {
HITCBC 53   107 std::error_code on_set_option( 53   107 std::error_code on_set_option(
54 - int fd, 54 + int fd, int level, int optname,
55 - int level, 55 + void const* data, std::size_t size) noexcept
56 - int optname,  
57 - void const* data,  
58 - std::size_t size) noexcept  
59   { 56   {
HITCBC 60   107 if (::setsockopt( 57   107 if (::setsockopt(
ECB 61 - 107 fd, level, optname, data, static_cast<socklen_t>(size)) != 58 + fd, level, optname, data,
HITGIC 62 - 0) 59 + 107 static_cast<socklen_t>(size)) != 0)
HITCBC 63   4 return make_err(errno); 60   4 return make_err(errno);
HITCBC 64   103 return {}; 61   103 return {};
65   } 62   }
HITCBC 66   19101 static void pre_shutdown(int) noexcept {} 63   18304 static void pre_shutdown(int) noexcept {}
HITCBC 67   6199 static void pre_destroy(int) noexcept {} 64   5933 static void pre_destroy(int) noexcept {}
68   }; 65   };
69   66  
70   struct write_policy 67   struct write_policy
71   { 68   {
HITCBC 72   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept 69   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
73   { 70   {
HITCBC 74   72 msghdr msg{}; 71   72 msghdr msg{};
HITCBC 75   72 msg.msg_iov = iovecs; 72   72 msg.msg_iov = iovecs;
HITCBC 76   72 msg.msg_iovlen = static_cast<std::size_t>(count); 73   72 msg.msg_iovlen = static_cast<std::size_t>(count);
77   74  
78   #ifdef MSG_NOSIGNAL 75   #ifdef MSG_NOSIGNAL
HITCBC 79   72 constexpr int send_flags = MSG_NOSIGNAL; 76   72 constexpr int send_flags = MSG_NOSIGNAL;
80   #else 77   #else
81   constexpr int send_flags = 0; 78   constexpr int send_flags = 0;
82   #endif 79   #endif
83   80  
84   ssize_t n; 81   ssize_t n;
85   do 82   do
86   { 83   {
HITCBC 87   73 n = ::sendmsg(fd, &msg, send_flags); 84   73 n = ::sendmsg(fd, &msg, send_flags);
88   } 85   }
HITCBC 89   73 while (n < 0 && errno == EINTR); 86   73 while (n < 0 && errno == EINTR);
HITCBC 90   72 return n; 87   72 return n;
91   } 88   }
92   89  
93   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use 90   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use
94   // send() to suppress SIGPIPE inline; otherwise fall back to 91   // send() to suppress SIGPIPE inline; otherwise fall back to
95   // write() and rely on the SO_NOSIGPIPE set in accept_policy 92   // write() and rely on the SO_NOSIGPIPE set in accept_policy
96   // and set_fd_options. 93   // and set_fd_options.
HITGIC 97 - static ssize_t 94 + 106717 static ssize_t write_one(
ECB 98 - 109937 write_one(int fd, void const* data, std::size_t size) noexcept 95 + int fd, void const* data, std::size_t size) noexcept
99   { 96   {
100   ssize_t n; 97   ssize_t n;
101   do 98   do
102   { 99   {
103   #ifdef MSG_NOSIGNAL 100   #ifdef MSG_NOSIGNAL
HITCBC 104   109938 n = ::send(fd, data, size, MSG_NOSIGNAL); 101   106718 n = ::send(fd, data, size, MSG_NOSIGNAL);
105   #else 102   #else
106   n = ::write(fd, data, size); 103   n = ::write(fd, data, size);
107   #endif 104   #endif
108   } 105   }
HITCBC 109   109938 while (n < 0 && errno == EINTR); 106   106718 while (n < 0 && errno == EINTR);
HITCBC 110   109937 return n; 107   106717 return n;
111   } 108   }
112   }; 109   };
113   110  
114   struct accept_policy 111   struct accept_policy
115   { 112   {
HITGIC 116 - static int 113 + 3906 static int do_accept(
ECB 117 - 4084 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept 114 + int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
118   { 115   {
HITCBC 119   4084 addrlen = sizeof(peer); 116   3906 addrlen = sizeof(peer);
120   int new_fd; 117   int new_fd;
121   do 118   do
122   { 119   {
HITGIC 123 - new_fd = 120 + 3907 new_fd = ::accept(
ECB 124 - 4085 ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen); 121 + fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
125   } 122   }
HITCBC 126   4085 while (new_fd < 0 && errno == EINTR); 123   3907 while (new_fd < 0 && errno == EINTR);
127   124  
HITCBC 128   4084 if (new_fd < 0) 125   3906 if (new_fd < 0)
HITCBC 129   2049 return new_fd; 126   1960 return new_fd;
130   127  
HITCBC 131   2035 if (new_fd >= FD_SETSIZE) 128   1946 if (new_fd >= FD_SETSIZE)
132   { 129   {
HITCBC 133   1 ::close(new_fd); 130   1 ::close(new_fd);
HITCBC 134   1 errno = EMFILE; 131   1 errno = EMFILE;
HITCBC 135   1 return -1; 132   1 return -1;
136   } 133   }
137   134  
HITCBC 138   2034 int flags = ::fcntl(new_fd, F_GETFL, 0); 135   1945 int flags = ::fcntl(new_fd, F_GETFL, 0);
HITCBC 139   2034 if (flags == -1) 136   1945 if (flags == -1)
140   { 137   {
HITCBC 141   1 int err = errno; 138   1 int err = errno;
HITCBC 142   1 ::close(new_fd); 139   1 ::close(new_fd);
HITCBC 143   1 errno = err; 140   1 errno = err;
HITCBC 144   1 return -1; 141   1 return -1;
145   } 142   }
146   143  
HITCBC 147   2033 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1) 144   1944 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
148   { 145   {
HITCBC 149   1 int err = errno; 146   1 int err = errno;
HITCBC 150   1 ::close(new_fd); 147   1 ::close(new_fd);
HITCBC 151   1 errno = err; 148   1 errno = err;
HITCBC 152   1 return -1; 149   1 return -1;
153   } 150   }
154   151  
HITCBC 155   2032 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1) 152   1943 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
156   { 153   {
HITCBC 157   1 int err = errno; 154   1 int err = errno;
HITCBC 158   1 ::close(new_fd); 155   1 ::close(new_fd);
HITCBC 159   1 errno = err; 156   1 errno = err;
HITCBC 160   1 return -1; 157   1 return -1;
161   } 158   }
162   159  
163   #ifdef SO_NOSIGPIPE 160   #ifdef SO_NOSIGPIPE
164   // MSG_NOSIGNAL is not universal across the platforms this 161   // MSG_NOSIGNAL is not universal across the platforms this
165   // portable backend covers, and the write() the fast path 162   // portable backend covers, and the write() the fast path
166   // falls back to there takes no flag at all; SO_NOSIGPIPE is 163   // falls back to there takes no flag at all; SO_NOSIGPIPE is
167   // the per-descriptor guard that covers both. Treat failure 164   // the per-descriptor guard that covers both. Treat failure
168   // as fatal, matching the kqueue backend. 165   // as fatal, matching the kqueue backend.
169   int one = 1; 166   int one = 1;
170   if (::setsockopt( 167   if (::setsockopt(
171 - new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0) 168 + new_fd, SOL_SOCKET, SO_NOSIGPIPE,
  169 + &one, sizeof(one)) != 0)
172   { 170   {
173   int err = errno; 171   int err = errno;
174   ::close(new_fd); 172   ::close(new_fd);
175   errno = err; 173   errno = err;
176   return -1; 174   return -1;
177   } 175   }
178   #endif 176   #endif
179   177  
HITCBC 180   2031 return new_fd; 178   1942 return new_fd;
181   } 179   }
182   }; 180   };
183   181  
184   // Create a plain socket (no atomic flags -- select is POSIX-portable). 182   // 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 183   2549 static int create_socket(int family, int type, int protocol) noexcept
186   { 184   {
HITCBC 187   2637 return ::socket(family, type, protocol); 185   2549 return ::socket(family, type, protocol);
188   } 186   }
189   187  
190   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE. 188   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
191   // Caller is responsible for closing fd on error. 189   // Caller is responsible for closing fd on error.
HITCBC 192   2631 static std::error_code set_fd_options(int fd) noexcept 190   2543 static std::error_code set_fd_options(int fd) noexcept
193   { 191   {
HITCBC 194   2631 int flags = ::fcntl(fd, F_GETFL, 0); 192   2543 int flags = ::fcntl(fd, F_GETFL, 0);
HITCBC 195   2631 if (flags == -1) 193   2543 if (flags == -1)
HITCBC 196   2 return make_err(errno); 194   2 return make_err(errno);
HITCBC 197   2629 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) 195   2541 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
HITCBC 198   2 return make_err(errno); 196   2 return make_err(errno);
HITCBC 199   2627 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) 197   2539 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
HITCBC 200   2 return make_err(errno); 198   2 return make_err(errno);
201   199  
HITCBC 202   2625 if (fd >= FD_SETSIZE) 200   2537 if (fd >= FD_SETSIZE)
HITCBC 203   2 return make_err(EMFILE); 201   2 return make_err(EMFILE);
204   202  
205   #ifdef SO_NOSIGPIPE 203   #ifdef SO_NOSIGPIPE
206   // MSG_NOSIGNAL is not universal across the platforms this 204   // MSG_NOSIGNAL is not universal across the platforms this
207   // portable backend covers, and the write() the fast path falls 205   // portable backend covers, and the write() the fast path falls
208   // back to there takes no flag at all; SO_NOSIGPIPE is the 206   // back to there takes no flag at all; SO_NOSIGPIPE is the
209   // per-descriptor guard that covers both. Treat failure as fatal, 207   // per-descriptor guard that covers both. Treat failure as fatal,
210   // matching the kqueue backend. Caller closes fd on error. 208   // matching the kqueue backend. Caller closes fd on error.
211   { 209   {
212   int one = 1; 210   int one = 1;
213 - if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 211 + if (::setsockopt(
214 - 0) 212 + fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
215   return make_err(errno); 213   return make_err(errno);
216   } 214   }
217   #endif 215   #endif
218   216  
HITCBC 219   2623 return {}; 217   2535 return {};
220   } 218   }
221   219  
222   // Apply protocol-specific options after socket creation. 220   // Apply protocol-specific options after socket creation.
223   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort). 221   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
ECB 224 - 2247 static std::error_code configure_ip_socket(int fd, int family) noexcept 222 + static std::error_code
HITGNC   223 + 2159 configure_ip_socket(int fd, int family) noexcept
225   { 224   {
HITCBC 226   2247 if (family == AF_INET6) 225   2159 if (family == AF_INET6)
227   { 226   {
HITCBC 228   22 int one = 1; 227   22 int one = 1;
HITGIC 229 - std::ignore = 228 + 44 std::ignore = ::setsockopt(
HITCBC 230 - 22 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 229 + 22 fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
231   } 230   }
232   231  
HITCBC 233   2247 return set_fd_options(fd); 232   2159 return set_fd_options(fd);
234   } 233   }
235   234  
236   // Apply protocol-specific options for acceptor sockets. 235   // Apply protocol-specific options for acceptor sockets.
237   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort). 236   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
ECB 238 - 275 static std::error_code configure_ip_acceptor(int fd, int family) noexcept 237 + static std::error_code
HITGNC   238 + 275 configure_ip_acceptor(int fd, int family) noexcept
239   { 239   {
HITCBC 240   275 if (family == AF_INET6) 240   275 if (family == AF_INET6)
241   { 241   {
HITCBC 242   11 int val = 0; 242   11 int val = 0;
HITGIC 243 - std::ignore = 243 + 22 std::ignore = ::setsockopt(
HITCBC 244 - 11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); 244 + 11 fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
245   } 245   }
246   246  
HITCBC 247   275 return set_fd_options(fd); 247   275 return set_fd_options(fd);
248   } 248   }
249   249  
250   // Apply options for local (unix) sockets. 250   // Apply options for local (unix) sockets.
ECB 251 - 109 static std::error_code configure_local_socket(int fd) noexcept 251 + static std::error_code
HITGNC   252 + 109 configure_local_socket(int fd) noexcept
252   { 253   {
HITCBC 253   109 return set_fd_options(fd); 254   109 return set_fd_options(fd);
254   } 255   }
255   256  
256   // Non-mutating validation for fds adopted via assign(). Select's 257   // Non-mutating validation for fds adopted via assign(). Select's
257   // reactor cannot handle fds above FD_SETSIZE, so reject them up 258   // reactor cannot handle fds above FD_SETSIZE, so reject them up
258   // front instead of letting FD_SET clobber unrelated memory. 259   // front instead of letting FD_SET clobber unrelated memory.
ECB 259 - 155 static std::error_code validate_assigned_fd(int fd) noexcept 260 + static std::error_code
HITGNC   261 + 155 validate_assigned_fd(int fd) noexcept
260   { 262   {
HITCBC 261   155 if (fd >= FD_SETSIZE) 263   155 if (fd >= FD_SETSIZE)
HITCBC 262   2 return make_err(EMFILE); 264   2 return make_err(EMFILE);
HITCBC 263   153 return {}; 265   153 return {};
264   } 266   }
265   }; 267   };
266   268  
267   } // namespace boost::corosio::detail 269   } // namespace boost::corosio::detail
268   270  
269   #endif // BOOST_COROSIO_HAS_SELECT 271   #endif // BOOST_COROSIO_HAS_SELECT
270   272  
271   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 273   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP