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 +
        std::error_code on_set_option(
 
52 +
            int fd, int level, int optname,
 
53 +
            void const* data, std::size_t size) noexcept
 
54 +
        {
 
55 +
            if (::setsockopt(
 
56 +
                    fd, level, optname, data,
 
57 +
                    static_cast<socklen_t>(size)) != 0)
 
58 +
                return make_err(errno);
 
59 +
            return {};
 
60 +
        }
 
61 +
        static void pre_shutdown(int) noexcept {}
 
62 +
        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 +

 
89 +
    struct accept_policy
 
90 +
    {
 
91 +
        static int do_accept(
 
92 +
            int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
 
93 +
        {
 
94 +
            addrlen = sizeof(peer);
 
95 +
            int new_fd;
 
96 +
            do
 
97 +
            {
 
98 +
                new_fd = ::accept(
 
99 +
                    fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
 
100 +
            }
 
101 +
            while (new_fd < 0 && errno == EINTR);
 
102 +

 
103 +
            if (new_fd < 0)
 
104 +
                return new_fd;
 
105 +

 
106 +
            if (new_fd >= FD_SETSIZE)
 
107 +
            {
 
108 +
                ::close(new_fd);
 
109 +
                errno = EINVAL;
 
110 +
                return -1;
 
111 +
            }
 
112 +

 
113 +
            int flags = ::fcntl(new_fd, F_GETFL, 0);
 
114 +
            if (flags == -1)
 
115 +
            {
 
116 +
                int err = errno;
 
117 +
                ::close(new_fd);
 
118 +
                errno = err;
 
119 +
                return -1;
 
120 +
            }
 
121 +

 
122 +
            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 +
            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 +
            return new_fd;
 
148 +
        }
 
149 +
    };
 
150 +

 
151 +
    // Create a plain socket (no atomic flags -- select is POSIX-portable).
 
152 +
    static int create_socket(int family, int type, int protocol) noexcept
 
153 +
    {
 
154 +
        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 +
    static std::error_code set_fd_options(int fd) noexcept
 
160 +
    {
 
161 +
        int flags = ::fcntl(fd, F_GETFL, 0);
 
162 +
        if (flags == -1)
 
163 +
            return make_err(errno);
 
164 +
        if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
 
165 +
            return make_err(errno);
 
166 +
        if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
 
167 +
            return make_err(errno);
 
168 +

 
169 +
        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 +
        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 +
    configure_ip_socket(int fd, int family) noexcept
 
191 +
    {
 
192 +
        if (family == AF_INET6)
 
193 +
        {
 
194 +
            int one = 1;
 
195 +
            (void)::setsockopt(
 
196 +
                fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
 
197 +
        }
 
198 +

 
199 +
        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 +
    configure_ip_acceptor(int fd, int family) noexcept
 
206 +
    {
 
207 +
        if (family == AF_INET6)
 
208 +
        {
 
209 +
            int val = 0;
 
210 +
            (void)::setsockopt(
 
211 +
                fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
 
212 +
        }
 
213 +

 
214 +
        return set_fd_options(fd);
 
215 +
    }
 
216 +

 
217 +
    // Apply options for local (unix) sockets.
 
218 +
    static std::error_code
 
219 +
    configure_local_socket(int fd) noexcept
 
220 +
    {
 
221 +
        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 +
    validate_assigned_fd(int fd) noexcept
 
229 +
    {
 
230 +
        if (fd >= FD_SETSIZE)
 
231 +
            return make_err(EMFILE);
 
232 +
        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