brush_core/sys/unix/
fd.rs

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