TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Steve Gerbino
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
12 : #define BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
13 :
14 : #include <boost/corosio/detail/platform.hpp>
15 :
16 : #if BOOST_COROSIO_POSIX
17 :
18 : #include <boost/corosio/native/detail/make_err.hpp>
19 : #include <boost/corosio/native/detail/posix/large_file.hpp>
20 :
21 : #include <cerrno>
22 : #include <system_error>
23 :
24 : #include <fcntl.h>
25 : #include <sys/socket.h>
26 : #include <sys/stat.h>
27 :
28 : namespace boost::corosio::detail {
29 :
30 : /** Validate a caller-supplied socket fd for adoption.
31 :
32 : Non-mutating: interrogates the fd without changing any of its
33 : flags, so a rejected fd goes back to the caller untouched.
34 :
35 : @param fd The descriptor to validate.
36 : @param expected_type `SOCK_STREAM` or `SOCK_DGRAM`.
37 : @param is_ip Accept `AF_INET`/`AF_INET6` when true, `AF_UNIX`
38 : when false.
39 : @return Empty on success; `EBADF`, `EAFNOSUPPORT`, `EPROTOTYPE`,
40 : or the `errno` reported by the interrogating call.
41 : */
42 : inline std::error_code
43 HIT 371 : validate_socket_fd(int fd, int expected_type, bool is_ip) noexcept
44 : {
45 371 : if (fd < 0)
46 14 : return make_err(EBADF);
47 :
48 357 : sockaddr_storage st{};
49 357 : socklen_t st_len = sizeof(st);
50 357 : if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0)
51 5 : return make_err(errno);
52 352 : if (is_ip)
53 : {
54 39 : if (st.ss_family != AF_INET && st.ss_family != AF_INET6)
55 6 : return make_err(EAFNOSUPPORT);
56 : }
57 313 : else if (st.ss_family != AF_UNIX)
58 : {
59 2 : return make_err(EAFNOSUPPORT);
60 : }
61 :
62 344 : int sock_type = 0;
63 344 : socklen_t opt_len = sizeof(sock_type);
64 344 : if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &opt_len) != 0)
65 15 : return make_err(errno);
66 329 : if (sock_type != expected_type)
67 14 : return make_err(EPROTOTYPE);
68 :
69 315 : return {};
70 : }
71 :
72 : /** Validate a caller-supplied fd for adoption by @ref posix_stream_descriptor.
73 :
74 : Non-mutating: interrogates the fd without changing any of its
75 : flags, so a rejected fd goes back to the caller untouched. In
76 : particular `O_NONBLOCK` is not applied here — see
77 : @ref ensure_nonblocking.
78 :
79 : The file-type test is a reject-list, not an accept-list. The
80 : flagship descriptor kinds -- eventfd, timerfd, inotify, pidfd --
81 : are anonymous inodes whose `st_mode` type bits are all zero, so
82 : an accept-list would silently reject exactly the fds this type
83 : exists to carry.
84 :
85 : @param fd The descriptor to validate.
86 : @return Empty on success; `EBADF` for a closed or negative fd,
87 : `operation_not_supported` for a regular file, directory or
88 : block device, or the `errno` reported by `fstat`.
89 : */
90 : inline std::error_code
91 120 : validate_descriptor_fd(int fd) noexcept
92 : {
93 120 : if (fd < 0)
94 3 : return make_err(EBADF);
95 :
96 117 : file_stat_t st{};
97 117 : if (file_fstat(fd, &st) != 0)
98 1 : return make_err(errno);
99 :
100 : // Regular files, block devices and directories are the province of
101 : // stream_file / random_access_file, whose assign() already adopts
102 : // them; a reactor cannot report readiness for them anyway.
103 116 : switch (st.st_mode & S_IFMT)
104 : {
105 4 : case S_IFREG:
106 : case S_IFBLK:
107 : case S_IFDIR:
108 4 : return std::make_error_code(std::errc::operation_not_supported);
109 112 : default:
110 112 : return {};
111 : }
112 : }
113 :
114 : /** Validate a caller-supplied fd for adoption by a file object.
115 :
116 : Non-mutating. Accepts the kinds a file object can position and
117 : read: regular files, block devices, and character devices such
118 : as /dev/null and /dev/zero.
119 :
120 : This is an accept-list, the inverse of @ref validate_descriptor_fd's
121 : reject-list: a file object needs a positionable fd, and the
122 : anonymous inodes that motivate the descriptor reject-list are
123 : exactly what a file object cannot use.
124 :
125 : `S_IFCHR` is deliberately broad: it admits `/dev/null` and
126 : `/dev/zero`, but also non-seekable character devices such as a
127 : tty. Those pass here and then fail loudly at first I/O on the
128 : POSIX backends, where `preadv`/`pwritev` report `ESPIPE`.
129 :
130 : @param fd The descriptor to validate.
131 : @return Empty on success; `EBADF` for a closed or negative fd,
132 : `operation_not_supported` for a directory or a descriptor
133 : with no file position, or the `errno` from `fstat`.
134 : */
135 : inline std::error_code
136 420 : validate_file_fd(int fd) noexcept
137 : {
138 420 : if (fd < 0)
139 4 : return make_err(EBADF);
140 :
141 416 : file_stat_t st{};
142 416 : if (file_fstat(fd, &st) != 0)
143 MIS 0 : return make_err(errno);
144 :
145 HIT 416 : switch (st.st_mode & S_IFMT)
146 : {
147 411 : case S_IFREG:
148 : case S_IFBLK:
149 : case S_IFCHR:
150 411 : return {};
151 5 : default:
152 5 : return std::make_error_code(std::errc::operation_not_supported);
153 : }
154 : }
155 :
156 : /** Put a descriptor into non-blocking mode, idempotently.
157 :
158 : The reactor backends call it lazily on the first `read_some` /
159 : `write_some`, never from `assign()`; io_uring never calls it. The
160 : change is permanent: `O_NONBLOCK` lives on the
161 : shared open file description, so restoring it later would race
162 : every other holder of that description. A `wait()`-only user
163 : never reaches this function and their fd is never modified.
164 :
165 : @param fd The descriptor to modify.
166 : @return Empty on success, otherwise the `errno` from `fcntl`.
167 : */
168 : inline std::error_code
169 64 : ensure_nonblocking(int fd) noexcept
170 : {
171 64 : int flags = ::fcntl(fd, F_GETFL, 0);
172 64 : if (flags < 0)
173 1 : return make_err(errno);
174 63 : if (flags & O_NONBLOCK)
175 9 : return {};
176 54 : if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
177 MIS 0 : return make_err(errno);
178 HIT 54 : return {};
179 : }
180 :
181 : } // namespace boost::corosio::detail
182 :
183 : #endif // BOOST_COROSIO_POSIX
184 :
185 : #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
|