include/boost/corosio/native/detail/select/select_traits.hpp

100.0% Lines (75/0/75) 100.0% List of functions (12/0/12)
select_traits.hpp
f(x) Functions (12)
Line TLA Hits 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 107x 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 107x if (::setsockopt(
61 107x fd, level, optname, data, static_cast<socklen_t>(size)) !=
62 0)
63 4x return make_err(errno);
64 103x return {};
65 }
66 19420x static void pre_shutdown(int) noexcept {}
67 6305x static void pre_destroy(int) noexcept {}
68 };
69
70 struct write_policy
71 {
72 72x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
73 {
74 72x msghdr msg{};
75 72x msg.msg_iov = iovecs;
76 72x msg.msg_iovlen = static_cast<std::size_t>(count);
77
78 #ifdef MSG_NOSIGNAL
79 72x 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 73x n = ::sendmsg(fd, &msg, send_flags);
88 }
89 73x while (n < 0 && errno == EINTR);
90 72x 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 103352x 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 103353x n = ::send(fd, data, size, MSG_NOSIGNAL);
105 #else
106 n = ::write(fd, data, size);
107 #endif
108 }
109 103353x while (n < 0 && errno == EINTR);
110 103352x return n;
111 }
112 };
113
114 struct accept_policy
115 {
116 static int
117 4154x do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
118 {
119 4154x addrlen = sizeof(peer);
120 int new_fd;
121 do
122 {
123 new_fd =
124 4155x ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
125 }
126 4155x while (new_fd < 0 && errno == EINTR);
127
128 4154x if (new_fd < 0)
129 2084x return new_fd;
130
131 2070x if (new_fd >= FD_SETSIZE)
132 {
133 1x ::close(new_fd);
134 1x errno = EMFILE;
135 1x return -1;
136 }
137
138 2069x int flags = ::fcntl(new_fd, F_GETFL, 0);
139 2069x if (flags == -1)
140 {
141 1x int err = errno;
142 1x ::close(new_fd);
143 1x errno = err;
144 1x return -1;
145 }
146
147 2068x if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
148 {
149 1x int err = errno;
150 1x ::close(new_fd);
151 1x errno = err;
152 1x return -1;
153 }
154
155 2067x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
156 {
157 1x int err = errno;
158 1x ::close(new_fd);
159 1x errno = err;
160 1x 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 2066x return new_fd;
181 }
182 };
183
184 // Create a plain socket (no atomic flags -- select is POSIX-portable).
185 2673x static int create_socket(int family, int type, int protocol) noexcept
186 {
187 2673x 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 2667x static std::error_code set_fd_options(int fd) noexcept
193 {
194 2667x int flags = ::fcntl(fd, F_GETFL, 0);
195 2667x if (flags == -1)
196 2x return make_err(errno);
197 2665x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
198 2x return make_err(errno);
199 2663x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
200 2x return make_err(errno);
201
202 2661x if (fd >= FD_SETSIZE)
203 2x 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 2659x return {};
220 }
221
222 // Apply protocol-specific options after socket creation.
223 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
224 2283x static std::error_code configure_ip_socket(int fd, int family) noexcept
225 {
226 2283x if (family == AF_INET6)
227 {
228 22x int one = 1;
229 std::ignore =
230 22x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
231 }
232
233 2283x 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 275x static std::error_code configure_ip_acceptor(int fd, int family) noexcept
239 {
240 275x if (family == AF_INET6)
241 {
242 11x int val = 0;
243 std::ignore =
244 11x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
245 }
246
247 275x return set_fd_options(fd);
248 }
249
250 // Apply options for local (unix) sockets.
251 109x static std::error_code configure_local_socket(int fd) noexcept
252 {
253 109x 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 155x static std::error_code validate_assigned_fd(int fd) noexcept
260 {
261 155x if (fd >= FD_SETSIZE)
262 2x return make_err(EMFILE);
263 153x 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
272