TLA Line data 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 HIT 51 : std::error_code on_set_option(
53 : int fd, int level, int optname,
54 : void const* data, std::size_t size) noexcept
55 : {
56 51 : if (::setsockopt(
57 : fd, level, optname, data,
58 51 : static_cast<socklen_t>(size)) != 0)
59 MIS 0 : return make_err(errno);
60 HIT 51 : return {};
61 : }
62 32130 : static void pre_shutdown(int) noexcept {}
63 10626 : static void pre_destroy(int) noexcept {}
64 : };
65 :
66 : struct write_policy
67 : {
68 67 : static ssize_t write(int fd, iovec* iovecs, int count) noexcept
69 : {
70 67 : msghdr msg{};
71 67 : msg.msg_iov = iovecs;
72 67 : msg.msg_iovlen = static_cast<std::size_t>(count);
73 :
74 : #ifdef MSG_NOSIGNAL
75 67 : 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 67 : n = ::sendmsg(fd, &msg, send_flags);
84 : }
85 67 : while (n < 0 && errno == EINTR);
86 67 : 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 104358 : 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 104358 : n = ::send(fd, data, size, MSG_NOSIGNAL);
101 : #else
102 : n = ::write(fd, data, size);
103 : #endif
104 : }
105 104358 : while (n < 0 && errno == EINTR);
106 104358 : return n;
107 : }
108 : };
109 :
110 : struct accept_policy
111 : {
112 7048 : static int do_accept(
113 : int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
114 : {
115 7048 : addrlen = sizeof(peer);
116 : int new_fd;
117 : do
118 : {
119 7048 : new_fd = ::accept(
120 : fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
121 : }
122 7048 : while (new_fd < 0 && errno == EINTR);
123 :
124 7048 : if (new_fd < 0)
125 3531 : return new_fd;
126 :
127 3517 : if (new_fd >= FD_SETSIZE)
128 : {
129 MIS 0 : ::close(new_fd);
130 0 : errno = EINVAL;
131 0 : return -1;
132 : }
133 :
134 HIT 3517 : int flags = ::fcntl(new_fd, F_GETFL, 0);
135 3517 : if (flags == -1)
136 : {
137 MIS 0 : int err = errno;
138 0 : ::close(new_fd);
139 0 : errno = err;
140 0 : return -1;
141 : }
142 :
143 HIT 3517 : if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
144 : {
145 MIS 0 : int err = errno;
146 0 : ::close(new_fd);
147 0 : errno = err;
148 0 : return -1;
149 : }
150 :
151 HIT 3517 : if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
152 : {
153 MIS 0 : int err = errno;
154 0 : ::close(new_fd);
155 0 : errno = err;
156 0 : 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 HIT 3517 : return new_fd;
176 : }
177 : };
178 :
179 : // Create a plain socket (no atomic flags -- select is POSIX-portable).
180 3848 : static int create_socket(int family, int type, int protocol) noexcept
181 : {
182 3848 : 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 3848 : static std::error_code set_fd_options(int fd) noexcept
188 : {
189 3848 : int flags = ::fcntl(fd, F_GETFL, 0);
190 3848 : if (flags == -1)
191 MIS 0 : return make_err(errno);
192 HIT 3848 : if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
193 MIS 0 : return make_err(errno);
194 HIT 3848 : if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
195 MIS 0 : return make_err(errno);
196 :
197 HIT 3848 : if (fd >= FD_SETSIZE)
198 MIS 0 : 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 HIT 3848 : 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 3639 : configure_ip_socket(int fd, int family) noexcept
219 : {
220 3639 : if (family == AF_INET6)
221 : {
222 19 : int one = 1;
223 19 : (void)::setsockopt(
224 : fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
225 : }
226 :
227 3639 : 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 133 : configure_ip_acceptor(int fd, int family) noexcept
234 : {
235 133 : if (family == AF_INET6)
236 : {
237 10 : int val = 0;
238 10 : (void)::setsockopt(
239 : fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
240 : }
241 :
242 133 : return set_fd_options(fd);
243 : }
244 :
245 : // Apply options for local (unix) sockets.
246 : static std::error_code
247 76 : configure_local_socket(int fd) noexcept
248 : {
249 76 : 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 76 : validate_assigned_fd(int fd) noexcept
257 : {
258 76 : if (fd >= FD_SETSIZE)
259 MIS 0 : return make_err(EMFILE);
260 HIT 76 : 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
|