1 -
//
 
2 -
// Copyright (c) 2026 Steve Gerbino
 
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_OP_HPP
 
11 -
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
 
12 -

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

 
15 -
#if BOOST_COROSIO_HAS_SELECT
 
16 -

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

 
20 -
#include <errno.h>
 
21 -
#include <fcntl.h>
 
22 -
#include <sys/socket.h>
 
23 -
#include <unistd.h>
 
24 -

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

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

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

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

 
42 -
namespace boost::corosio::detail {
 
43 -

 
44 -
// Forward declarations
 
45 -
class select_tcp_socket;
 
46 -
class select_tcp_acceptor;
 
47 -
struct select_op;
 
48 -

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

 
52 -
/// Per-descriptor state for persistent select registration.
 
53 -
struct select_descriptor_state final : reactor_descriptor_state
 
54 -
{};
 
55 -

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

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

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

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

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

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

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

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

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

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

 
130 -
        if (new_fd < 0)
 
131 -
            return new_fd;
 
132 -

 
133 -
        if (new_fd >= FD_SETSIZE)
 
134 -
        {
 
135 -
            ::close(new_fd);
 
136 -
            errno = EINVAL;
 
137 -
            return -1;
 
138 -
        }
 
139 -

 
140 -
        int flags = ::fcntl(new_fd, F_GETFL, 0);
 
141 -
        if (flags == -1)
 
142 -
        {
 
143 -
            int err = errno;
 
144 -
            ::close(new_fd);
 
145 -
            errno = err;
 
146 -
            return -1;
 
147 -
        }
 
148 -

 
149 -
        if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
 
150 -
        {
 
151 -
            int err = errno;
 
152 -
            ::close(new_fd);
 
153 -
            errno = err;
 
154 -
            return -1;
 
155 -
        }
 
156 -

 
157 -
        if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
 
158 -
        {
 
159 -
            int err = errno;
 
160 -
            ::close(new_fd);
 
161 -
            errno = err;
 
162 -
            return -1;
 
163 -
        }
 
164 -

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

 
177 -
        return new_fd;
 
178 -
    }
 
179 -
};
 
180 -

 
181 -
/// select accept operation.
 
182 -
struct select_accept_op final
 
183 -
    : reactor_accept_op<select_op, select_accept_policy>
 
184 -
{
 
185 -
    void operator()() override;
 
186 -
    void cancel() noexcept override;
 
187 -
};
 
188 -

 
189 -
} // namespace boost::corosio::detail
 
190 -

 
191 -
#endif // BOOST_COROSIO_HAS_SELECT
 
192 -

 
193 -
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP