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