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