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