1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
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_OP_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_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 -
#include <boost/corosio/native/detail/reactor/reactor_op.hpp>
 
17  

16  

18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
17  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19 -
#include <errno.h>
 
20 -
#include <fcntl.h>
 
21 -
#include <sys/socket.h>
 
22 -
#include <unistd.h>
 
23 -

 
24 -
/*
 
25 -
    File descriptors are registered with the select scheduler once (via
 
26 -
    select_descriptor_state) and stay registered until closed.
 
27 -

 
28 -
    select() is level-triggered but the descriptor_state pattern
 
29 -
    (designed for edge-triggered) works correctly: is_enqueued_ CAS
 
30 -
    prevents double-enqueue, add_ready_events is idempotent, and
 
31 -
    EAGAIN ops stay parked until the next select() re-reports readiness.
 
32 -

 
33 -
    cancel() captures shared_from_this() into op.impl_ptr to prevent
 
34 -
    use-after-free when the socket is closed with pending ops.
 
35 -

 
36 -
    Writes use sendmsg(MSG_NOSIGNAL) on Linux. On macOS/BSD where
 
37 -
    MSG_NOSIGNAL may be absent, SO_NOSIGPIPE is set at socket creation
 
38 -
    and accepted-socket setup instead.
 
39 -
*/
 
40 -

 
41  

18  

42  
namespace boost::corosio::detail {
19  
namespace boost::corosio::detail {
43 -
// Forward declarations
 
44 -
class select_tcp_socket;
 
45 -
class select_tcp_acceptor;
 
46 -
struct select_op;
 
47 -

 
48 -
// Forward declaration
 
49 -
class select_scheduler;
 
50 -

 
51  

20  

52  
/// Per-descriptor state for persistent select registration.
21  
/// Per-descriptor state for persistent select registration.
53  
struct select_descriptor_state final : reactor_descriptor_state
22  
struct select_descriptor_state final : reactor_descriptor_state
54 -

 
55 -
/// select base operation — thin wrapper over reactor_op.
 
56 -
struct select_op : reactor_op<select_tcp_socket, select_tcp_acceptor>
 
57 -
{
 
58 -
    void operator()() override;
 
59 -
};
 
60 -

 
61 -
/// select connect operation.
 
62 -
struct select_connect_op final : reactor_connect_op<select_op>
 
63 -
{
 
64 -
    void operator()() override;
 
65 -
    void cancel() noexcept override;
 
66 -
};
 
67 -

 
68 -
/// select scatter-read operation.
 
69 -
struct select_read_op final : reactor_read_op<select_op>
 
70 -
{
 
71 -
    void cancel() noexcept override;
 
72 -
};
 
73 -

 
74 -
/** Provides sendmsg() with EINTR retry for select writes.
 
75 -

 
76 -
    Uses MSG_NOSIGNAL where available (Linux). On platforms without
 
77 -
    it (macOS/BSD), SO_NOSIGPIPE is set at socket creation time
 
78 -
    and flags=0 is used here.
 
79 -
*/
 
80 -
struct select_write_policy
 
81 -
{
 
82 -
    static ssize_t write(int fd, iovec* iovecs, int count) noexcept
 
83 -
    {
 
84 -
        msghdr msg{};
 
85 -
        msg.msg_iov    = iovecs;
 
86 -
        msg.msg_iovlen = static_cast<std::size_t>(count);
 
87 -

 
88 -
#ifdef MSG_NOSIGNAL
 
89 -
        constexpr int send_flags = MSG_NOSIGNAL;
 
90 -
#else
 
91 -
        constexpr int send_flags = 0;
 
92 -
#endif
 
93 -

 
94 -
        ssize_t n;
 
95 -
        do
 
96 -
        {
 
97 -
            n = ::sendmsg(fd, &msg, send_flags);
 
98 -
        }
 
99 -
        while (n < 0 && errno == EINTR);
 
100 -
        return n;
 
101 -
    }
 
102 -
};
 
103 -

 
104 -
/// select gather-write operation.
 
105 -
struct select_write_op final : reactor_write_op<select_op, select_write_policy>
 
106 -
{
 
107 -
    void cancel() noexcept override;
 
108 -
};
 
109 -

 
110 -
/** Provides accept() + fcntl(O_NONBLOCK|FD_CLOEXEC) with FD_SETSIZE check.
 
111 -

 
112 -
    Uses accept() instead of accept4() for broader POSIX compatibility.
 
113 -
*/
 
114 -
struct select_accept_policy
 
115 -
{
 
116 -
    static int do_accept(int fd, sockaddr_storage& peer) noexcept
 
117 -
    {
 
118 -
        socklen_t addrlen = sizeof(peer);
 
119 -
        int new_fd;
 
120 -
        do
 
121 -
        {
 
122 -
            new_fd = ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
 
123 -
        }
 
124 -
        while (new_fd < 0 && errno == EINTR);
 
125 -

 
126 -
        if (new_fd < 0)
 
127 -
            return new_fd;
 
128 -

 
129 -
        if (new_fd >= FD_SETSIZE)
 
130 -
        {
 
131 -
            ::close(new_fd);
 
132 -
            errno = EINVAL;
 
133 -
            return -1;
 
134 -
        }
 
135 -

 
136 -
        int flags = ::fcntl(new_fd, F_GETFL, 0);
 
137 -
        if (flags == -1)
 
138 -
        {
 
139 -
            int err = errno;
 
140 -
            ::close(new_fd);
 
141 -
            errno = err;
 
142 -
            return -1;
 
143 -
        }
 
144 -

 
145 -
        if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
 
146 -
        {
 
147 -
            int err = errno;
 
148 -
            ::close(new_fd);
 
149 -
            errno = err;
 
150 -
            return -1;
 
151 -
        }
 
152 -

 
153 -
        if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
 
154 -
        {
 
155 -
            int err = errno;
 
156 -
            ::close(new_fd);
 
157 -
            errno = err;
 
158 -
            return -1;
 
159 -
        }
 
160 -

 
161 -
#ifdef SO_NOSIGPIPE
 
162 -
        int one = 1;
 
163 -
        if (::setsockopt(new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) ==
 
164 -
            -1)
 
165 -
        {
 
166 -
            int err = errno;
 
167 -
            ::close(new_fd);
 
168 -
            errno = err;
 
169 -
            return -1;
 
170 -
        }
 
171 -
#endif
 
172 -

 
173 -
        return new_fd;
 
174 -
    }
 
175 -
};
 
176 -

 
177 -
/// select accept operation.
 
178 -
struct select_accept_op final
 
179 -
    : reactor_accept_op<select_op, select_accept_policy>
 
180 -
{
 
181 -
    void operator()() override;
 
182 -
    void cancel() noexcept override;
 
183 -
};
 
184  
{};
23  
{};
185  

24  

186  
} // namespace boost::corosio::detail
25  
} // namespace boost::corosio::detail
187  

26  

188  
#endif // BOOST_COROSIO_HAS_SELECT
27  
#endif // BOOST_COROSIO_HAS_SELECT
189  

28  

190  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
29  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP