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