Skip to main content

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}