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