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