70.42% Lines (50/71) 100.00% Functions (11/11)
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   28561 static void pre_shutdown(int) noexcept {} 61   31644 static void pre_shutdown(int) noexcept {}
HITCBC 62   9494 static void pre_destroy(int) noexcept {} 62   10522 static void pre_destroy(int) noexcept {}
63   }; 63   };
64   64  
65   struct write_policy 65   struct write_policy
66   { 66   {
HITCBC 67   106293 static ssize_t write(int fd, iovec* iovecs, int count) noexcept 67   113275 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
68   { 68   {
HITCBC 69   106293 msghdr msg{}; 69   113275 msghdr msg{};
HITCBC 70   106293 msg.msg_iov = iovecs; 70   113275 msg.msg_iov = iovecs;
HITCBC 71   106293 msg.msg_iovlen = static_cast<std::size_t>(count); 71   113275 msg.msg_iovlen = static_cast<std::size_t>(count);
72   72  
73   #ifdef MSG_NOSIGNAL 73   #ifdef MSG_NOSIGNAL
HITCBC 74   106293 constexpr int send_flags = MSG_NOSIGNAL; 74   113275 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   {
HITCBC 82   106293 n = ::sendmsg(fd, &msg, send_flags); 82   113275 n = ::sendmsg(fd, &msg, send_flags);
83   } 83   }
HITCBC 84   106293 while (n < 0 && errno == EINTR); 84   113275 while (n < 0 && errno == EINTR);
HITCBC 85   106293 return n; 85   113275 return n;
86   } 86   }
87   }; 87   };
88   88  
89   struct accept_policy 89   struct accept_policy
90   { 90   {
HITCBC 91   6308 static int do_accept( 91   6994 static int do_accept(
92   int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept 92   int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
93   { 93   {
HITCBC 94   6308 addrlen = sizeof(peer); 94   6994 addrlen = sizeof(peer);
95   int new_fd; 95   int new_fd;
96   do 96   do
97   { 97   {
HITCBC 98   6308 new_fd = ::accept( 98   6994 new_fd = ::accept(
99   fd, reinterpret_cast<sockaddr*>(&peer), &addrlen); 99   fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
100   } 100   }
HITCBC 101   6308 while (new_fd < 0 && errno == EINTR); 101   6994 while (new_fd < 0 && errno == EINTR);
102   102  
HITCBC 103   6308 if (new_fd < 0) 103   6994 if (new_fd < 0)
HITCBC 104   3154 return new_fd; 104   3497 return new_fd;
105   105  
HITCBC 106   3154 if (new_fd >= FD_SETSIZE) 106   3497 if (new_fd >= FD_SETSIZE)
107   { 107   {
MISUBC 108   ::close(new_fd); 108   ::close(new_fd);
MISUBC 109   errno = EINVAL; 109   errno = EINVAL;
MISUBC 110   return -1; 110   return -1;
111   } 111   }
112   112  
HITCBC 113   3154 int flags = ::fcntl(new_fd, F_GETFL, 0); 113   3497 int flags = ::fcntl(new_fd, F_GETFL, 0);
HITCBC 114   3154 if (flags == -1) 114   3497 if (flags == -1)
115   { 115   {
MISUBC 116   int err = errno; 116   int err = errno;
MISUBC 117   ::close(new_fd); 117   ::close(new_fd);
MISUBC 118   errno = err; 118   errno = err;
MISUBC 119   return -1; 119   return -1;
120   } 120   }
121   121  
HITCBC 122   3154 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1) 122   3497 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
123   { 123   {
MISUBC 124   int err = errno; 124   int err = errno;
MISUBC 125   ::close(new_fd); 125   ::close(new_fd);
MISUBC 126   errno = err; 126   errno = err;
MISUBC 127   return -1; 127   return -1;
128   } 128   }
129   129  
HITCBC 130   3154 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1) 130   3497 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
131   { 131   {
MISUBC 132   int err = errno; 132   int err = errno;
MISUBC 133   ::close(new_fd); 133   ::close(new_fd);
MISUBC 134   errno = err; 134   errno = err;
MISUBC 135   return -1; 135   return -1;
136   } 136   }
137   137  
138   #ifdef SO_NOSIGPIPE 138   #ifdef SO_NOSIGPIPE
139   // Best-effort: SO_NOSIGPIPE is a safety net; write paths 139   // Best-effort: SO_NOSIGPIPE is a safety net; write paths
140   // also use MSG_NOSIGNAL where available. Failure here 140   // also use MSG_NOSIGNAL where available. Failure here
141   // should not prevent the accepted connection from being used. 141   // should not prevent the accepted connection from being used.
142   int one = 1; 142   int one = 1;
143   (void)::setsockopt( 143   (void)::setsockopt(
144   new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); 144   new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
145   #endif 145   #endif
146   146  
HITCBC 147   3154 return new_fd; 147   3497 return new_fd;
148   } 148   }
149   }; 149   };
150   150  
151   // Create a plain socket (no atomic flags -- select is POSIX-portable). 151   // Create a plain socket (no atomic flags -- select is POSIX-portable).
HITCBC 152   3315 static int create_socket(int family, int type, int protocol) noexcept 152   3657 static int create_socket(int family, int type, int protocol) noexcept
153   { 153   {
HITCBC 154   3315 return ::socket(family, type, protocol); 154   3657 return ::socket(family, type, protocol);
155   } 155   }
156   156  
157   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE. 157   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
158   // Caller is responsible for closing fd on error. 158   // Caller is responsible for closing fd on error.
HITCBC 159   3315 static std::error_code set_fd_options(int fd) noexcept 159   3657 static std::error_code set_fd_options(int fd) noexcept
160   { 160   {
HITCBC 161   3315 int flags = ::fcntl(fd, F_GETFL, 0); 161   3657 int flags = ::fcntl(fd, F_GETFL, 0);
HITCBC 162   3315 if (flags == -1) 162   3657 if (flags == -1)
MISUBC 163   return make_err(errno); 163   return make_err(errno);
HITCBC 164   3315 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) 164   3657 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
MISUBC 165   return make_err(errno); 165   return make_err(errno);
HITCBC 166   3315 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) 166   3657 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
MISUBC 167   return make_err(errno); 167   return make_err(errno);
168   168  
HITCBC 169   3315 if (fd >= FD_SETSIZE) 169   3657 if (fd >= FD_SETSIZE)
MISUBC 170   return make_err(EMFILE); 170   return make_err(EMFILE);
171   171  
172   #ifdef SO_NOSIGPIPE 172   #ifdef SO_NOSIGPIPE
173   // Best-effort: SO_NOSIGPIPE is a safety net; write paths 173   // Best-effort: SO_NOSIGPIPE is a safety net; write paths
174   // also use MSG_NOSIGNAL where available. Match develop's 174   // also use MSG_NOSIGNAL where available. Match develop's
175   // predominant behavior of ignoring failures here rather 175   // predominant behavior of ignoring failures here rather
176   // than failing socket creation. 176   // than failing socket creation.
177   { 177   {
178   int one = 1; 178   int one = 1;
179   (void)::setsockopt( 179   (void)::setsockopt(
180   fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); 180   fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
181   } 181   }
182   #endif 182   #endif
183   183  
HITCBC 184   3315 return {}; 184   3657 return {};
185   } 185   }
186   186  
187   // Apply protocol-specific options after socket creation. 187   // Apply protocol-specific options after socket creation.
188   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort). 188   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
189   static std::error_code 189   static std::error_code
HITCBC 190   3221 configure_ip_socket(int fd, int family) noexcept 190   3563 configure_ip_socket(int fd, int family) noexcept
191   { 191   {
HITCBC 192   3221 if (family == AF_INET6) 192   3563 if (family == AF_INET6)
193   { 193   {
HITCBC 194   14 int one = 1; 194   14 int one = 1;
HITCBC 195   14 (void)::setsockopt( 195   14 (void)::setsockopt(
196   fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 196   fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
197   } 197   }
198   198  
HITCBC 199   3221 return set_fd_options(fd); 199   3563 return set_fd_options(fd);
200   } 200   }
201   201  
202   // Apply protocol-specific options for acceptor sockets. 202   // Apply protocol-specific options for acceptor sockets.
203   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort). 203   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
204   static std::error_code 204   static std::error_code
HITCBC 205   74 configure_ip_acceptor(int fd, int family) noexcept 205   74 configure_ip_acceptor(int fd, int family) noexcept
206   { 206   {
HITCBC 207   74 if (family == AF_INET6) 207   74 if (family == AF_INET6)
208   { 208   {
HITCBC 209   9 int val = 0; 209   9 int val = 0;
HITCBC 210   9 (void)::setsockopt( 210   9 (void)::setsockopt(
211   fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); 211   fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
212   } 212   }
213   213  
HITCBC 214   74 return set_fd_options(fd); 214   74 return set_fd_options(fd);
215   } 215   }
216   216  
217   // Apply options for local (unix) sockets. 217   // Apply options for local (unix) sockets.
218   static std::error_code 218   static std::error_code
HITCBC 219   20 configure_local_socket(int fd) noexcept 219   20 configure_local_socket(int fd) noexcept
220   { 220   {
HITCBC 221   20 return set_fd_options(fd); 221   20 return set_fd_options(fd);
222   } 222   }
223   223  
224   // Non-mutating validation for fds adopted via assign(). Select's 224   // Non-mutating validation for fds adopted via assign(). Select's
225   // reactor cannot handle fds above FD_SETSIZE, so reject them up 225   // reactor cannot handle fds above FD_SETSIZE, so reject them up
226   // front instead of letting FD_SET clobber unrelated memory. 226   // front instead of letting FD_SET clobber unrelated memory.
227   static std::error_code 227   static std::error_code
HITCBC 228   14 validate_assigned_fd(int fd) noexcept 228   14 validate_assigned_fd(int fd) noexcept
229   { 229   {
HITCBC 230   14 if (fd >= FD_SETSIZE) 230   14 if (fd >= FD_SETSIZE)
MISUBC 231   return make_err(EMFILE); 231   return make_err(EMFILE);
HITCBC 232   14 return {}; 232   14 return {};
233   } 233   }
234   }; 234   };
235   235  
236   } // namespace boost::corosio::detail 236   } // namespace boost::corosio::detail
237   237  
238   #endif // BOOST_COROSIO_HAS_SELECT 238   #endif // BOOST_COROSIO_HAS_SELECT
239   239  
240   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 240   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP