include/boost/corosio/native/detail/validate_fd.hpp

96.0% Lines (48 / 50) 100.0% Functions (4 / 4)
validate_fd.hpp
f(x) Functions (4)
Line TLA Hits 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 552x validate_socket_fd(int fd, int expected_type, bool is_ip) noexcept
44 {
45 552x if (fd < 0)
46 21x return make_err(EBADF);
47
48 531x sockaddr_storage st{};
49 531x socklen_t st_len = sizeof(st);
50 531x if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0)
51 9x return make_err(errno);
52 522x if (is_ip)
53 {
54 59x if (st.ss_family != AF_INET && st.ss_family != AF_INET6)
55 9x return make_err(EAFNOSUPPORT);
56 }
57 463x else if (st.ss_family != AF_UNIX)
58 {
59 2x return make_err(EAFNOSUPPORT);
60 }
61
62 511x int sock_type = 0;
63 511x socklen_t opt_len = sizeof(sock_type);
64 511x if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &opt_len) != 0)
65 27x return make_err(errno);
66 484x if (sock_type != expected_type)
67 20x return make_err(EPROTOTYPE);
68
69 464x 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 174x validate_descriptor_fd(int fd) noexcept
92 {
93 174x if (fd < 0)
94 4x return make_err(EBADF);
95
96 170x file_stat_t st{};
97 170x if (file_fstat(fd, &st) != 0)
98 1x 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 169x switch (st.st_mode & S_IFMT)
104 {
105 5x case S_IFREG:
106 case S_IFBLK:
107 case S_IFDIR:
108 5x return std::make_error_code(std::errc::operation_not_supported);
109 164x default:
110 164x 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 428x validate_file_fd(int fd) noexcept
137 {
138 428x if (fd < 0)
139 6x return make_err(EBADF);
140
141 422x file_stat_t st{};
142 422x if (file_fstat(fd, &st) != 0)
143 ✗ return make_err(errno);
144
145 422x switch (st.st_mode & S_IFMT)
146 {
147 415x case S_IFREG:
148 case S_IFBLK:
149 case S_IFCHR:
150 415x return {};
151 7x default:
152 7x 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 64x ensure_nonblocking(int fd) noexcept
170 {
171 64x int flags = ::fcntl(fd, F_GETFL, 0);
172 64x if (flags < 0)
173 1x return make_err(errno);
174 63x if (flags & O_NONBLOCK)
175 9x return {};
176 54x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
177 ✗ return make_err(errno);
178 54x return {};
179 }
180
181 } // namespace boost::corosio::detail
182
183 #endif // BOOST_COROSIO_POSIX
184
185 #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
186