1use std::collections::HashMap;
4use std::io::IsTerminal;
5use std::process::Stdio;
6
7use crate::ShellFd;
8use crate::error;
9use crate::sys;
10
11pub enum OpenFile {
13 Stdin(std::io::Stdin),
15 Stdout(std::io::Stdout),
17 Stderr(std::io::Stderr),
19 File(std::fs::File),
21 PipeReader(std::io::PipeReader),
23 PipeWriter(std::io::PipeWriter),
25}
26
27pub fn null() -> Result<OpenFile, error::Error> {
29 let file = sys::fs::open_null_file()?;
30 Ok(OpenFile::File(file))
31}
32
33impl Clone for OpenFile {
34 fn clone(&self) -> Self {
35 self.try_clone().unwrap()
36 }
37}
38
39impl std::fmt::Display for OpenFile {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Self::Stdin(_) => write!(f, "stdin"),
43 Self::Stdout(_) => write!(f, "stdout"),
44 Self::Stderr(_) => write!(f, "stderr"),
45 Self::File(_) => write!(f, "file"),
46 Self::PipeReader(_) => write!(f, "pipe reader"),
47 Self::PipeWriter(_) => write!(f, "pipe writer"),
48 }
49 }
50}
51
52impl OpenFile {
53 pub fn try_clone(&self) -> Result<Self, std::io::Error> {
55 let result = match self {
56 Self::Stdin(_) => Self::Stdin(std::io::stdin()),
57 Self::Stdout(_) => Self::Stdout(std::io::stdout()),
58 Self::Stderr(_) => Self::Stderr(std::io::stderr()),
59 Self::File(f) => Self::File(f.try_clone()?),
60 Self::PipeReader(f) => Self::PipeReader(f.try_clone()?),
61 Self::PipeWriter(f) => Self::PipeWriter(f.try_clone()?),
62 };
63
64 Ok(result)
65 }
66
67 #[cfg(unix)]
69 pub(crate) fn into_owned_fd(self) -> Result<std::os::fd::OwnedFd, error::Error> {
70 use std::os::fd::AsFd as _;
71
72 match self {
73 Self::Stdin(f) => Ok(f.as_fd().try_clone_to_owned()?),
74 Self::Stdout(f) => Ok(f.as_fd().try_clone_to_owned()?),
75 Self::Stderr(f) => Ok(f.as_fd().try_clone_to_owned()?),
76 Self::File(f) => Ok(f.into()),
77 Self::PipeReader(r) => Ok(std::os::fd::OwnedFd::from(r)),
78 Self::PipeWriter(w) => Ok(std::os::fd::OwnedFd::from(w)),
79 }
80 }
81
82 pub(crate) fn is_dir(&self) -> bool {
83 match self {
84 Self::Stdin(_) | Self::Stdout(_) | Self::Stderr(_) => false,
85 Self::File(file) => file.metadata().map(|m| m.is_dir()).unwrap_or(false),
86 Self::PipeReader(_) | Self::PipeWriter(_) => false,
87 }
88 }
89
90 pub(crate) fn is_term(&self) -> bool {
91 match self {
92 Self::Stdin(f) => f.is_terminal(),
93 Self::Stdout(f) => f.is_terminal(),
94 Self::Stderr(f) => f.is_terminal(),
95 Self::File(f) => f.is_terminal(),
96 Self::PipeReader(_) => false,
97 Self::PipeWriter(_) => false,
98 }
99 }
100}
101
102#[cfg(unix)]
103impl std::os::fd::AsFd for OpenFile {
104 fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
105 match self {
106 Self::Stdin(f) => f.as_fd(),
107 Self::Stdout(f) => f.as_fd(),
108 Self::Stderr(f) => f.as_fd(),
109 Self::File(f) => f.as_fd(),
110 Self::PipeReader(r) => r.as_fd(),
111 Self::PipeWriter(w) => w.as_fd(),
112 }
113 }
114}
115
116impl From<std::fs::File> for OpenFile {
117 fn from(file: std::fs::File) -> Self {
118 Self::File(file)
119 }
120}
121
122impl From<std::io::PipeReader> for OpenFile {
123 fn from(reader: std::io::PipeReader) -> Self {
124 Self::PipeReader(reader)
125 }
126}
127
128impl From<std::io::PipeWriter> for OpenFile {
129 fn from(writer: std::io::PipeWriter) -> Self {
130 Self::PipeWriter(writer)
131 }
132}
133
134impl From<OpenFile> for Stdio {
135 fn from(open_file: OpenFile) -> Self {
136 match open_file {
137 OpenFile::Stdin(_) => Self::inherit(),
138 OpenFile::Stdout(_) => Self::inherit(),
139 OpenFile::Stderr(_) => Self::inherit(),
140 OpenFile::File(f) => f.into(),
141 OpenFile::PipeReader(f) => f.into(),
142 OpenFile::PipeWriter(f) => f.into(),
143 }
144 }
145}
146
147impl std::io::Read for OpenFile {
148 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
149 match self {
150 Self::Stdin(f) => f.read(buf),
151 Self::Stdout(_) => Err(std::io::Error::other(
152 error::ErrorKind::OpenFileNotReadable("stdout"),
153 )),
154 Self::Stderr(_) => Err(std::io::Error::other(
155 error::ErrorKind::OpenFileNotReadable("stderr"),
156 )),
157 Self::File(f) => f.read(buf),
158 Self::PipeReader(reader) => reader.read(buf),
159 Self::PipeWriter(_) => Err(std::io::Error::other(
160 error::ErrorKind::OpenFileNotReadable("pipe writer"),
161 )),
162 }
163 }
164}
165
166impl std::io::Write for OpenFile {
167 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
168 match self {
169 Self::Stdin(_) => Err(std::io::Error::other(
170 error::ErrorKind::OpenFileNotWritable("stdin"),
171 )),
172 Self::Stdout(f) => f.write(buf),
173 Self::Stderr(f) => f.write(buf),
174 Self::File(f) => f.write(buf),
175 Self::PipeReader(_) => Err(std::io::Error::other(
176 error::ErrorKind::OpenFileNotWritable("pipe reader"),
177 )),
178 Self::PipeWriter(writer) => writer.write(buf),
179 }
180 }
181
182 fn flush(&mut self) -> std::io::Result<()> {
183 match self {
184 Self::Stdin(_) => Ok(()),
185 Self::Stdout(f) => f.flush(),
186 Self::Stderr(f) => f.flush(),
187 Self::File(f) => f.flush(),
188 Self::PipeReader(_) => Ok(()),
189 Self::PipeWriter(writer) => writer.flush(),
190 }
191 }
192}
193
194pub enum OpenFileEntry<'a> {
196 Open(&'a OpenFile),
198 NotPresent,
200 NotSpecified,
203}
204
205#[derive(Clone, Default)]
207pub struct OpenFiles {
208 files: HashMap<ShellFd, Option<OpenFile>>,
210}
211
212impl OpenFiles {
213 pub const STDIN_FD: ShellFd = 0;
215 pub const STDOUT_FD: ShellFd = 1;
217 pub const STDERR_FD: ShellFd = 2;
219
220 #[allow(unused)]
223 pub(crate) fn new() -> Self {
224 Self {
225 files: HashMap::from([
226 (Self::STDIN_FD, Some(OpenFile::Stdin(std::io::stdin()))),
227 (Self::STDOUT_FD, Some(OpenFile::Stdout(std::io::stdout()))),
228 (Self::STDERR_FD, Some(OpenFile::Stderr(std::io::stderr()))),
229 ]),
230 }
231 }
232
233 pub fn update_from(&mut self, files: impl Iterator<Item = (ShellFd, OpenFile)>) {
240 for (fd, file) in files {
241 let _ = self.files.insert(fd, Some(file));
242 }
243 }
244
245 pub fn try_stdin(&self) -> Option<&OpenFile> {
247 self.files.get(&Self::STDIN_FD).and_then(|f| f.as_ref())
248 }
249
250 pub fn try_stdout(&self) -> Option<&OpenFile> {
252 self.files.get(&Self::STDOUT_FD).and_then(|f| f.as_ref())
253 }
254
255 pub fn try_stderr(&self) -> Option<&OpenFile> {
257 self.files.get(&Self::STDERR_FD).and_then(|f| f.as_ref())
258 }
259
260 pub fn remove_fd(&mut self, fd: ShellFd) -> Option<OpenFile> {
268 self.files.insert(fd, None).and_then(|f| f)
269 }
270
271 pub fn try_fd(&self, fd: ShellFd) -> Option<&OpenFile> {
278 self.files.get(&fd).and_then(|f| f.as_ref())
279 }
280
281 pub fn fd_entry(&self, fd: ShellFd) -> OpenFileEntry<'_> {
288 self.files
289 .get(&fd)
290 .map_or(OpenFileEntry::NotSpecified, |opt_file| match opt_file {
291 Some(f) => OpenFileEntry::Open(f),
292 None => OpenFileEntry::NotPresent,
293 })
294 }
295
296 pub fn contains_fd(&self, fd: ShellFd) -> bool {
298 self.files.contains_key(&fd)
299 }
300
301 pub fn set_fd(&mut self, fd: ShellFd, file: OpenFile) -> Option<OpenFile> {
310 self.files.insert(fd, Some(file)).and_then(|f| f)
311 }
312
313 pub fn iter_fds(&self) -> impl Iterator<Item = (ShellFd, &OpenFile)> {
315 self.files
316 .iter()
317 .filter_map(|(fd, file)| file.as_ref().map(|f| (*fd, f)))
318 }
319}
320
321impl<I> From<I> for OpenFiles
322where
323 I: Iterator<Item = (ShellFd, OpenFile)>,
324{
325 fn from(iter: I) -> Self {
326 let files = iter.map(|(fd, file)| (fd, Some(file))).collect();
327 Self { files }
328 }
329}