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 100 : std::error_code on_set_option(
54 : int fd, int level, int optname,
55 : void const* data, std::size_t size) noexcept
56 : {
57 100 : if (::setsockopt(
58 : fd, level, optname, data,
59 100 : static_cast<socklen_t>(size)) != 0)
60 4 : return make_err(errno);
61 96 : return {};
62 : }
63 18529 : static void pre_shutdown(int) noexcept {}
64 6013 : static void pre_destroy(int) noexcept {}
65 : };
66 :
67 : struct write_policy
68 : {
69 72 : static ssize_t write(int fd, iovec* iovecs, int count) noexcept
70 : {
71 72 : msghdr msg{};
72 72 : msg.msg_iov = iovecs;
73 72 : msg.msg_iovlen = static_cast<std::size_t>(count);
74 :
75 : #ifdef MSG_NOSIGNAL
76 72 : 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 73 : n = ::sendmsg(fd, &msg, send_flags);
85 : }
86 73 : while (n < 0 && errno == EINTR);
87 72 : 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 98344 : 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 98345 : n = ::send(fd, data, size, MSG_NOSIGNAL);
102 : #else
103 : n = ::write(fd, data, size);
104 : #endif
105 : }
106 98345 : while (n < 0 && errno == EINTR);
107 98344 : return n;
108 : }
109 : };
110 :
111 : struct accept_policy
112 : {
113 3950 : static int do_accept(
114 : int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
115 : {
116 3950 : addrlen = sizeof(peer);
117 : int new_fd;
118 : do
119 : {
120 3951 : new_fd = ::accept(
121 : fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
122 : }
123 3951 : while (new_fd < 0 && errno == EINTR);
124 :
125 3950 : if (new_fd < 0)
126 1976 : return new_fd;
127 :
128 1974 : if (new_fd >= FD_SETSIZE)
129 : {
130 1 : ::close(new_fd);
131 1 : errno = EMFILE;
132 1 : return -1;
133 : }
134 :
135 1973 : int flags = ::fcntl(new_fd, F_GETFL, 0);
136 1973 : if (flags == -1)
137 : {
138 1 : int err = errno;
139 1 : ::close(new_fd);
140 1 : errno = err;
141 1 : return -1;
142 : }
143 :
144 1972 : if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
145 : {
146 1 : int err = errno;
147 1 : ::close(new_fd);
148 1 : errno = err;
149 1 : return -1;
150 : }
151 :
152 1971 : if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
153 : {
154 1 : int err = errno;
155 1 : ::close(new_fd);
156 1 : errno = err;
157 1 : return -1;
158 : }
159 :
160 : #ifdef SO_NOSIGPIPE
161 : // MSG_NOSIGNAL is not universal across the platforms this
162 : // portable backend covers, and the write() the fast path
163 : // falls back to there takes no flag at all; SO_NOSIGPIPE is
164 : // the per-descriptor guard that covers both. Treat failure
165 : // as fatal, matching the kqueue backend.
166 : int one = 1;
167 : if (::setsockopt(
168 : new_fd, SOL_SOCKET, SO_NOSIGPIPE,
169 : &one, sizeof(one)) != 0)
170 : {
171 : int err = errno;
172 : ::close(new_fd);
173 : errno = err;
174 : return -1;
175 : }
176 : #endif
177 :
178 1970 : return new_fd;
179 : }
180 : };
181 :
182 : // Create a plain socket (no atomic flags -- select is POSIX-portable).
183 2449 : static int create_socket(int family, int type, int protocol) noexcept
184 : {
185 2449 : return ::socket(family, type, protocol);
186 : }
187 :
188 : // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
189 : // Caller is responsible for closing fd on error.
190 2443 : static std::error_code set_fd_options(int fd) noexcept
191 : {
192 2443 : int flags = ::fcntl(fd, F_GETFL, 0);
193 2443 : if (flags == -1)
194 2 : return make_err(errno);
195 2441 : if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
196 2 : return make_err(errno);
197 2439 : if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
198 2 : return make_err(errno);
199 :
200 2437 : if (fd >= FD_SETSIZE)
201 2 : return make_err(EMFILE);
202 :
203 : #ifdef SO_NOSIGPIPE
204 : // MSG_NOSIGNAL is not universal across the platforms this
205 : // portable backend covers, and the write() the fast path falls
206 : // back to there takes no flag at all; SO_NOSIGPIPE is the
207 : // per-descriptor guard that covers both. Treat failure as fatal,
208 : // matching the kqueue backend. Caller closes fd on error.
209 : {
210 : int one = 1;
211 : if (::setsockopt(
212 : fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
213 : return make_err(errno);
214 : }
215 : #endif
216 :
217 2435 : return {};
218 : }
219 :
220 : // Apply protocol-specific options after socket creation.
221 : // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
222 : static std::error_code
223 2133 : configure_ip_socket(int fd, int family) noexcept
224 : {
225 2133 : if (family == AF_INET6)
226 : {
227 22 : int one = 1;
228 44 : std::ignore = ::setsockopt(
229 22 : fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
230 : }
231 :
232 2133 : return set_fd_options(fd);
233 : }
234 :
235 : // Apply protocol-specific options for acceptor sockets.
236 : // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
237 : static std::error_code
238 212 : configure_ip_acceptor(int fd, int family) noexcept
239 : {
240 212 : if (family == AF_INET6)
241 : {
242 11 : int val = 0;
243 22 : std::ignore = ::setsockopt(
244 11 : fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
245 : }
246 :
247 212 : return set_fd_options(fd);
248 : }
249 :
250 : // Apply options for local (unix) sockets.
251 : static std::error_code
252 98 : configure_local_socket(int fd) noexcept
253 : {
254 98 : return set_fd_options(fd);
255 : }
256 :
257 : // Non-mutating validation for fds adopted via assign(). Select's
258 : // reactor cannot handle fds above FD_SETSIZE, so reject them up
259 : // front instead of letting FD_SET clobber unrelated memory.
260 : static std::error_code
261 146 : validate_assigned_fd(int fd) noexcept
262 : {
263 146 : if (fd >= FD_SETSIZE)
264 2 : return make_err(EMFILE);
265 144 : return {};
266 : }
267 : };
268 :
269 : } // namespace boost::corosio::detail
270 :
271 : #endif // BOOST_COROSIO_HAS_SELECT
272 :
273 : #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
|