1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
use {Fd, RefCounted, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use io::{dup_stdio, FileDesc, Permissions};
use env::SubEnvironment;
use std::collections::HashMap;
use std::fmt;
use std::io::Result;
use std::rc::Rc;
use std::sync::Arc;

/// An interface for setting and getting shell file descriptors.
pub trait FileDescEnvironment {
    /// A file handle (or wrapper) to associate with shell file descriptors.
    type FileHandle;
    /// Get the permissions and a handle associated with an opened file descriptor.
    fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)>;
    /// Associate a file descriptor with a given handle and permissions.
    fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions);
    /// Treat the specified file descriptor as closed for the current environment.
    fn close_file_desc(&mut self, fd: Fd);
}

impl<'a, T: ?Sized + FileDescEnvironment> FileDescEnvironment for &'a mut T {
    type FileHandle = T::FileHandle;

    fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)> {
        (**self).file_desc(fd)
    }

    fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions) {
        (**self).set_file_desc(fd, handle, perms)
    }

    fn close_file_desc(&mut self, fd: Fd) {
        (**self).close_file_desc(fd)
    }
}

macro_rules! impl_env {
    ($(#[$attr:meta])* pub struct $Env:ident, $Rc:ident) => {
        $(#[$attr])*
        #[derive(PartialEq, Eq)]
        pub struct $Env<T> {
            fds: $Rc<HashMap<Fd, (T, Permissions)>>,
        }

        impl<T> $Env<T> {
            /// Constructs a new environment with no open file descriptors.
            pub fn new() -> Self {
                $Env {
                    fds: HashMap::new().into(),
                }
            }

            /// Constructs a new environment and initializes it with duplicated
            /// stdio file descriptors or handles of the current process.
            pub fn with_process_stdio() -> Result<Self> where T: From<FileDesc> {
                let (stdin, stdout, stderr) = try!(dup_stdio());
                Ok(Self::with_fds(vec!(
                    (STDIN_FILENO,  stdin.into(),  Permissions::Read),
                    (STDOUT_FILENO, stdout.into(), Permissions::Write),
                    (STDERR_FILENO, stderr.into(), Permissions::Write),
                )))
            }

            /// Constructs a new environment with a provided collection of provided
            /// file descriptors in the form `(shell_fd, handle, permissions)`.
            pub fn with_fds<I: IntoIterator<Item = (Fd, T, Permissions)>>(iter: I) -> Self {
                $Env {
                    fds: iter.into_iter()
                        .map(|(fd, handle, perms)| (fd, (handle, perms)))
                        .collect::<HashMap<_, _>>()
                        .into(),
                }
            }
        }

        impl<T: fmt::Debug> fmt::Debug for $Env<T> {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                use std::collections::BTreeMap;

                #[derive(Debug)]
                struct FileDescDebug<T> {
                    permissions: Permissions,
                    os_handle: T,
                }

                let mut fds = BTreeMap::new();
                for (fd, &(ref handle, perms)) in &*self.fds {
                    fds.insert(fd, FileDescDebug {
                        os_handle: handle,
                        permissions: perms,
                    });
                }

                fmt.debug_struct(stringify!($Env))
                    .field("fds", &fds)
                    .finish()
            }
        }

        impl<T> Default for $Env<T> {
            fn default() -> Self {
                Self::new()
            }
        }

        impl<T> Clone for $Env<T> {
            fn clone(&self) -> Self {
                $Env {
                    fds: self.fds.clone(),
                }
            }
        }

        impl<T> SubEnvironment for $Env<T> {
            fn sub_env(&self) -> Self {
                self.clone()
            }
        }

        impl<T: Clone + Eq> FileDescEnvironment for $Env<T> {
            type FileHandle = T;

            fn file_desc(&self, fd: Fd) -> Option<(&Self::FileHandle, Permissions)> {
                self.fds.get(&fd).map(|&(ref handle, perms)| (handle, perms))
            }

            fn set_file_desc(&mut self, fd: Fd, handle: Self::FileHandle, perms: Permissions) {
                let needs_insert = {
                    let existing = self.fds.get(&fd).map(|&(ref handle, perms)| (handle, perms));
                    existing != Some((&handle, perms))
                };

                if needs_insert {
                    self.fds.make_mut().insert(fd, (handle, perms));
                }
            }

            fn close_file_desc(&mut self, fd: Fd) {
                if self.fds.contains_key(&fd) {
                    self.fds.make_mut().remove(&fd);
                }
            }
        }
    };
}

impl_env!(
    /// An environment module for setting and getting shell file descriptors.
    ///
    /// Uses `Rc` internally. For a possible `Send` and `Sync` implementation,
    /// see `env::atomic::FileDescEnv`.
    pub struct FileDescEnv,
    Rc
);

impl_env!(
    /// An environment module for setting and getting shell file descriptors.
    ///
    /// Uses `Arc` internally. If `Send` and `Sync` is not required of the implementation,
    /// see `env::FileDescEnv` as a cheaper alternative.
    pub struct AtomicFileDescEnv,
    Arc
);

#[cfg(test)]
mod tests {
    use {RefCounted, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
    use io::Permissions;
    use env::SubEnvironment;
    use super::*;

    #[test]
    fn test_set_get_and_close_file_desc() {
        let fd = STDIN_FILENO;
        let perms = Permissions::ReadWrite;
        let file_desc = "file_desc";

        let mut env = FileDescEnv::new();
        assert_eq!(env.file_desc(fd), None);

        env.set_file_desc(fd, file_desc, perms);
        assert_eq!(env.file_desc(fd), Some((&file_desc, perms)));

        env.close_file_desc(fd);
        assert_eq!(env.file_desc(fd), None);
    }

    #[test]
    fn test_sub_env_no_needless_clone() {
        let fd = STDIN_FILENO;
        let fd_not_set = 42;
        let perms = Permissions::ReadWrite;
        let file_desc = "file_desc";

        let env = FileDescEnv::with_fds(vec!((fd, file_desc, perms)));
        assert_eq!(env.file_desc(fd), Some((&file_desc, perms)));

        let mut env = env.sub_env();
        env.set_file_desc(fd, file_desc, perms);
        if env.fds.get_mut().is_some() {
            panic!("needles clone!");
        }

        assert_eq!(env.file_desc(fd_not_set), None);
        env.close_file_desc(fd_not_set);
        if env.fds.get_mut().is_some() {
            panic!("needles clone!");
        }
    }

    #[test]
    fn test_set_and_closefile_desc_in_child_env_should_not_affect_parent() {
        let fd = STDIN_FILENO;
        let fd_open_in_child = STDOUT_FILENO;
        let fd_close_in_child = STDERR_FILENO;

        let perms = Permissions::Write;
        let fdes = "fdes";
        let fdes_close_in_child = "fdes_close_in_child";

        let parent = FileDescEnv::with_fds(vec!(
            (fd, fdes, perms),
            (fd_close_in_child, fdes_close_in_child, perms),
        ));

        assert_eq!(parent.file_desc(fd_open_in_child), None);

        {
            let child_perms = Permissions::Read;
            let fdes_open_in_child = "fdes_open_in_child";
            let mut child = parent.sub_env();
            child.set_file_desc(fd, fdes_open_in_child, child_perms);
            child.set_file_desc(fd_open_in_child, fdes_open_in_child, child_perms);
            child.close_file_desc(fd_close_in_child);

            assert_eq!(child.file_desc(fd), Some((&fdes_open_in_child, child_perms)));
            assert_eq!(child.file_desc(fd_open_in_child), Some((&fdes_open_in_child, child_perms)));
            assert_eq!(child.file_desc(fd_close_in_child), None);
        }

        assert_eq!(parent.file_desc(fd), Some((&fdes, perms)));
        assert_eq!(parent.file_desc(fd_close_in_child), Some((&fdes_close_in_child, perms)));
        assert_eq!(parent.file_desc(fd_open_in_child), None);
    }
}