100.00% Lines (75/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   #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, int level, int optname, 54 + int fd,
55 - void const* data, std::size_t size) noexcept 55 + int level,
  56 + int optname,
  57 + void const* data,
  58 + std::size_t size) noexcept
56   { 59   {
HITCBC 57   107 if (::setsockopt( 60   107 if (::setsockopt(
HITGIC 58 - fd, level, optname, data, 61 + 107 fd, level, optname, data, static_cast<socklen_t>(size)) !=
ECB 59 - 107 static_cast<socklen_t>(size)) != 0) 62 + 0)
HITCBC 60   4 return make_err(errno); 63   4 return make_err(errno);
HITCBC 61   103 return {}; 64   103 return {};
62   } 65   }
HITCBC 63   19299 static void pre_shutdown(int) noexcept {} 66   19420 static void pre_shutdown(int) noexcept {}
HITCBC 64   6265 static void pre_destroy(int) noexcept {} 67   6305 static void pre_destroy(int) noexcept {}
65   }; 68   };
66   69  
67   struct write_policy 70   struct write_policy
68   { 71   {
HITCBC 69   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept 72   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
70   { 73   {
HITCBC 71   72 msghdr msg{}; 74   72 msghdr msg{};
HITCBC 72   72 msg.msg_iov = iovecs; 75   72 msg.msg_iov = iovecs;
HITCBC 73   72 msg.msg_iovlen = static_cast<std::size_t>(count); 76   72 msg.msg_iovlen = static_cast<std::size_t>(count);
74   77  
75   #ifdef MSG_NOSIGNAL 78   #ifdef MSG_NOSIGNAL
HITCBC 76   72 constexpr int send_flags = MSG_NOSIGNAL; 79   72 constexpr int send_flags = MSG_NOSIGNAL;
77   #else 80   #else
78   constexpr int send_flags = 0; 81   constexpr int send_flags = 0;
79   #endif 82   #endif
80   83  
81   ssize_t n; 84   ssize_t n;
82   do 85   do
83   { 86   {
HITCBC 84   73 n = ::sendmsg(fd, &msg, send_flags); 87   73 n = ::sendmsg(fd, &msg, send_flags);
85   } 88   }
HITCBC 86   73 while (n < 0 && errno == EINTR); 89   73 while (n < 0 && errno == EINTR);
HITCBC 87   72 return n; 90   72 return n;
88   } 91   }
89   92  
90   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use 93   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use
91   // send() to suppress SIGPIPE inline; otherwise fall back to 94   // send() to suppress SIGPIPE inline; otherwise fall back to
92   // write() and rely on the SO_NOSIGPIPE set in accept_policy 95   // write() and rely on the SO_NOSIGPIPE set in accept_policy
93   // and set_fd_options. 96   // and set_fd_options.
ECB 94 - 113949 static ssize_t write_one( 97 + static ssize_t
HITGIC 95 - int fd, void const* data, std::size_t size) noexcept 98 + 103352 write_one(int fd, void const* data, std::size_t size) noexcept
96   { 99   {
97   ssize_t n; 100   ssize_t n;
98   do 101   do
99   { 102   {
100   #ifdef MSG_NOSIGNAL 103   #ifdef MSG_NOSIGNAL
HITCBC 101   113950 n = ::send(fd, data, size, MSG_NOSIGNAL); 104   103353 n = ::send(fd, data, size, MSG_NOSIGNAL);
102   #else 105   #else
103   n = ::write(fd, data, size); 106   n = ::write(fd, data, size);
104   #endif 107   #endif
105   } 108   }
HITCBC 106   113950 while (n < 0 && errno == EINTR); 109   103353 while (n < 0 && errno == EINTR);
HITCBC 107   113949 return n; 110   103352 return n;
108   } 111   }
109   }; 112   };
110   113  
111   struct accept_policy 114   struct accept_policy
112   { 115   {
ECB 113 - 4128 static int do_accept( 116 + static int
HITGIC 114 - int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept 117 + 4154 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
115   { 118   {
HITCBC 116   4128 addrlen = sizeof(peer); 119   4154 addrlen = sizeof(peer);
117   int new_fd; 120   int new_fd;
118   do 121   do
119   { 122   {
ECB 120 - 4129 new_fd = ::accept( 123 + new_fd =
HITGIC 121 - fd, reinterpret_cast<sockaddr*>(&peer), &addrlen); 124 + 4155 ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
122   } 125   }
HITCBC 123   4129 while (new_fd < 0 && errno == EINTR); 126   4155 while (new_fd < 0 && errno == EINTR);
124   127  
HITCBC 125   4128 if (new_fd < 0) 128   4154 if (new_fd < 0)
HITCBC 126   2071 return new_fd; 129   2084 return new_fd;
127   130  
HITCBC 128   2057 if (new_fd >= FD_SETSIZE) 131   2070 if (new_fd >= FD_SETSIZE)
129   { 132   {
HITCBC 130   1 ::close(new_fd); 133   1 ::close(new_fd);
HITCBC 131   1 errno = EMFILE; 134   1 errno = EMFILE;
HITCBC 132   1 return -1; 135   1 return -1;
133   } 136   }
134   137  
HITCBC 135   2056 int flags = ::fcntl(new_fd, F_GETFL, 0); 138   2069 int flags = ::fcntl(new_fd, F_GETFL, 0);
HITCBC 136   2056 if (flags == -1) 139   2069 if (flags == -1)
137   { 140   {
HITCBC 138   1 int err = errno; 141   1 int err = errno;
HITCBC 139   1 ::close(new_fd); 142   1 ::close(new_fd);
HITCBC 140   1 errno = err; 143   1 errno = err;
HITCBC 141   1 return -1; 144   1 return -1;
142   } 145   }
143   146  
HITCBC 144   2055 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1) 147   2068 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
145   { 148   {
HITCBC 146   1 int err = errno; 149   1 int err = errno;
HITCBC 147   1 ::close(new_fd); 150   1 ::close(new_fd);
HITCBC 148   1 errno = err; 151   1 errno = err;
HITCBC 149   1 return -1; 152   1 return -1;
150   } 153   }
151   154  
HITCBC 152   2054 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1) 155   2067 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
153   { 156   {
HITCBC 154   1 int err = errno; 157   1 int err = errno;
HITCBC 155   1 ::close(new_fd); 158   1 ::close(new_fd);
HITCBC 156   1 errno = err; 159   1 errno = err;
HITCBC 157   1 return -1; 160   1 return -1;
158   } 161   }
159   162  
160   #ifdef SO_NOSIGPIPE 163   #ifdef SO_NOSIGPIPE
161   // MSG_NOSIGNAL is not universal across the platforms this 164   // MSG_NOSIGNAL is not universal across the platforms this
162   // portable backend covers, and the write() the fast path 165   // portable backend covers, and the write() the fast path
163   // falls back to there takes no flag at all; SO_NOSIGPIPE is 166   // falls back to there takes no flag at all; SO_NOSIGPIPE is
164   // the per-descriptor guard that covers both. Treat failure 167   // the per-descriptor guard that covers both. Treat failure
165   // as fatal, matching the kqueue backend. 168   // as fatal, matching the kqueue backend.
166   int one = 1; 169   int one = 1;
167   if (::setsockopt( 170   if (::setsockopt(
168 - new_fd, SOL_SOCKET, SO_NOSIGPIPE, 171 + new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
169 - &one, sizeof(one)) != 0)  
170   { 172   {
171   int err = errno; 173   int err = errno;
172   ::close(new_fd); 174   ::close(new_fd);
173   errno = err; 175   errno = err;
174   return -1; 176   return -1;
175   } 177   }
176   #endif 178   #endif
177   179  
HITCBC 178   2053 return new_fd; 180   2066 return new_fd;
179   } 181   }
180   }; 182   };
181   183  
182   // Create a plain socket (no atomic flags -- select is POSIX-portable). 184   // Create a plain socket (no atomic flags -- select is POSIX-portable).
HITCBC 183   2659 static int create_socket(int family, int type, int protocol) noexcept 185   2673 static int create_socket(int family, int type, int protocol) noexcept
184   { 186   {
HITCBC 185   2659 return ::socket(family, type, protocol); 187   2673 return ::socket(family, type, protocol);
186   } 188   }
187   189  
188   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE. 190   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
189   // Caller is responsible for closing fd on error. 191   // Caller is responsible for closing fd on error.
HITCBC 190   2653 static std::error_code set_fd_options(int fd) noexcept 192   2667 static std::error_code set_fd_options(int fd) noexcept
191   { 193   {
HITCBC 192   2653 int flags = ::fcntl(fd, F_GETFL, 0); 194   2667 int flags = ::fcntl(fd, F_GETFL, 0);
HITCBC 193   2653 if (flags == -1) 195   2667 if (flags == -1)
HITCBC 194   2 return make_err(errno); 196   2 return make_err(errno);
HITCBC 195   2651 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) 197   2665 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
HITCBC 196   2 return make_err(errno); 198   2 return make_err(errno);
HITCBC 197   2649 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) 199   2663 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
HITCBC 198   2 return make_err(errno); 200   2 return make_err(errno);
199   201  
HITCBC 200   2647 if (fd >= FD_SETSIZE) 202   2661 if (fd >= FD_SETSIZE)
HITCBC 201   2 return make_err(EMFILE); 203   2 return make_err(EMFILE);
202   204  
203   #ifdef SO_NOSIGPIPE 205   #ifdef SO_NOSIGPIPE
204   // MSG_NOSIGNAL is not universal across the platforms this 206   // MSG_NOSIGNAL is not universal across the platforms this
205   // portable backend covers, and the write() the fast path falls 207   // portable backend covers, and the write() the fast path falls
206   // back to there takes no flag at all; SO_NOSIGPIPE is the 208   // back to there takes no flag at all; SO_NOSIGPIPE is the
207   // per-descriptor guard that covers both. Treat failure as fatal, 209   // per-descriptor guard that covers both. Treat failure as fatal,
208   // matching the kqueue backend. Caller closes fd on error. 210   // matching the kqueue backend. Caller closes fd on error.
209   { 211   {
210   int one = 1; 212   int one = 1;
211 - if (::setsockopt( 213 + if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) !=
212 - fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0) 214 + 0)
213   return make_err(errno); 215   return make_err(errno);
214   } 216   }
215   #endif 217   #endif
216   218  
HITCBC 217   2645 return {}; 219   2659 return {};
218   } 220   }
219   221  
220   // Apply protocol-specific options after socket creation. 222   // Apply protocol-specific options after socket creation.
221   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort). 223   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
HITGIC 222 - static std::error_code 224 + 2283 static std::error_code configure_ip_socket(int fd, int family) noexcept
DCB 223 - 2269 configure_ip_socket(int fd, int family) noexcept  
224   { 225   {
HITCBC 225   2269 if (family == AF_INET6) 226   2283 if (family == AF_INET6)
226   { 227   {
HITCBC 227   22 int one = 1; 228   22 int one = 1;
ECB 228 - 44 std::ignore = ::setsockopt( 229 + std::ignore =
HITCBC 229 - 22 fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 230 + 22 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
230   } 231   }
231   232  
HITCBC 232   2269 return set_fd_options(fd); 233   2283 return set_fd_options(fd);
233   } 234   }
234   235  
235   // Apply protocol-specific options for acceptor sockets. 236   // Apply protocol-specific options for acceptor sockets.
236   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort). 237   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
HITGIC 237 - static std::error_code 238 + 275 static std::error_code configure_ip_acceptor(int fd, int family) noexcept
DCB 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;
ECB 243 - 22 std::ignore = ::setsockopt( 243 + std::ignore =
HITCBC 244 - 11 fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); 244 + 11 ::setsockopt(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.
HITGIC 251 - static std::error_code 251 + 109 static std::error_code configure_local_socket(int fd) noexcept
DCB 252 - 109 configure_local_socket(int fd) noexcept  
253   { 252   {
HITCBC 254   109 return set_fd_options(fd); 253   109 return set_fd_options(fd);
255   } 254   }
256   255  
257   // Non-mutating validation for fds adopted via assign(). Select's 256   // Non-mutating validation for fds adopted via assign(). Select's
258   // reactor cannot handle fds above FD_SETSIZE, so reject them up 257   // reactor cannot handle fds above FD_SETSIZE, so reject them up
259   // front instead of letting FD_SET clobber unrelated memory. 258   // front instead of letting FD_SET clobber unrelated memory.
HITGIC 260 - static std::error_code 259 + 155 static std::error_code validate_assigned_fd(int fd) noexcept
DCB 261 - 155 validate_assigned_fd(int fd) noexcept  
262   { 260   {
HITCBC 263   155 if (fd >= FD_SETSIZE) 261   155 if (fd >= FD_SETSIZE)
HITCBC 264   2 return make_err(EMFILE); 262   2 return make_err(EMFILE);
HITCBC 265   153 return {}; 263   153 return {};
266   } 264   }
267   }; 265   };
268   266  
269   } // namespace boost::corosio::detail 267   } // namespace boost::corosio::detail
270   268  
271   #endif // BOOST_COROSIO_HAS_SELECT 269   #endif // BOOST_COROSIO_HAS_SELECT
272   270  
273   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 271   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP