conch_runtime_pshaw/env/
fd.rs

1use crate::env::SubEnvironment;
2use crate::io::{dup_stdio, FileDesc, Permissions};
3use crate::{Fd, RefCounted, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
4use std::collections::HashMap;
5use std::fmt;
6use std::io::Result;
7use std::sync::Arc;
8
9/// An interface for setting and getting shell file descriptors.
10pub trait FileDescEnvironment {
11    /// A file handle (or wrapper) to associate with shell file descriptors.
12    type FileHandle;
13    /// Get the permissions and a handle associated with an opened file descriptor.
14    fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)>;
15    /// Associate a file descriptor with a given handle and permissions.
16    fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions);
17    /// Treat the specified file descriptor as closed for the current environment.
18    fn close_file_desc(&mut self, fd: Fd);
19}
20
21impl<'a, T: ?Sized + FileDescEnvironment> FileDescEnvironment for &'a mut T {
22    type FileHandle = T::FileHandle;
23
24    fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)> {
25        (**self).file_desc(fd)
26    }
27
28    fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions) {
29        (**self).set_file_desc(fd, handle, perms)
30    }
31
32    fn close_file_desc(&mut self, fd: Fd) {
33        (**self).close_file_desc(fd)
34    }
35}
36
37/// An environment module for setting and getting shell file descriptors.
38#[derive(PartialEq, Eq)]
39pub struct FileDescEnv<T> {
40    fds: Arc<HashMap<Fd, (T, Permissions)>>,
41}
42
43impl<T> FileDescEnv<T> {
44    /// Constructs a new environment with no open file descriptors.
45    pub fn new() -> Self {
46        Self {
47            fds: HashMap::new().into(),
48        }
49    }
50
51    /// Constructs a new environment with no open file descriptors,
52    /// but with a specified capacity for storing open file descriptors.
53    pub fn with_capacity(capacity: usize) -> Self {
54        Self {
55            fds: HashMap::with_capacity(capacity).into(),
56        }
57    }
58
59    /// Constructs a new environment and initializes it with duplicated
60    /// stdio file descriptors or handles of the current process.
61    pub fn with_process_stdio() -> Result<Self>
62    where
63        T: From<FileDesc>,
64    {
65        let (stdin, stdout, stderr) = dup_stdio()?;
66
67        let mut fds = HashMap::with_capacity(3);
68        fds.insert(STDIN_FILENO, (stdin.into(), Permissions::Read));
69        fds.insert(STDOUT_FILENO, (stdout.into(), Permissions::Write));
70        fds.insert(STDERR_FILENO, (stderr.into(), Permissions::Write));
71
72        Ok(Self { fds: fds.into() })
73    }
74
75    /// Constructs a new environment with a provided collection of provided
76    /// file descriptors in the form `(shell_fd, handle, permissions)`.
77    pub fn with_fds<I: IntoIterator<Item = (Fd, T, Permissions)>>(iter: I) -> Self {
78        Self {
79            fds: iter
80                .into_iter()
81                .map(|(fd, handle, perms)| (fd, (handle, perms)))
82                .collect::<HashMap<_, _>>()
83                .into(),
84        }
85    }
86}
87
88impl<T: fmt::Debug> fmt::Debug for FileDescEnv<T> {
89    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
90        use std::collections::BTreeMap;
91
92        #[derive(Debug)]
93        struct FileDescDebug<T> {
94            permissions: Permissions,
95            os_handle: T,
96        }
97
98        let mut fds = BTreeMap::new();
99        for (fd, &(ref handle, perms)) in &*self.fds {
100            fds.insert(
101                fd,
102                FileDescDebug {
103                    os_handle: handle,
104                    permissions: perms,
105                },
106            );
107        }
108
109        fmt.debug_struct(stringify!(FileDescEnv))
110            .field("fds", &fds)
111            .finish()
112    }
113}
114
115impl<T> Default for FileDescEnv<T> {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl<T> Clone for FileDescEnv<T> {
122    fn clone(&self) -> Self {
123        Self {
124            fds: self.fds.clone(),
125        }
126    }
127}
128
129impl<T> SubEnvironment for FileDescEnv<T> {
130    fn sub_env(&self) -> Self {
131        self.clone()
132    }
133}
134
135impl<T: Clone + Eq> FileDescEnvironment for FileDescEnv<T> {
136    type FileHandle = T;
137
138    fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)> {
139        self.fds
140            .get(&fd)
141            .map(|&(ref handle, perms)| (handle, perms))
142    }
143
144    fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions) {
145        let needs_insert = {
146            let existing = self
147                .fds
148                .get(&fd)
149                .map(|&(ref handle, perms)| (handle, perms));
150            existing != Some((&handle, perms))
151        };
152
153        if needs_insert {
154            self.fds.make_mut().insert(fd, (handle, perms));
155        }
156    }
157
158    fn close_file_desc(&mut self, fd: Fd) {
159        if self.fds.contains_key(&fd) {
160            self.fds.make_mut().remove(&fd);
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::env::SubEnvironment;
169    use crate::io::Permissions;
170    use crate::{RefCounted, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
171
172    #[test]
173    fn test_set_get_and_close_file_desc() {
174        let fd = STDIN_FILENO;
175        let perms = Permissions::ReadWrite;
176        let file_desc = "file_desc";
177
178        let mut env = FileDescEnv::new();
179        assert_eq!(env.file_desc(fd), None);
180
181        env.set_file_desc(fd, file_desc, perms);
182        assert_eq!(env.file_desc(fd), Some((&file_desc, perms)));
183
184        env.close_file_desc(fd);
185        assert_eq!(env.file_desc(fd), None);
186    }
187
188    #[test]
189    fn test_sub_env_no_needless_clone() {
190        let fd = STDIN_FILENO;
191        let fd_not_set = 42;
192        let perms = Permissions::ReadWrite;
193        let file_desc = "file_desc";
194
195        let env = FileDescEnv::with_fds(vec![(fd, file_desc, perms)]);
196        assert_eq!(env.file_desc(fd), Some((&file_desc, perms)));
197
198        let mut env = env.sub_env();
199        env.set_file_desc(fd, file_desc, perms);
200        if env.fds.get_mut().is_some() {
201            panic!("needles clone!");
202        }
203
204        assert_eq!(env.file_desc(fd_not_set), None);
205        env.close_file_desc(fd_not_set);
206        if env.fds.get_mut().is_some() {
207            panic!("needles clone!");
208        }
209    }
210
211    #[test]
212    fn test_set_and_closefile_desc_in_child_env_should_not_affect_parent() {
213        let fd = STDIN_FILENO;
214        let fd_open_in_child = STDOUT_FILENO;
215        let fd_close_in_child = STDERR_FILENO;
216
217        let perms = Permissions::Write;
218        let fdes = "fdes";
219        let fdes_close_in_child = "fdes_close_in_child";
220
221        let parent = FileDescEnv::with_fds(vec![
222            (fd, fdes, perms),
223            (fd_close_in_child, fdes_close_in_child, perms),
224        ]);
225
226        assert_eq!(parent.file_desc(fd_open_in_child), None);
227
228        {
229            let child_perms = Permissions::Read;
230            let fdes_open_in_child = "fdes_open_in_child";
231            let mut child = parent.sub_env();
232            child.set_file_desc(fd, fdes_open_in_child, child_perms);
233            child.set_file_desc(fd_open_in_child, fdes_open_in_child, child_perms);
234            child.close_file_desc(fd_close_in_child);
235
236            assert_eq!(
237                child.file_desc(fd),
238                Some((&fdes_open_in_child, child_perms))
239            );
240            assert_eq!(
241                child.file_desc(fd_open_in_child),
242                Some((&fdes_open_in_child, child_perms))
243            );
244            assert_eq!(child.file_desc(fd_close_in_child), None);
245        }
246
247        assert_eq!(parent.file_desc(fd), Some((&fdes, perms)));
248        assert_eq!(
249            parent.file_desc(fd_close_in_child),
250            Some((&fdes_close_in_child, perms))
251        );
252        assert_eq!(parent.file_desc(fd_open_in_child), None);
253    }
254}