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