1use std::collections::HashMap;
4use std::io::IsTerminal;
5use std::process::Stdio;
6
7use crate::ShellFd;
8use crate::error;
9use crate::sys;
10
11pub trait Stream: std::io::Read + std::io::Write + Send + Sync {
17 fn clone_box(&self) -> Box<dyn Stream>;
19
20 #[cfg(unix)]
23 fn try_clone_to_owned(&self) -> Result<std::os::fd::OwnedFd, error::Error>;
24
25 #[cfg(unix)]
28 fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error>;
29}
30
31pub enum OpenFile {
33 Stdin(std::io::Stdin),
35 Stdout(std::io::Stdout),
37 Stderr(std::io::Stderr),
39 File(std::fs::File),
41 PipeReader(std::io::PipeReader),
43 PipeWriter(std::io::PipeWriter),
45 Stream(Box<dyn Stream>),
47}
48
49#[cfg(feature = "serde")]
50impl serde::Serialize for OpenFile {
51 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52 where
53 S: serde::Serializer,
54 {
55 match self {
56 Self::Stdin(_) => serializer.serialize_str("stdin"),
57 Self::Stdout(_) => serializer.serialize_str("stdout"),
58 Self::Stderr(_) => serializer.serialize_str("stderr"),
59 Self::File(_) => serializer.serialize_str("file"),
60 Self::PipeReader(_) => serializer.serialize_str("pipe_reader"),
61 Self::PipeWriter(_) => serializer.serialize_str("pipe_writer"),
62 Self::Stream(_) => serializer.serialize_str("stream"),
63 }
64 }
65}
66
67#[cfg(feature = "serde")]
68impl<'de> serde::Deserialize<'de> for OpenFile {
69 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70 where
71 D: serde::Deserializer<'de>,
72 {
73 match String::deserialize(deserializer)?.as_str() {
74 "stdin" => return Ok(std::io::stdin().into()),
75 "stdout" => return Ok(std::io::stdout().into()),
76 "stderr" => return Ok(std::io::stderr().into()),
77 "file" => (),
78 "pipe_reader" => (),
79 "pipe_writer" => (),
80 "stream" => (),
81 _ => return Err(serde::de::Error::custom("invalid open file")),
82 }
83
84 null().map_err(serde::de::Error::custom)
86 }
87}
88
89pub fn null() -> Result<OpenFile, error::Error> {
91 let file = sys::fs::open_null_file()?;
92 Ok(OpenFile::File(file))
93}
94
95impl Clone for OpenFile {
96 fn clone(&self) -> Self {
97 #[allow(clippy::unwrap_used)]
99 self.try_clone().unwrap()
100 }
101}
102
103impl std::fmt::Display for OpenFile {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self {
106 Self::Stdin(_) => write!(f, "stdin"),
107 Self::Stdout(_) => write!(f, "stdout"),
108 Self::Stderr(_) => write!(f, "stderr"),
109 Self::File(_) => write!(f, "file"),
110 Self::PipeReader(_) => write!(f, "pipe reader"),
111 Self::PipeWriter(_) => write!(f, "pipe writer"),
112 Self::Stream(_) => write!(f, "stream"),
113 }
114 }
115}
116
117impl OpenFile {
118 pub fn try_clone(&self) -> Result<Self, std::io::Error> {
120 let result = match self {
121 Self::Stdin(_) => std::io::stdin().into(),
122 Self::Stdout(_) => std::io::stdout().into(),
123 Self::Stderr(_) => std::io::stderr().into(),
124 Self::File(f) => f.try_clone()?.into(),
125 Self::PipeReader(f) => f.try_clone()?.into(),
126 Self::PipeWriter(f) => f.try_clone()?.into(),
127 Self::Stream(s) => Self::Stream(s.clone_box()),
128 };
129
130 Ok(result)
131 }
132
133 #[cfg(unix)]
135 pub(crate) fn try_clone_to_owned(self) -> Result<std::os::fd::OwnedFd, error::Error> {
136 use std::os::fd::AsFd as _;
137
138 match self {
139 Self::Stdin(f) => Ok(f.as_fd().try_clone_to_owned()?),
140 Self::Stdout(f) => Ok(f.as_fd().try_clone_to_owned()?),
141 Self::Stderr(f) => Ok(f.as_fd().try_clone_to_owned()?),
142 Self::File(f) => Ok(f.into()),
143 Self::PipeReader(r) => Ok(std::os::fd::OwnedFd::from(r)),
144 Self::PipeWriter(w) => Ok(std::os::fd::OwnedFd::from(w)),
145 Self::Stream(s) => s.try_clone_to_owned(),
146 }
147 }
148
149 #[cfg(unix)]
155 pub fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error> {
156 use std::os::fd::AsFd as _;
157
158 match self {
159 Self::Stdin(f) => Ok(f.as_fd()),
160 Self::Stdout(f) => Ok(f.as_fd()),
161 Self::Stderr(f) => Ok(f.as_fd()),
162 Self::File(f) => Ok(f.as_fd()),
163 Self::PipeReader(r) => Ok(r.as_fd()),
164 Self::PipeWriter(w) => Ok(w.as_fd()),
165 Self::Stream(s) => s.try_borrow_as_fd(),
166 }
167 }
168
169 pub(crate) fn is_dir(&self) -> bool {
170 match self {
171 Self::Stdin(_) | Self::Stdout(_) | Self::Stderr(_) => false,
172 Self::File(file) => file.metadata().is_ok_and(|m| m.is_dir()),
173 Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
174 }
175 }
176
177 pub fn is_terminal(&self) -> bool {
179 match self {
180 Self::Stdin(f) => f.is_terminal(),
181 Self::Stdout(f) => f.is_terminal(),
182 Self::Stderr(f) => f.is_terminal(),
183 Self::File(f) => f.is_terminal(),
184 Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
185 }
186 }
187}
188
189impl From<std::io::Stdin> for OpenFile {
190 fn from(stdin: std::io::Stdin) -> Self {
192 Self::Stdin(stdin)
193 }
194}
195
196impl From<std::io::Stdout> for OpenFile {
197 fn from(stdout: std::io::Stdout) -> Self {
199 Self::Stdout(stdout)
200 }
201}
202
203impl From<std::io::Stderr> for OpenFile {
204 fn from(stderr: std::io::Stderr) -> Self {
206 Self::Stderr(stderr)
207 }
208}
209
210impl From<std::fs::File> for OpenFile {
211 fn from(file: std::fs::File) -> Self {
212 Self::File(file)
213 }
214}
215
216impl From<std::io::PipeReader> for OpenFile {
217 fn from(reader: std::io::PipeReader) -> Self {
218 Self::PipeReader(reader)
219 }
220}
221
222impl From<std::io::PipeWriter> for OpenFile {
223 fn from(writer: std::io::PipeWriter) -> Self {
224 Self::PipeWriter(writer)
225 }
226}
227
228impl From<OpenFile> for Stdio {
229 fn from(open_file: OpenFile) -> Self {
230 match open_file {
231 OpenFile::Stdin(_) => Self::inherit(),
232 OpenFile::Stdout(_) => Self::inherit(),
233 OpenFile::Stderr(_) => Self::inherit(),
234 OpenFile::File(f) => f.into(),
235 OpenFile::PipeReader(f) => f.into(),
236 OpenFile::PipeWriter(f) => f.into(),
237 OpenFile::Stream(_) => Self::null(),
240 }
241 }
242}
243
244impl std::io::Read for OpenFile {
245 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
246 match self {
247 Self::Stdin(f) => f.read(buf),
248 Self::Stdout(_) => Err(std::io::Error::other(
249 error::ErrorKind::OpenFileNotReadable("stdout"),
250 )),
251 Self::Stderr(_) => Err(std::io::Error::other(
252 error::ErrorKind::OpenFileNotReadable("stderr"),
253 )),
254 Self::File(f) => f.read(buf),
255 Self::PipeReader(reader) => reader.read(buf),
256 Self::PipeWriter(_) => Err(std::io::Error::other(
257 error::ErrorKind::OpenFileNotReadable("pipe writer"),
258 )),
259 Self::Stream(s) => s.read(buf),
260 }
261 }
262}
263
264impl std::io::Write for OpenFile {
265 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
266 match self {
267 Self::Stdin(_) => Err(std::io::Error::other(
268 error::ErrorKind::OpenFileNotWritable("stdin"),
269 )),
270 Self::Stdout(f) => f.write(buf),
271 Self::Stderr(f) => f.write(buf),
272 Self::File(f) => f.write(buf),
273 Self::PipeReader(_) => Err(std::io::Error::other(
274 error::ErrorKind::OpenFileNotWritable("pipe reader"),
275 )),
276 Self::PipeWriter(writer) => writer.write(buf),
277 Self::Stream(s) => s.write(buf),
278 }
279 }
280
281 fn flush(&mut self) -> std::io::Result<()> {
282 match self {
283 Self::Stdin(_) => Ok(()),
284 Self::Stdout(f) => f.flush(),
285 Self::Stderr(f) => f.flush(),
286 Self::File(f) => f.flush(),
287 Self::PipeReader(_) => Ok(()),
288 Self::PipeWriter(writer) => writer.flush(),
289 Self::Stream(s) => s.flush(),
290 }
291 }
292}
293
294pub enum OpenFileEntry<'a> {
296 Open(&'a OpenFile),
298 NotPresent,
300 NotSpecified,
303}
304
305#[derive(Clone, Default)]
307#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
308pub struct OpenFiles {
309 files: HashMap<ShellFd, Option<OpenFile>>,
311}
312
313impl OpenFiles {
314 pub const STDIN_FD: ShellFd = 0;
316 pub const STDOUT_FD: ShellFd = 1;
318 pub const STDERR_FD: ShellFd = 2;
320
321 const FIRST_NON_STDIO_FD: ShellFd = 3;
323 const MAX_FD: ShellFd = 1024;
325
326 pub(crate) fn new() -> Self {
329 Self {
330 files: HashMap::from([
331 (Self::STDIN_FD, Some(std::io::stdin().into())),
332 (Self::STDOUT_FD, Some(std::io::stdout().into())),
333 (Self::STDERR_FD, Some(std::io::stderr().into())),
334 ]),
335 }
336 }
337
338 pub fn update_from(&mut self, files: impl Iterator<Item = (ShellFd, OpenFile)>) {
345 for (fd, file) in files {
346 let _ = self.files.insert(fd, Some(file));
347 }
348 }
349
350 pub fn try_stdin(&self) -> Option<&OpenFile> {
352 self.files.get(&Self::STDIN_FD).and_then(|f| f.as_ref())
353 }
354
355 pub fn try_stdout(&self) -> Option<&OpenFile> {
357 self.files.get(&Self::STDOUT_FD).and_then(|f| f.as_ref())
358 }
359
360 pub fn try_stderr(&self) -> Option<&OpenFile> {
362 self.files.get(&Self::STDERR_FD).and_then(|f| f.as_ref())
363 }
364
365 pub fn remove_fd(&mut self, fd: ShellFd) -> Option<OpenFile> {
373 self.files.insert(fd, None).and_then(|f| f)
374 }
375
376 pub fn try_fd(&self, fd: ShellFd) -> Option<&OpenFile> {
383 self.files.get(&fd).and_then(|f| f.as_ref())
384 }
385
386 pub fn fd_entry(&self, fd: ShellFd) -> OpenFileEntry<'_> {
393 self.files
394 .get(&fd)
395 .map_or(OpenFileEntry::NotSpecified, |opt_file| match opt_file {
396 Some(f) => OpenFileEntry::Open(f),
397 None => OpenFileEntry::NotPresent,
398 })
399 }
400
401 pub fn contains_fd(&self, fd: ShellFd) -> bool {
403 self.files.contains_key(&fd)
404 }
405
406 pub fn set_fd(&mut self, fd: ShellFd, file: OpenFile) -> Option<OpenFile> {
415 self.files.insert(fd, Some(file)).and_then(|f| f)
416 }
417
418 pub fn iter_fds(&self) -> impl Iterator<Item = (ShellFd, &OpenFile)> {
420 self.files
421 .iter()
422 .filter_map(|(fd, file)| file.as_ref().map(|f| (*fd, f)))
423 }
424
425 pub fn add(&mut self, file: OpenFile) -> Result<ShellFd, error::Error> {
431 let mut fd = Self::FIRST_NON_STDIO_FD;
433 while self.files.contains_key(&fd) {
434 if fd >= Self::MAX_FD {
435 return Err(error::ErrorKind::TooManyOpenFiles.into());
436 }
437
438 fd += 1;
439 }
440
441 self.files.insert(fd, Some(file));
442 Ok(fd)
443 }
444}
445
446impl<I> From<I> for OpenFiles
447where
448 I: Iterator<Item = (ShellFd, OpenFile)>,
449{
450 fn from(iter: I) -> Self {
451 let files = iter.map(|(fd, file)| (fd, Some(file))).collect();
452 Self { files }
453 }
454}