1  
//
1  
//
2  
// Copyright (c) 2026 Michael Vandeberg
2  
// Copyright (c) 2026 Michael Vandeberg
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
12  

12  

13  
#include <boost/corosio/detail/platform.hpp>
13  
#include <boost/corosio/detail/platform.hpp>
14  

14  

15  
#if BOOST_COROSIO_HAS_SELECT
15  
#if BOOST_COROSIO_HAS_SELECT
16  

16  

17  
#include <boost/corosio/native/detail/make_err.hpp>
17  
#include <boost/corosio/native/detail/make_err.hpp>
18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19  

19  

20  
#include <system_error>
20  
#include <system_error>
21  

21  

22  
#include <errno.h>
22  
#include <errno.h>
23  
#include <fcntl.h>
23  
#include <fcntl.h>
24  
#include <netinet/in.h>
24  
#include <netinet/in.h>
25  
#include <sys/select.h>
25  
#include <sys/select.h>
26  
#include <sys/socket.h>
26  
#include <sys/socket.h>
27  
#include <unistd.h>
27  
#include <unistd.h>
28  

28  

29  
/* select backend traits.
29  
/* select backend traits.
30  

30  

31  
   Captures the platform-specific behavior of the portable select() backend:
31  
   Captures the platform-specific behavior of the portable select() backend:
32  
   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation,
32  
   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation,
33  
   conditional SO_NOSIGPIPE, sendmsg(MSG_NOSIGNAL) where available,
33  
   conditional SO_NOSIGPIPE, sendmsg(MSG_NOSIGNAL) where available,
34  
   and accept()+fcntl for accepted connections.
34  
   and accept()+fcntl for accepted connections.
35  
*/
35  
*/
36  

36  

37  
namespace boost::corosio::detail {
37  
namespace boost::corosio::detail {
38  

38  

39  
class select_scheduler;
39  
class select_scheduler;
40  

40  

41  
struct select_traits
41  
struct select_traits
42  
{
42  
{
43  
    using scheduler_type    = select_scheduler;
43  
    using scheduler_type    = select_scheduler;
44  
    using desc_state_type   = reactor_descriptor_state;
44  
    using desc_state_type   = reactor_descriptor_state;
45  

45  

46  
    static constexpr bool needs_write_notification = true;
46  
    static constexpr bool needs_write_notification = true;
47  

47  

48  
    // No extra per-socket state or lifecycle hooks needed for select.
48  
    // No extra per-socket state or lifecycle hooks needed for select.
49  
    struct stream_socket_hook
49  
    struct stream_socket_hook
50  
    {
50  
    {
51  
        std::error_code on_set_option(
51  
        std::error_code on_set_option(
52  
            int fd, int level, int optname,
52  
            int fd, int level, int optname,
53  
            void const* data, std::size_t size) noexcept
53  
            void const* data, std::size_t size) noexcept
54  
        {
54  
        {
55  
            if (::setsockopt(
55  
            if (::setsockopt(
56  
                    fd, level, optname, data,
56  
                    fd, level, optname, data,
57  
                    static_cast<socklen_t>(size)) != 0)
57  
                    static_cast<socklen_t>(size)) != 0)
58  
                return make_err(errno);
58  
                return make_err(errno);
59  
            return {};
59  
            return {};
60  
        }
60  
        }
61  
        static void pre_shutdown(int) noexcept {}
61  
        static void pre_shutdown(int) noexcept {}
62  
        static void pre_destroy(int) noexcept {}
62  
        static void pre_destroy(int) noexcept {}
63  
    };
63  
    };
64  

64  

65  
    struct write_policy
65  
    struct write_policy
66  
    {
66  
    {
67  
        static ssize_t write(int fd, iovec* iovecs, int count) noexcept
67  
        static ssize_t write(int fd, iovec* iovecs, int count) noexcept
68  
        {
68  
        {
69  
            msghdr msg{};
69  
            msghdr msg{};
70  
            msg.msg_iov    = iovecs;
70  
            msg.msg_iov    = iovecs;
71  
            msg.msg_iovlen = static_cast<std::size_t>(count);
71  
            msg.msg_iovlen = static_cast<std::size_t>(count);
72  

72  

73  
#ifdef MSG_NOSIGNAL
73  
#ifdef MSG_NOSIGNAL
74  
            constexpr int send_flags = MSG_NOSIGNAL;
74  
            constexpr int send_flags = MSG_NOSIGNAL;
75  
#else
75  
#else
76  
            constexpr int send_flags = 0;
76  
            constexpr int send_flags = 0;
77  
#endif
77  
#endif
78  

78  

79  
            ssize_t n;
79  
            ssize_t n;
80  
            do
80  
            do
81  
            {
81  
            {
82  
                n = ::sendmsg(fd, &msg, send_flags);
82  
                n = ::sendmsg(fd, &msg, send_flags);
83  
            }
83  
            }
84  
            while (n < 0 && errno == EINTR);
84  
            while (n < 0 && errno == EINTR);
85  
            return n;
85  
            return n;
86  
        }
86  
        }
87  
    };
87  
    };
88  

88  

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

102  

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

105  

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

112  

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

121  

122  
            if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
122  
            if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
123  
            {
123  
            {
124  
                int err = errno;
124  
                int err = errno;
125  
                ::close(new_fd);
125  
                ::close(new_fd);
126  
                errno = err;
126  
                errno = err;
127  
                return -1;
127  
                return -1;
128  
            }
128  
            }
129  

129  

130  
            if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
130  
            if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
131  
            {
131  
            {
132  
                int err = errno;
132  
                int err = errno;
133  
                ::close(new_fd);
133  
                ::close(new_fd);
134  
                errno = err;
134  
                errno = err;
135  
                return -1;
135  
                return -1;
136  
            }
136  
            }
137  

137  

138  
#ifdef SO_NOSIGPIPE
138  
#ifdef SO_NOSIGPIPE
139  
            // Best-effort: SO_NOSIGPIPE is a safety net; write paths
139  
            // Best-effort: SO_NOSIGPIPE is a safety net; write paths
140  
            // also use MSG_NOSIGNAL where available. Failure here
140  
            // also use MSG_NOSIGNAL where available. Failure here
141  
            // should not prevent the accepted connection from being used.
141  
            // should not prevent the accepted connection from being used.
142  
            int one = 1;
142  
            int one = 1;
143  
            (void)::setsockopt(
143  
            (void)::setsockopt(
144  
                new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
144  
                new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
145  
#endif
145  
#endif
146  

146  

147  
            return new_fd;
147  
            return new_fd;
148  
        }
148  
        }
149  
    };
149  
    };
150  

150  

151  
    // Create a plain socket (no atomic flags -- select is POSIX-portable).
151  
    // Create a plain socket (no atomic flags -- select is POSIX-portable).
152  
    static int create_socket(int family, int type, int protocol) noexcept
152  
    static int create_socket(int family, int type, int protocol) noexcept
153  
    {
153  
    {
154  
        return ::socket(family, type, protocol);
154  
        return ::socket(family, type, protocol);
155  
    }
155  
    }
156  

156  

157  
    // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
157  
    // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
158  
    // Caller is responsible for closing fd on error.
158  
    // Caller is responsible for closing fd on error.
159  
    static std::error_code set_fd_options(int fd) noexcept
159  
    static std::error_code set_fd_options(int fd) noexcept
160  
    {
160  
    {
161  
        int flags = ::fcntl(fd, F_GETFL, 0);
161  
        int flags = ::fcntl(fd, F_GETFL, 0);
162  
        if (flags == -1)
162  
        if (flags == -1)
163  
            return make_err(errno);
163  
            return make_err(errno);
164  
        if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
164  
        if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
165  
            return make_err(errno);
165  
            return make_err(errno);
166  
        if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
166  
        if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
167  
            return make_err(errno);
167  
            return make_err(errno);
168  

168  

169  
        if (fd >= FD_SETSIZE)
169  
        if (fd >= FD_SETSIZE)
170  
            return make_err(EMFILE);
170  
            return make_err(EMFILE);
171  

171  

172  
#ifdef SO_NOSIGPIPE
172  
#ifdef SO_NOSIGPIPE
173  
        // Best-effort: SO_NOSIGPIPE is a safety net; write paths
173  
        // Best-effort: SO_NOSIGPIPE is a safety net; write paths
174  
        // also use MSG_NOSIGNAL where available. Match develop's
174  
        // also use MSG_NOSIGNAL where available. Match develop's
175  
        // predominant behavior of ignoring failures here rather
175  
        // predominant behavior of ignoring failures here rather
176  
        // than failing socket creation.
176  
        // than failing socket creation.
177  
        {
177  
        {
178  
            int one = 1;
178  
            int one = 1;
179  
            (void)::setsockopt(
179  
            (void)::setsockopt(
180  
                fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
180  
                fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
181  
        }
181  
        }
182  
#endif
182  
#endif
183  

183  

184  
        return {};
184  
        return {};
185  
    }
185  
    }
186  

186  

187  
    // Apply protocol-specific options after socket creation.
187  
    // Apply protocol-specific options after socket creation.
188  
    // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
188  
    // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
189  
    static std::error_code
189  
    static std::error_code
190  
    configure_ip_socket(int fd, int family) noexcept
190  
    configure_ip_socket(int fd, int family) noexcept
191  
    {
191  
    {
192  
        if (family == AF_INET6)
192  
        if (family == AF_INET6)
193  
        {
193  
        {
194  
            int one = 1;
194  
            int one = 1;
195  
            (void)::setsockopt(
195  
            (void)::setsockopt(
196  
                fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
196  
                fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
197  
        }
197  
        }
198  

198  

199  
        return set_fd_options(fd);
199  
        return set_fd_options(fd);
200  
    }
200  
    }
201  

201  

202  
    // Apply protocol-specific options for acceptor sockets.
202  
    // Apply protocol-specific options for acceptor sockets.
203  
    // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
203  
    // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
204  
    static std::error_code
204  
    static std::error_code
205  
    configure_ip_acceptor(int fd, int family) noexcept
205  
    configure_ip_acceptor(int fd, int family) noexcept
206  
    {
206  
    {
207  
        if (family == AF_INET6)
207  
        if (family == AF_INET6)
208  
        {
208  
        {
209  
            int val = 0;
209  
            int val = 0;
210  
            (void)::setsockopt(
210  
            (void)::setsockopt(
211  
                fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
211  
                fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
212  
        }
212  
        }
213  

213  

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

216  

217  
    // Apply options for local (unix) sockets.
217  
    // Apply options for local (unix) sockets.
218  
    static std::error_code
218  
    static std::error_code
219  
    configure_local_socket(int fd) noexcept
219  
    configure_local_socket(int fd) noexcept
220  
    {
220  
    {
221  
        return set_fd_options(fd);
221  
        return set_fd_options(fd);
222  
    }
222  
    }
223  

223  

224  
    // Non-mutating validation for fds adopted via assign(). Select's
224  
    // Non-mutating validation for fds adopted via assign(). Select's
225  
    // reactor cannot handle fds above FD_SETSIZE, so reject them up
225  
    // reactor cannot handle fds above FD_SETSIZE, so reject them up
226  
    // front instead of letting FD_SET clobber unrelated memory.
226  
    // front instead of letting FD_SET clobber unrelated memory.
227  
    static std::error_code
227  
    static std::error_code
228  
    validate_assigned_fd(int fd) noexcept
228  
    validate_assigned_fd(int fd) noexcept
229  
    {
229  
    {
230  
        if (fd >= FD_SETSIZE)
230  
        if (fd >= FD_SETSIZE)
231  
            return make_err(EMFILE);
231  
            return make_err(EMFILE);
232  
        return {};
232  
        return {};
233  
    }
233  
    }
234  
};
234  
};
235  

235  

236  
} // namespace boost::corosio::detail
236  
} // namespace boost::corosio::detail
237  

237  

238  
#endif // BOOST_COROSIO_HAS_SELECT
238  
#endif // BOOST_COROSIO_HAS_SELECT
239  

239  

240  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
240  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP