yash_env/system/real/fd_set.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2026 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementation of file descriptor sets for use with `select` system call
18
19use crate::io::Fd;
20use crate::system::FdSet as FdSetTrait;
21use std::mem::MaybeUninit;
22use std::os::fd::RawFd;
23
24/// File descriptor set for the real system, wrapping the `fd_set` type from libc
25///
26/// This is an implementation of the [`FdSet` trait](crate::system::FdSet) for the
27/// [`RealSystem`](super::RealSystem).
28#[derive(Clone)]
29pub struct FdSet {
30 /// The underlying `fd_set` structure from libc
31 ///
32 /// We use `MaybeUninit` because `fd_set` may contain uninitialized fields
33 /// or padding bytes that are not initialized by `FD_ZERO`.
34 fds: MaybeUninit<libc::fd_set>,
35
36 /// An FD that is greater than any FD currently in the set
37 ///
38 /// This is used to optimize the `select` system call by providing an upper bound
39 /// on the FDs in the set. The `select` system call only needs to check FDs
40 /// up to this upper bound, which can improve performance when the set
41 /// contains a small number of FDs that are much smaller than `FD_SETSIZE`.
42 upper_bound: Fd,
43}
44
45impl Default for FdSet {
46 fn default() -> Self {
47 let mut fds = MaybeUninit::<libc::fd_set>::uninit();
48 unsafe { libc::FD_ZERO(fds.as_mut_ptr()) };
49 Self {
50 fds,
51 upper_bound: Fd(0),
52 }
53 }
54}
55
56impl FdSetTrait for FdSet {
57 const MAX_FD: Fd = Fd(libc::FD_SETSIZE as RawFd - 1);
58
59 fn insert(&mut self, fd: Fd) {
60 if 0 <= fd.0 && fd <= Self::MAX_FD {
61 // SAFETY: We just verified that `fd` is within the valid range for
62 // an `fd_set`.
63 // SAFETY: POSIX also requires that `fd` be valid (i.e. open), but
64 // we cannot verify that here. We assume that the underlying
65 // `fd_set` implementation accepts invalid FDs and the `select`
66 // system call will handle them appropriately.
67 unsafe { libc::FD_SET(fd.0, self.fds.as_mut_ptr()) };
68
69 self.upper_bound = self.upper_bound.max(Fd(fd.0 + 1));
70 }
71 }
72
73 fn remove(&mut self, fd: Fd) {
74 if 0 <= fd.0 && fd <= Self::MAX_FD {
75 // SAFETY: The same as in `insert`.
76 unsafe { libc::FD_CLR(fd.0, self.fds.as_mut_ptr()) };
77 }
78 }
79
80 fn contains(&self, fd: Fd) -> bool {
81 0 <= fd.0 && fd <= Self::MAX_FD && unsafe { libc::FD_ISSET(fd.0, self.fds.as_ptr()) }
82 }
83}
84
85impl std::fmt::Debug for FdSet {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 let fds = (0..=Self::MAX_FD.0).filter(|&fd| self.contains(Fd(fd)));
88 f.debug_set().entries(fds).finish()
89 }
90}
91
92impl FdSet {
93 #[inline(always)]
94 #[must_use]
95 pub(super) fn as_mut_ptr(&mut self) -> *mut libc::fd_set {
96 self.fds.as_mut_ptr()
97 }
98
99 #[inline(always)]
100 #[must_use]
101 pub(super) fn upper_bound(&self) -> Fd {
102 self.upper_bound
103 }
104}