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>
  19 + #include <boost/corosio/native/detail/posix/large_file.hpp>
18   20  
19   #include <cerrno> 21   #include <cerrno>
20   #include <system_error> 22   #include <system_error>
21   23  
  24 + #include <fcntl.h>
22   #include <sys/socket.h> 25   #include <sys/socket.h>
  26 + #include <sys/stat.h>
23   27  
24   namespace boost::corosio::detail { 28   namespace boost::corosio::detail {
25   29  
26   /** Validate a caller-supplied socket fd for adoption. 30   /** Validate a caller-supplied socket fd for adoption.
27   31  
28   Non-mutating: interrogates the fd without changing any of its 32   Non-mutating: interrogates the fd without changing any of its
29   flags, so a rejected fd goes back to the caller untouched. 33   flags, so a rejected fd goes back to the caller untouched.
30   34  
31   @param fd The descriptor to validate. 35   @param fd The descriptor to validate.
32   @param expected_type `SOCK_STREAM` or `SOCK_DGRAM`. 36   @param expected_type `SOCK_STREAM` or `SOCK_DGRAM`.
33   @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`
34   when false. 38   when false.
35   @return Empty on success; `EBADF`, `EAFNOSUPPORT`, `EPROTOTYPE`, 39   @return Empty on success; `EBADF`, `EAFNOSUPPORT`, `EPROTOTYPE`,
36   or the `errno` reported by the interrogating call. 40   or the `errno` reported by the interrogating call.
37   */ 41   */
38   inline std::error_code 42   inline std::error_code
HITCBC 39   385 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
40   { 44   {
HITCBC 41   385 if (fd < 0) 45   371 if (fd < 0)
HITCBC 42   14 return make_err(EBADF); 46   14 return make_err(EBADF);
43   47  
HITCBC 44   371 sockaddr_storage st{}; 48   357 sockaddr_storage st{};
HITCBC 45   371 socklen_t st_len = sizeof(st); 49   357 socklen_t st_len = sizeof(st);
HITCBC 46   371 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0) 50   357 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&st), &st_len) != 0)
HITCBC 47   5 return make_err(errno); 51   5 return make_err(errno);
HITCBC 48   366 if (is_ip) 52   352 if (is_ip)
49   { 53   {
HITCBC 50   49 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 51   6 return make_err(EAFNOSUPPORT); 55   6 return make_err(EAFNOSUPPORT);
52   } 56   }
HITCBC 53   317 else if (st.ss_family != AF_UNIX) 57   313 else if (st.ss_family != AF_UNIX)
54   { 58   {
HITCBC 55   2 return make_err(EAFNOSUPPORT); 59   2 return make_err(EAFNOSUPPORT);
56   } 60   }
57   61  
HITCBC 58   358 int sock_type = 0; 62   344 int sock_type = 0;
HITCBC 59   358 socklen_t opt_len = sizeof(sock_type); 63   344 socklen_t opt_len = sizeof(sock_type);
HITCBC 60   358 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 61   15 return make_err(errno); 65   15 return make_err(errno);
HITCBC 62   343 if (sock_type != expected_type) 66   329 if (sock_type != expected_type)
HITCBC 63   20 return make_err(EPROTOTYPE); 67   14 return make_err(EPROTOTYPE);
64   68  
HITGNC   69 + 315 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
HITGNC   91 + 120 validate_descriptor_fd(int fd) noexcept
  92 + {
HITGNC   93 + 120 if (fd < 0)
HITGNC   94 + 3 return make_err(EBADF);
  95 +
HITGNC   96 + 117 file_stat_t st{};
HITGNC   97 + 117 if (file_fstat(fd, &st) != 0)
HITGNC   98 + 1 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.
HITGNC   103 + 116 switch (st.st_mode & S_IFMT)
  104 + {
HITGNC   105 + 4 case S_IFREG:
  106 + case S_IFBLK:
  107 + case S_IFDIR:
HITGNC   108 + 4 return std::make_error_code(std::errc::operation_not_supported);
HITGNC   109 + 112 default:
HITGNC   110 + 112 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
HITGNC   136 + 420 validate_file_fd(int fd) noexcept
  137 + {
HITGNC   138 + 420 if (fd < 0)
HITGNC   139 + 4 return make_err(EBADF);
  140 +
HITGNC   141 + 416 file_stat_t st{};
HITGNC   142 + 416 if (file_fstat(fd, &st) != 0)
MISUNC   143 + ✗ return make_err(errno);
  144 +
HITGNC   145 + 416 switch (st.st_mode & S_IFMT)
  146 + {
HITGNC   147 + 411 case S_IFREG:
  148 + case S_IFBLK:
  149 + case S_IFCHR:
HITGNC   150 + 411 return {};
HITGNC   151 + 5 default:
HITGNC   152 + 5 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
HITGNC   169 + 64 ensure_nonblocking(int fd) noexcept
  170 + {
HITGNC   171 + 64 int flags = ::fcntl(fd, F_GETFL, 0);
HITGNC   172 + 64 if (flags < 0)
HITGNC   173 + 1 return make_err(errno);
HITGNC   174 + 63 if (flags & O_NONBLOCK)
HITGNC   175 + 9 return {};
HITGNC   176 + 54 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
MISUNC   177 + ✗ return make_err(errno);
HITCBC 65   323 return {}; 178   54 return {};
66   } 179   }
67   180  
68   } // namespace boost::corosio::detail 181   } // namespace boost::corosio::detail
69   182  
70   #endif // BOOST_COROSIO_POSIX 183   #endif // BOOST_COROSIO_POSIX
71   184  
72   #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP 185   #endif // BOOST_COROSIO_NATIVE_DETAIL_VALIDATE_FD_HPP