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

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