Skip to main content

pnut_child/
fd.rs

1//! File-descriptor helpers for child-runtime setup.
2
3use crate::error::{Errno, Result};
4
5/// Minimal internal fd guard for child-runtime error paths.
6#[derive(Debug)]
7pub(crate) struct OwnedFd(libc::c_int);
8
9impl OwnedFd {
10    pub(crate) const fn new(fd: libc::c_int) -> Self {
11        Self(fd)
12    }
13
14    pub(crate) const fn as_raw(&self) -> libc::c_int {
15        self.0
16    }
17}
18
19impl Drop for OwnedFd {
20    fn drop(&mut self) {
21        if self.0 >= 0 {
22            unsafe {
23                libc::close(self.0);
24            }
25        }
26    }
27}
28
29/// One precomputed fd action for the child runtime.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum FdAction {
32    Dup2 { src: libc::c_int, dst: libc::c_int },
33    Close(libc::c_int),
34}
35
36/// Close one file descriptor.
37///
38/// On Linux the fd is always released regardless of the return value, so
39/// EINTR is not retried.
40pub fn close(fd: libc::c_int) -> Result<()> {
41    let ret = unsafe { libc::close(fd) };
42    if ret == 0 { Ok(()) } else { Err(Errno::last()) }
43}
44
45/// Duplicate `src` onto `dst`.
46pub fn dup2(src: libc::c_int, dst: libc::c_int) -> Result<()> {
47    loop {
48        let ret = unsafe { libc::dup2(src, dst) };
49        if ret >= 0 {
50            return Ok(());
51        }
52        let err = Errno::last();
53        if err.0 == libc::EINTR {
54            continue;
55        }
56        return Err(err);
57    }
58}
59
60/// Apply all fd actions in order.
61pub fn apply_actions(actions: &[FdAction]) -> Result<()> {
62    for action in actions {
63        match *action {
64            FdAction::Dup2 { src, dst } if src != dst => dup2(src, dst)?,
65            FdAction::Dup2 { .. } => {}
66            FdAction::Close(fd) => close(fd)?,
67        }
68    }
69    Ok(())
70}
71
72/// Close all fds >= 3 that are not in `keep_sorted` or `extra_keep`.
73///
74/// `keep_sorted` should be sorted ascending and contain no duplicates. This
75/// function does not require that property for correctness, but it keeps the
76/// close-range scan deterministic and cheap.
77pub fn close_other_fds(keep_sorted: &[libc::c_int], extra_keep: &[libc::c_int]) -> Result<()> {
78    let mut cursor = 3u32;
79
80    loop {
81        let next_keep = next_keep_fd(cursor, keep_sorted, extra_keep);
82        if next_keep == u32::MAX {
83            close_range(cursor, u32::MAX)?;
84            return Ok(());
85        }
86        if next_keep > cursor {
87            close_range(cursor, next_keep - 1)?;
88        }
89        cursor = next_keep.saturating_add(1);
90    }
91}
92
93fn next_keep_fd(cursor: u32, keep_sorted: &[libc::c_int], extra_keep: &[libc::c_int]) -> u32 {
94    let mut next = u32::MAX;
95
96    // keep_sorted is ascending, so the first fd >= cursor is the answer.
97    for &fd in keep_sorted {
98        let fd = fd as u32;
99        if fd >= cursor {
100            next = fd;
101            break;
102        }
103    }
104
105    for &fd in extra_keep {
106        if fd < 3 {
107            continue;
108        }
109        let fd = fd as u32;
110        if fd >= cursor && fd < next {
111            next = fd;
112        }
113    }
114
115    next
116}
117
118fn close_range(first: u32, last: u32) -> Result<()> {
119    if first > last {
120        return Ok(());
121    }
122
123    let ret = unsafe { libc::syscall(libc::SYS_close_range, first, last, 0u32) };
124    if ret == 0 { Ok(()) } else { Err(Errno::last()) }
125}