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
9pub trait FileDescEnvironment {
11 type FileHandle;
13 fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)>;
15 fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions);
17 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#[derive(PartialEq, Eq)]
39pub struct FileDescEnv<T> {
40 fds: Arc<HashMap<Fd, (T, Permissions)>>,
41}
42
43impl<T> FileDescEnv<T> {
44 pub fn new() -> Self {
46 Self {
47 fds: HashMap::new().into(),
48 }
49 }
50
51 pub fn with_capacity(capacity: usize) -> Self {
54 Self {
55 fds: HashMap::with_capacity(capacity).into(),
56 }
57 }
58
59 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 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}