include/boost/corosio/native/detail/select/select_traits.hpp

72.0% Lines (54/75) 100.0% List of functions (12/12)
select_traits.hpp
f(x) Functions (12)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
3 //
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)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_SELECT
16
17 #include <boost/corosio/native/detail/make_err.hpp>
18 #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19
20 #include <system_error>
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <netinet/in.h>
25 #include <sys/select.h>
26 #include <sys/socket.h>
27 #include <unistd.h>
28
29 /* select backend traits.
30
31 Captures the platform-specific behavior of the portable select() backend:
32 manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation,
33 mandatory SO_NOSIGPIPE where the platform defines it,
34 sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for
35 accepted connections.
36 */
37
38 namespace boost::corosio::detail {
39
40 class select_scheduler;
41
42 struct select_traits
43 {
44 using scheduler_type = select_scheduler;
45 using desc_state_type = reactor_descriptor_state;
46
47 static constexpr bool needs_write_notification = true;
48
49 // No extra per-socket state or lifecycle hooks needed for select.
50 struct stream_socket_hook
51 {
52 51x std::error_code on_set_option(
53 int fd, int level, int optname,
54 void const* data, std::size_t size) noexcept
55 {
56 51x if (::setsockopt(
57 fd, level, optname, data,
58 51x static_cast<socklen_t>(size)) != 0)
59 return make_err(errno);
60 51x return {};
61 }
62 30384x static void pre_shutdown(int) noexcept {}
63 10044x static void pre_destroy(int) noexcept {}
64 };
65
66 struct write_policy
67 {
68 67x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
69 {
70 67x msghdr msg{};
71 67x msg.msg_iov = iovecs;
72 67x msg.msg_iovlen = static_cast<std::size_t>(count);
73
74 #ifdef MSG_NOSIGNAL
75 67x constexpr int send_flags = MSG_NOSIGNAL;
76 #else
77 constexpr int send_flags = 0;
78 #endif
79
80 ssize_t n;
81 do
82 {
83 67x n = ::sendmsg(fd, &msg, send_flags);
84 }
85 67x while (n < 0 && errno == EINTR);
86 67x return n;
87 }
88
89 // Single-buffer fast path. Where MSG_NOSIGNAL exists we use
90 // send() to suppress SIGPIPE inline; otherwise fall back to
91 // write() and rely on the SO_NOSIGPIPE set in accept_policy
92 // and set_fd_options.
93 102902x static ssize_t write_one(
94 int fd, void const* data, std::size_t size) noexcept
95 {
96 ssize_t n;
97 do
98 {
99 #ifdef MSG_NOSIGNAL
100 102902x n = ::send(fd, data, size, MSG_NOSIGNAL);
101 #else
102 n = ::write(fd, data, size);
103 #endif
104 }
105 102902x while (n < 0 && errno == EINTR);
106 102902x return n;
107 }
108 };
109
110 struct accept_policy
111 {
112 6660x static int do_accept(
113 int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
114 {
115 6660x addrlen = sizeof(peer);
116 int new_fd;
117 do
118 {
119 6660x new_fd = ::accept(
120 fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
121 }
122 6660x while (new_fd < 0 && errno == EINTR);
123
124 6660x if (new_fd < 0)
125 3337x return new_fd;
126
127 3323x if (new_fd >= FD_SETSIZE)
128 {
129 ::close(new_fd);
130 errno = EINVAL;
131 return -1;
132 }
133
134 3323x int flags = ::fcntl(new_fd, F_GETFL, 0);
135 3323x if (flags == -1)
136 {
137 int err = errno;
138 ::close(new_fd);
139 errno = err;
140 return -1;
141 }
142
143 3323x if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
144 {
145 int err = errno;
146 ::close(new_fd);
147 errno = err;
148 return -1;
149 }
150
151 3323x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
152 {
153 int err = errno;
154 ::close(new_fd);
155 errno = err;
156 return -1;
157 }
158
159 #ifdef SO_NOSIGPIPE
160 // SO_NOSIGPIPE is the only SIGPIPE guard on platforms that
161 // lack MSG_NOSIGNAL (macOS/BSD). Treat failure as fatal,
162 // matching the kqueue backend and Boost.Asio.
163 int one = 1;
164 if (::setsockopt(
165 new_fd, SOL_SOCKET, SO_NOSIGPIPE,
166 &one, sizeof(one)) != 0)
167 {
168 int err = errno;
169 ::close(new_fd);
170 errno = err;
171 return -1;
172 }
173 #endif
174
175 3323x return new_fd;
176 }
177 };
178
179 // Create a plain socket (no atomic flags -- select is POSIX-portable).
180 3654x static int create_socket(int family, int type, int protocol) noexcept
181 {
182 3654x return ::socket(family, type, protocol);
183 }
184
185 // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
186 // Caller is responsible for closing fd on error.
187 3654x static std::error_code set_fd_options(int fd) noexcept
188 {
189 3654x int flags = ::fcntl(fd, F_GETFL, 0);
190 3654x if (flags == -1)
191 return make_err(errno);
192 3654x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
193 return make_err(errno);
194 3654x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
195 return make_err(errno);
196
197 3654x if (fd >= FD_SETSIZE)
198 return make_err(EMFILE);
199
200 #ifdef SO_NOSIGPIPE
201 // SO_NOSIGPIPE is the only SIGPIPE guard on platforms that lack
202 // MSG_NOSIGNAL (macOS/BSD). Treat failure as fatal, matching the
203 // kqueue backend and Boost.Asio. Caller closes fd on error.
204 {
205 int one = 1;
206 if (::setsockopt(
207 fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
208 return make_err(errno);
209 }
210 #endif
211
212 3654x return {};
213 }
214
215 // Apply protocol-specific options after socket creation.
216 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
217 static std::error_code
218 3445x configure_ip_socket(int fd, int family) noexcept
219 {
220 3445x if (family == AF_INET6)
221 {
222 19x int one = 1;
223 19x (void)::setsockopt(
224 fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
225 }
226
227 3445x return set_fd_options(fd);
228 }
229
230 // Apply protocol-specific options for acceptor sockets.
231 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
232 static std::error_code
233 133x configure_ip_acceptor(int fd, int family) noexcept
234 {
235 133x if (family == AF_INET6)
236 {
237 10x int val = 0;
238 10x (void)::setsockopt(
239 fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
240 }
241
242 133x return set_fd_options(fd);
243 }
244
245 // Apply options for local (unix) sockets.
246 static std::error_code
247 76x configure_local_socket(int fd) noexcept
248 {
249 76x return set_fd_options(fd);
250 }
251
252 // Non-mutating validation for fds adopted via assign(). Select's
253 // reactor cannot handle fds above FD_SETSIZE, so reject them up
254 // front instead of letting FD_SET clobber unrelated memory.
255 static std::error_code
256 76x validate_assigned_fd(int fd) noexcept
257 {
258 76x if (fd >= FD_SETSIZE)
259 return make_err(EMFILE);
260 76x return {};
261 }
262 };
263
264 } // namespace boost::corosio::detail
265
266 #endif // BOOST_COROSIO_HAS_SELECT
267
268 #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
269