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

96.1% Lines (49/51) 100.0% List of functions (4/4) 74.1% Branches (40/54)
validate_fd.hpp
f(x) Functions (4)
Line Branch 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 339x validate_socket_fd(int fd, int expected_type, bool is_ip) noexcept
44 {
45
2/2
✓ Branch 0 taken 325 times.
✓ Branch 1 taken 14 times.
339x if (fd < 0)
46 14x return make_err(EBADF);
47
48 325x sockaddr_storage st{};
49 325x socklen_t st_len = sizeof(st);
50
3/4
✓ Branch 0 taken 325 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 323 times.
325x if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0)
51
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
52
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 286 times.
323x if (is_ip)
53 {
54
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
37x if (st.ss_family != AF_INET && st.ss_family != AF_INET6)
55 6x return make_err(EAFNOSUPPORT);
56 31x }
57
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 284 times.
286x else if (st.ss_family != AF_UNIX)
58 {
59 2x return make_err(EAFNOSUPPORT);
60 }
61
62 315x int sock_type = 0;
63 315x socklen_t opt_len = sizeof(sock_type);
64
3/4
✓ Branch 0 taken 315 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 309 times.
315x if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &opt_len) != 0)
65
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6x return make_err(errno);
66
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 295 times.
309x if (sock_type != expected_type)
67 14x return make_err(EPROTOTYPE);
68
69 295x return {};
70 339x }
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 119x validate_descriptor_fd(int fd) noexcept
92 {
93
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 3 times.
119x if (fd < 0)
94 3x return make_err(EBADF);
95
96 116x file_stat_t st{};
97
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 115 times.
116x if (file_fstat(fd, &st) != 0)
98
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
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
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 111 times.
115x switch (st.st_mode & S_IFMT)
104 {
105 case S_IFREG:
106 case S_IFBLK:
107 case S_IFDIR:
108 4x return std::make_error_code(std::errc::operation_not_supported);
109 default:
110 111x return {};
111 }
112 119x }
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 416x validate_file_fd(int fd) noexcept
137 {
138
2/2
✓ Branch 0 taken 412 times.
✓ Branch 1 taken 4 times.
416x if (fd < 0)
139 4x return make_err(EBADF);
140
141 412x file_stat_t st{};
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 412 times.
412x if (file_fstat(fd, &st) != 0)
143 ✗ return make_err(errno);
144
145
2/2
✓ Branch 0 taken 408 times.
✓ Branch 1 taken 4 times.
412x switch (st.st_mode & S_IFMT)
146 {
147 case S_IFREG:
148 case S_IFBLK:
149 case S_IFCHR:
150 408x return {};
151 default:
152 4x return std::make_error_code(std::errc::operation_not_supported);
153 }
154 416x }
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
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64x int flags = ::fcntl(fd, F_GETFL, 0);
172
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 63 times.
64x if (flags < 0)
173
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x return make_err(errno);
174
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 9 times.
63x if (flags & O_NONBLOCK)
175 9x return {};
176
2/4
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 54 times.
54x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
177 ✗ return make_err(errno);
178 54x return {};
179 64x }
180
181 } // namespace boost::corosio::detail
182
183 #endif // BOOST_COROSIO_POSIX
184
185 #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
186