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

100.0% Lines (100/100) 100.0% List of functions (12/12) 66.4% Branches (73/110)
select_traits.hpp
f(x) Functions (12)
Line Branch 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 106x 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
3/4
✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 103 times.
106x if (::setsockopt(
61 106x fd, level, optname, data, static_cast<socklen_t>(size)) !=
62 0)
63
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3x return make_err(errno);
64 103x return {};
65 106x }
66 16457x static void pre_shutdown(int) noexcept {}
67 5328x static void pre_destroy(int) noexcept {}
68 };
69
70 struct write_policy
71 {
72 515x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
73 {
74 515x msghdr msg{};
75 515x msg.msg_iov = iovecs;
76 515x msg.msg_iovlen = static_cast<std::size_t>(count);
77
78 #ifdef MSG_NOSIGNAL
79 515x constexpr int send_flags = MSG_NOSIGNAL;
80 #else
81 constexpr int send_flags = 0;
82 #endif
83
84 ssize_t n;
85 515x do
86 {
87
1/2
✓ Branch 0 taken 516 times.
✗ Branch 1 not taken.
516x n = ::sendmsg(fd, &msg, send_flags);
88 1032x }
89
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 513 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
516x while (n < 0 && errno == EINTR);
90 515x 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 239399x write_one(int fd, void const* data, std::size_t size) noexcept
99 {
100 ssize_t n;
101 239399x do
102 {
103 #ifdef MSG_NOSIGNAL
104
1/2
✓ Branch 0 taken 239400 times.
✗ Branch 1 not taken.
239400x n = ::send(fd, data, size, MSG_NOSIGNAL);
105 #else
106 n = ::write(fd, data, size);
107 #endif
108 478800x }
109
3/4
✓ Branch 0 taken 517 times.
✓ Branch 1 taken 238883 times.
✓ Branch 2 taken 517 times.
✗ Branch 3 not taken.
239400x while (n < 0 && errno == EINTR);
110 239399x return n;
111 }
112 };
113
114 struct accept_policy
115 {
116 static int
117 3508x do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
118 {
119 3508x addrlen = sizeof(peer);
120 int new_fd;
121 3508x do
122 {
123 3509x new_fd =
124
1/2
✓ Branch 0 taken 3509 times.
✗ Branch 1 not taken.
3509x ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
125 7018x }
126
3/4
✓ Branch 0 taken 1763 times.
✓ Branch 1 taken 1746 times.
✓ Branch 2 taken 1763 times.
✗ Branch 3 not taken.
3509x while (new_fd < 0 && errno == EINTR);
127
128
2/2
✓ Branch 0 taken 1762 times.
✓ Branch 1 taken 1746 times.
3508x if (new_fd < 0)
129 1762x return new_fd;
130
131
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1745 times.
1746x if (new_fd >= FD_SETSIZE)
132 {
133
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ::close(new_fd);
134
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x errno = EMFILE;
135 1x return -1;
136 }
137
138
1/2
✓ Branch 0 taken 1745 times.
✗ Branch 1 not taken.
1745x int flags = ::fcntl(new_fd, F_GETFL, 0);
139
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1744 times.
1745x if (flags == -1)
140 {
141
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x int err = errno;
142
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ::close(new_fd);
143
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x errno = err;
144 1x return -1;
145 }
146
147
3/4
✓ Branch 0 taken 1744 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1743 times.
1744x if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
148 {
149
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x int err = errno;
150
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ::close(new_fd);
151
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x errno = err;
152 1x return -1;
153 }
154
155
3/4
✓ Branch 0 taken 1743 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1742 times.
1743x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
156 {
157
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x int err = errno;
158
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ::close(new_fd);
159
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
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 1742x int one = 1;
170
3/4
✓ Branch 0 taken 1742 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1741 times.
1742x if (::setsockopt(
171 1742x new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
172 {
173
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x int err = errno;
174
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ::close(new_fd);
175
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x errno = err;
176 1x return -1;
177 }
178 #endif
179
180 1741x return new_fd;
181 3508x }
182 };
183
184 // Create a plain socket (no atomic flags -- select is POSIX-portable).
185 2272x static int create_socket(int family, int type, int protocol) noexcept
186 {
187
1/2
✓ Branch 0 taken 2272 times.
✗ Branch 1 not taken.
2272x 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 2268x static std::error_code set_fd_options(int fd) noexcept
193 {
194
1/2
✓ Branch 0 taken 2268 times.
✗ Branch 1 not taken.
2268x int flags = ::fcntl(fd, F_GETFL, 0);
195
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2266 times.
2268x if (flags == -1)
196
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
197
3/4
✓ Branch 0 taken 2266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2264 times.
2266x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
198
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
199
3/4
✓ Branch 0 taken 2264 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2262 times.
2264x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
200
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
201
202
2/2
✓ Branch 0 taken 2260 times.
✓ Branch 1 taken 2 times.
2262x 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 2260x int one = 1;
213
3/4
✓ Branch 0 taken 2260 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2258 times.
2260x if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) !=
214 0)
215
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
216 }
217 #endif
218
219 2258x return {};
220 2268x }
221
222 // Apply protocol-specific options after socket creation.
223 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
224 1923x static std::error_code configure_ip_socket(int fd, int family) noexcept
225 {
226
2/2
✓ Branch 0 taken 1901 times.
✓ Branch 1 taken 22 times.
1923x if (family == AF_INET6)
227 {
228 22x int one = 1;
229 22x std::ignore =
230
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
231 22x }
232
233 1923x 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 240x static std::error_code configure_ip_acceptor(int fd, int family) noexcept
239 {
240
2/2
✓ Branch 0 taken 229 times.
✓ Branch 1 taken 11 times.
240x if (family == AF_INET6)
241 {
242 11x int val = 0;
243 11x std::ignore =
244
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
245 11x }
246
247 240x return set_fd_options(fd);
248 }
249
250 // Apply options for local (unix) sockets.
251 105x static std::error_code configure_local_socket(int fd) noexcept
252 {
253 105x 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 148x static std::error_code validate_assigned_fd(int fd) noexcept
260 {
261
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 146 times.
148x if (fd >= FD_SETSIZE)
262 2x return make_err(EMFILE);
263 146x return {};
264 148x }
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