Skip to main content

shrub_core/sys/unix/
fd.rs

1//! File descriptor utilities.
2
3use std::os::fd::RawFd;
4
5use crate::{ShellFd, error, openfiles};
6
7cfg_if::cfg_if! {
8    if #[cfg(any(target_os = "linux", target_os = "android"))] {
9        const FD_DIR_PATH: &str = "/proc/self/fd";
10    } else if #[cfg(any(
11            target_os = "freebsd",
12            target_os = "macos",
13            target_os = "netbsd",
14            target_os = "openbsd"
15        ))] {
16        const FD_DIR_PATH: &str = "/dev/fd";
17    } else {
18        /// Returns an iterator over all open file descriptors for the shell.
19        ///
20        /// # Errors
21        ///
22        /// This fallback implementation always succeeds with an empty iterator.
23        pub fn iter_fds()
24        -> Result<impl Iterator<Item = (ShellFd, openfiles::OpenFile)>, error::Error> {
25            Ok(std::iter::empty())
26        }
27    }
28}
29
30/// Makes a best-effort attempt to iterate over all open file descriptors
31/// for the current process.
32///
33/// If the platform does not support enumerating file descriptors, an empty
34/// iterator is returned. This function will skip any file descriptors that
35/// cannot be opened.
36#[cfg(any(
37    target_os = "linux",
38    target_os = "android",
39    target_os = "freebsd",
40    target_os = "macos",
41    target_os = "netbsd",
42    target_os = "openbsd"
43))]
44pub fn try_iter_open_fds() -> impl Iterator<Item = (ShellFd, openfiles::OpenFile)> {
45    std::fs::read_dir(FD_DIR_PATH)
46        .into_iter()
47        .flatten()
48        .filter_map(Result::ok)
49        .filter_map(|entry| {
50            let fd: RawFd = entry.file_name().to_str()?.parse().ok()?;
51
52            // SAFETY:
53            // We are trying to open the file descriptor we found listed
54            // in the filesystem, but there's a risk that it's not the same one
55            // that we enumerated or that it's since been closed. For the
56            // purposes of this function, either of those outcomes
57            // are acceptable. We simply skip any fds that we can't
58            // open, and the function's purpose is to make a
59            // best-effort attempt to open all available fds.
60            let file = unsafe { open_file_by_fd(fd) }.ok()?;
61
62            Some((fd, file))
63        })
64}
65
66/// Attempts to retrieve an `OpenFile` representation for the given already-open
67/// file descriptor.
68///
69/// If the file descriptor cannot be opened, `None` is returned. Note that there
70/// is no guarantee that the returned file matches the original file descriptor,
71/// as the fd may have been closed and potentially re-used in the meantime.
72///
73/// # Arguments
74///
75/// * `fd` - The file descriptor to open.
76pub fn try_get_file_for_open_fd(fd: RawFd) -> Option<openfiles::OpenFile> {
77    // SAFETY:
78    // We are trying to open the file descriptor provided by the caller. There's
79    // a risk that the fd is invalid or has been closed since it was
80    // enumerated. For the purposes of this function, we simply return None
81    // if we can't open it. There's also a risk that the fd has been closed
82    // and re-used for a different file; again, for the purposes of this
83    // function, we accept that risk and document it as part of the
84    // function's contract.
85    unsafe { open_file_by_fd(fd).ok() }
86}
87
88unsafe fn open_file_by_fd(fd: RawFd) -> Result<openfiles::OpenFile, error::Error> {
89    // SAFETY: We are creating a BorrowedFd from a file descriptor. Callers
90    // typically enumerate available file descriptors from procfs, devfs, or
91    // similar, but there's still a risk that the fd has become invalid or
92    // closed since then -- or that this function gets used incorrectly.
93    let borrowed_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) };
94    let owned_fd = borrowed_fd.try_clone_to_owned()?;
95    Ok(std::fs::File::from(owned_fd).into())
96}