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