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

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