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