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