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   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // 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)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
8   // 9   //
9   10  
10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP 11   #ifndef BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP 12   #define BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP
12   13  
13   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
14   15  
15   #if BOOST_COROSIO_POSIX 16   #if BOOST_COROSIO_POSIX
16   17  
17   #include <boost/corosio/native/detail/make_err.hpp> 18   #include <boost/corosio/native/detail/make_err.hpp>
18   19  
19   #include <cerrno> 20   #include <cerrno>
20   #include <system_error> 21   #include <system_error>
21   22  
  23 + #include <fcntl.h>
22   #include <sys/socket.h> 24   #include <sys/socket.h>
  25 + #include <sys/stat.h>
23   26  
24   namespace boost::corosio::detail { 27   namespace boost::corosio::detail {
25   28  
26   /** Validate a caller-supplied socket fd for adoption. 29   /** Validate a caller-supplied socket fd for adoption.
27   30  
28   Non-mutating: interrogates the fd without changing any of its 31   Non-mutating: interrogates the fd without changing any of its
29   flags, so a rejected fd goes back to the caller untouched. 32   flags, so a rejected fd goes back to the caller untouched.
30   33  
31   @param fd The descriptor to validate. 34   @param fd The descriptor to validate.
32   @param expected_type `SOCK_STREAM` or `SOCK_DGRAM`. 35   @param expected_type `SOCK_STREAM` or `SOCK_DGRAM`.
33   @param is_ip Accept `AF_INET`/`AF_INET6` when true, `AF_UNIX` 36   @param is_ip Accept `AF_INET`/`AF_INET6` when true, `AF_UNIX`
34   when false. 37   when false.
35   @return Empty on success; `EBADF`, `EAFNOSUPPORT`, `EPROTOTYPE`, 38   @return Empty on success; `EBADF`, `EAFNOSUPPORT`, `EPROTOTYPE`,
36   or the `errno` reported by the interrogating call. 39   or the `errno` reported by the interrogating call.
37   */ 40   */
38   inline std::error_code 41   inline std::error_code
HITCBC 39   385 validate_socket_fd(int fd, int expected_type, bool is_ip) noexcept 42   385 validate_socket_fd(int fd, int expected_type, bool is_ip) noexcept
40   { 43   {
HITCBC 41   385 if (fd < 0) 44   385 if (fd < 0)
HITCBC 42   14 return make_err(EBADF); 45   14 return make_err(EBADF);
43   46  
HITCBC 44   371 sockaddr_storage st{}; 47   371 sockaddr_storage st{};
HITCBC 45   371 socklen_t st_len = sizeof(st); 48   371 socklen_t st_len = sizeof(st);
HITCBC 46   371 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0) 49   371 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0)
HITCBC 47   5 return make_err(errno); 50   5 return make_err(errno);
HITCBC 48   366 if (is_ip) 51   366 if (is_ip)
49   { 52   {
HITCBC 50   49 if (st.ss_family != AF_INET && st.ss_family != AF_INET6) 53   49 if (st.ss_family != AF_INET && st.ss_family != AF_INET6)
HITCBC 51   6 return make_err(EAFNOSUPPORT); 54   6 return make_err(EAFNOSUPPORT);
52   } 55   }
HITCBC 53   317 else if (st.ss_family != AF_UNIX) 56   317 else if (st.ss_family != AF_UNIX)
54   { 57   {
HITCBC 55   2 return make_err(EAFNOSUPPORT); 58   2 return make_err(EAFNOSUPPORT);
56   } 59   }
57   60  
HITCBC 58   358 int sock_type = 0; 61   358 int sock_type = 0;
HITCBC 59   358 socklen_t opt_len = sizeof(sock_type); 62   358 socklen_t opt_len = sizeof(sock_type);
HITCBC 60   358 if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &opt_len) != 0) 63   358 if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &opt_len) != 0)
HITCBC 61   15 return make_err(errno); 64   15 return make_err(errno);
HITCBC 62   343 if (sock_type != expected_type) 65   343 if (sock_type != expected_type)
HITCBC 63   20 return make_err(EPROTOTYPE); 66   20 return make_err(EPROTOTYPE);
64   67  
HITGNC   68 + 323 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
HITGNC   90 + 62 validate_descriptor_fd(int fd) noexcept
  91 + {
HITGNC   92 + 62 if (fd < 0)
HITGNC   93 + 3 return make_err(EBADF);
  94 +
HITGNC   95 + 59 struct stat st{};
HITGNC   96 + 59 if (::fstat(fd, &st) != 0)
HITGNC   97 + 1 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.
HITGNC   102 + 58 switch (st.st_mode & S_IFMT)
  103 + {
HITGNC   104 + 6 case S_IFREG:
  105 + case S_IFBLK:
  106 + case S_IFDIR:
HITGNC   107 + 6 return std::make_error_code(std::errc::operation_not_supported);
HITGNC   108 + 52 default:
HITGNC   109 + 52 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
HITGNC   135 + 23 validate_file_fd(int fd) noexcept
  136 + {
HITGNC   137 + 23 if (fd < 0)
HITGNC   138 + 4 return make_err(EBADF);
  139 +
HITGNC   140 + 19 struct stat st{};
HITGNC   141 + 19 if (::fstat(fd, &st) != 0)
MISUNC   142 + ✗ return make_err(errno);
  143 +
HITGNC   144 + 19 switch (st.st_mode & S_IFMT)
  145 + {
HITGNC   146 + 14 case S_IFREG:
  147 + case S_IFBLK:
  148 + case S_IFCHR:
HITGNC   149 + 14 return {};
HITGNC   150 + 5 default:
HITGNC   151 + 5 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
HITGNC   167 + 32 ensure_nonblocking(int fd) noexcept
  168 + {
HITGNC   169 + 32 int flags = ::fcntl(fd, F_GETFL, 0);
HITGNC   170 + 32 if (flags < 0)
HITGNC   171 + 1 return make_err(errno);
HITGNC   172 + 31 if (flags & O_NONBLOCK)
HITGNC   173 + 3 return {};
HITGNC   174 + 28 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
MISUNC   175 + ✗ return make_err(errno);
HITCBC 65   323 return {}; 176   28 return {};
66   } 177   }
67   178  
68   } // namespace boost::corosio::detail 179   } // namespace boost::corosio::detail
69   180  
70   #endif // BOOST_COROSIO_POSIX 181   #endif // BOOST_COROSIO_POSIX
71   182  
72   #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP 183   #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP