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

70.4% Lines (50/71) 100.0% List of functions (11/11)
select_traits.hpp
f(x) Functions (11)
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 22612x static void pre_shutdown(int) noexcept {}
62 7511x static void pre_destroy(int) noexcept {}
63 };
64
65 struct write_policy
66 {
67 95803x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
68 {
69 95803x msghdr msg{};
70 95803x msg.msg_iov = iovecs;
71 95803x msg.msg_iovlen = static_cast<std::size_t>(count);
72
73 #ifdef MSG_NOSIGNAL
74 95803x 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 95803x n = ::sendmsg(fd, &msg, send_flags);
83 }
84 95803x while (n < 0 && errno == EINTR);
85 95803x return n;
86 }
87 };
88
89 struct accept_policy
90 {
91 4986x static int do_accept(
92 int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
93 {
94 4986x addrlen = sizeof(peer);
95 int new_fd;
96 do
97 {
98 4986x new_fd = ::accept(
99 fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
100 }
101 4986x while (new_fd < 0 && errno == EINTR);
102
103 4986x if (new_fd < 0)
104 2493x return new_fd;
105
106 2493x if (new_fd >= FD_SETSIZE)
107 {
108 ::close(new_fd);
109 errno = EINVAL;
110 return -1;
111 }
112
113 2493x int flags = ::fcntl(new_fd, F_GETFL, 0);
114 2493x if (flags == -1)
115 {
116 int err = errno;
117 ::close(new_fd);
118 errno = err;
119 return -1;
120 }
121
122 2493x if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
123 {
124 int err = errno;
125 ::close(new_fd);
126 errno = err;
127 return -1;
128 }
129
130 2493x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
131 {
132 int err = errno;
133 ::close(new_fd);
134 errno = err;
135 return -1;
136 }
137
138 #ifdef SO_NOSIGPIPE
139 // Best-effort: SO_NOSIGPIPE is a safety net; write paths
140 // also use MSG_NOSIGNAL where available. Failure here
141 // should not prevent the accepted connection from being used.
142 int one = 1;
143 (void)::setsockopt(
144 new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
145 #endif
146
147 2493x return new_fd;
148 }
149 };
150
151 // Create a plain socket (no atomic flags -- select is POSIX-portable).
152 2654x static int create_socket(int family, int type, int protocol) noexcept
153 {
154 2654x return ::socket(family, type, protocol);
155 }
156
157 // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
158 // Caller is responsible for closing fd on error.
159 2654x static std::error_code set_fd_options(int fd) noexcept
160 {
161 2654x int flags = ::fcntl(fd, F_GETFL, 0);
162 2654x if (flags == -1)
163 return make_err(errno);
164 2654x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
165 return make_err(errno);
166 2654x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
167 return make_err(errno);
168
169 2654x if (fd >= FD_SETSIZE)
170 return make_err(EMFILE);
171
172 #ifdef SO_NOSIGPIPE
173 // Best-effort: SO_NOSIGPIPE is a safety net; write paths
174 // also use MSG_NOSIGNAL where available. Match develop's
175 // predominant behavior of ignoring failures here rather
176 // than failing socket creation.
177 {
178 int one = 1;
179 (void)::setsockopt(
180 fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
181 }
182 #endif
183
184 2654x return {};
185 }
186
187 // Apply protocol-specific options after socket creation.
188 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
189 static std::error_code
190 2560x configure_ip_socket(int fd, int family) noexcept
191 {
192 2560x if (family == AF_INET6)
193 {
194 14x int one = 1;
195 14x (void)::setsockopt(
196 fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
197 }
198
199 2560x return set_fd_options(fd);
200 }
201
202 // Apply protocol-specific options for acceptor sockets.
203 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
204 static std::error_code
205 74x configure_ip_acceptor(int fd, int family) noexcept
206 {
207 74x if (family == AF_INET6)
208 {
209 9x int val = 0;
210 9x (void)::setsockopt(
211 fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
212 }
213
214 74x return set_fd_options(fd);
215 }
216
217 // Apply options for local (unix) sockets.
218 static std::error_code
219 20x configure_local_socket(int fd) noexcept
220 {
221 20x return set_fd_options(fd);
222 }
223
224 // Non-mutating validation for fds adopted via assign(). Select's
225 // reactor cannot handle fds above FD_SETSIZE, so reject them up
226 // front instead of letting FD_SET clobber unrelated memory.
227 static std::error_code
228 14x validate_assigned_fd(int fd) noexcept
229 {
230 14x if (fd >= FD_SETSIZE)
231 return make_err(EMFILE);
232 14x return {};
233 }
234 };
235
236 } // namespace boost::corosio::detail
237
238 #endif // BOOST_COROSIO_HAS_SELECT
239
240 #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
241