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