1use std::collections::HashMap;
4use std::io::IsTerminal;
5use std::process::Stdio;
6use std::sync::Arc;
7
8use crate::{ShellFd, error, sys};
9
10pub trait Stream: std::io::Read + std::io::Write + Send + Sync {
16 fn clone_box(&self) -> Box<dyn Stream>;
18
19 #[cfg(unix)] fn try_clone_to_owned(&self) -> Result<std::os::fd::OwnedFd, error::Error>;
23
24 #[cfg(unix)] fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error>;
28}
29
30pub enum OpenFile {
43 Stdin(std::io::Stdin),
45 Stdout(std::io::Stdout),
47 Stderr(std::io::Stderr),
49 File(Arc<std::fs::File>),
51 PipeReader(Arc<std::io::PipeReader>),
53 PipeWriter(Arc<std::io::PipeWriter>),
55 Stream(Box<dyn Stream>),
57}
58
59#[cfg(feature = "serde")]
60impl serde::Serialize for OpenFile {
61 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62 where
63 S: serde::Serializer,
64 {
65 match self {
66 Self::Stdin(_) => serializer.serialize_str("stdin"),
67 Self::Stdout(_) => serializer.serialize_str("stdout"),
68 Self::Stderr(_) => serializer.serialize_str("stderr"),
69 Self::File(_) => serializer.serialize_str("file"),
70 Self::PipeReader(_) => serializer.serialize_str("pipe_reader"),
71 Self::PipeWriter(_) => serializer.serialize_str("pipe_writer"),
72 Self::Stream(_) => serializer.serialize_str("stream"),
73 }
74 }
75}
76
77#[cfg(feature = "serde")]
78impl<'de> serde::Deserialize<'de> for OpenFile {
79 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80 where
81 D: serde::Deserializer<'de>,
82 {
83 match String::deserialize(deserializer)?.as_str() {
84 "stdin" => return Ok(std::io::stdin().into()),
85 "stdout" => return Ok(std::io::stdout().into()),
86 "stderr" => return Ok(std::io::stderr().into()),
87 "file" => (),
88 "pipe_reader" => (),
89 "pipe_writer" => (),
90 "stream" => (),
91 _ => return Err(serde::de::Error::custom("invalid open file")),
92 }
93
94 null().map_err(serde::de::Error::custom)
97 }
98}
99
100pub fn null() -> Result<OpenFile, error::Error> {
102 let file = sys::fs::open_null_file()?;
103 Ok(file.into())
104}
105
106impl Clone for OpenFile {
107 fn clone(&self) -> Self {
108 match self {
109 Self::Stdin(_) => std::io::stdin().into(),
110 Self::Stdout(_) => std::io::stdout().into(),
111 Self::Stderr(_) => std::io::stderr().into(),
112 Self::File(f) => Self::File(Arc::clone(f)),
115 Self::PipeReader(r) => Self::PipeReader(Arc::clone(r)),
116 Self::PipeWriter(w) => Self::PipeWriter(Arc::clone(w)),
117 Self::Stream(s) => Self::Stream(s.clone_box()),
118 }
119 }
120}
121
122impl std::fmt::Display for OpenFile {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 Self::Stdin(_) => write!(f, "stdin"),
126 Self::Stdout(_) => write!(f, "stdout"),
127 Self::Stderr(_) => write!(f, "stderr"),
128 Self::File(_) => write!(f, "file"),
129 Self::PipeReader(_) => write!(f, "pipe reader"),
130 Self::PipeWriter(_) => write!(f, "pipe writer"),
131 Self::Stream(_) => write!(f, "stream"),
132 }
133 }
134}
135
136impl OpenFile {
137 #[cfg(unix)] pub(crate) fn try_clone_to_owned(self) -> Result<std::os::fd::OwnedFd, error::Error> {
142 use std::os::fd::AsFd as _;
143
144 match self {
145 Self::Stdin(f) => Ok(f.as_fd().try_clone_to_owned()?),
146 Self::Stdout(f) => Ok(f.as_fd().try_clone_to_owned()?),
147 Self::Stderr(f) => Ok(f.as_fd().try_clone_to_owned()?),
148 Self::File(f) => Ok(f.as_fd().try_clone_to_owned()?),
149 Self::PipeReader(r) => Ok(r.as_fd().try_clone_to_owned()?),
150 Self::PipeWriter(w) => Ok(w.as_fd().try_clone_to_owned()?),
151 Self::Stream(s) => s.try_clone_to_owned(),
152 }
153 }
154
155 #[cfg(unix)] pub fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error> {
163 use std::os::fd::AsFd as _;
164
165 match self {
166 Self::Stdin(f) => Ok(f.as_fd()),
167 Self::Stdout(f) => Ok(f.as_fd()),
168 Self::Stderr(f) => Ok(f.as_fd()),
169 Self::File(f) => Ok(f.as_fd()),
170 Self::PipeReader(r) => Ok(r.as_fd()),
171 Self::PipeWriter(w) => Ok(w.as_fd()),
172 Self::Stream(s) => s.try_borrow_as_fd(),
173 }
174 }
175
176 pub(crate) fn is_dir(&self) -> bool {
177 match self {
178 Self::Stdin(_) | Self::Stdout(_) | Self::Stderr(_) => false,
179 Self::File(file) => file.metadata().is_ok_and(|m| m.is_dir()),
180 Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
181 }
182 }
183
184 pub fn is_terminal(&self) -> bool {
186 match self {
187 Self::Stdin(f) => f.is_terminal(),
188 Self::Stdout(f) => f.is_terminal(),
189 Self::Stderr(f) => f.is_terminal(),
190 Self::File(f) => f.is_terminal(),
191 Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
192 }
193 }
194}
195
196impl From<std::io::Stdin> for OpenFile {
197 fn from(stdin: std::io::Stdin) -> Self {
199 Self::Stdin(stdin)
200 }
201}
202
203impl From<std::io::Stdout> for OpenFile {
204 fn from(stdout: std::io::Stdout) -> Self {
206 Self::Stdout(stdout)
207 }
208}
209
210impl From<std::io::Stderr> for OpenFile {
211 fn from(stderr: std::io::Stderr) -> Self {
213 Self::Stderr(stderr)
214 }
215}
216
217impl From<std::fs::File> for OpenFile {
218 fn from(file: std::fs::File) -> Self {
219 Self::File(Arc::new(file))
220 }
221}
222
223impl From<std::io::PipeReader> for OpenFile {
224 fn from(reader: std::io::PipeReader) -> Self {
225 Self::PipeReader(Arc::new(reader))
226 }
227}
228
229impl From<std::io::PipeWriter> for OpenFile {
230 fn from(writer: std::io::PipeWriter) -> Self {
231 Self::PipeWriter(Arc::new(writer))
232 }
233}
234
235impl TryFrom<OpenFile> for Stdio {
236 type Error = error::Error;
237
238 fn try_from(open_file: OpenFile) -> Result<Self, Self::Error> {
239 match open_file {
246 OpenFile::Stdin(_) | OpenFile::Stdout(_) | OpenFile::Stderr(_) => Ok(Self::inherit()),
247 OpenFile::File(f) => Ok(f.try_clone()?.into()),
248 OpenFile::PipeReader(r) => Ok(r.try_clone()?.into()),
249 OpenFile::PipeWriter(w) => Ok(w.try_clone()?.into()),
250 OpenFile::Stream(_) => Ok(Self::null()),
252 }
253 }
254}
255
256impl std::io::Read for OpenFile {
257 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
258 match self {
259 Self::Stdin(f) => f.read(buf),
260 Self::Stdout(_) => Err(std::io::Error::other(
261 error::ErrorKind::OpenFileNotReadable("stdout"),
262 )),
263 Self::Stderr(_) => Err(std::io::Error::other(
264 error::ErrorKind::OpenFileNotReadable("stderr"),
265 )),
266 Self::File(f) => f.as_ref().read(buf),
269 Self::PipeReader(reader) => reader.as_ref().read(buf),
270 Self::PipeWriter(_) => Err(std::io::Error::other(
271 error::ErrorKind::OpenFileNotReadable("pipe writer"),
272 )),
273 Self::Stream(s) => s.read(buf),
274 }
275 }
276}
277
278impl std::io::Write for OpenFile {
279 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
280 match self {
281 Self::Stdin(_) => Err(std::io::Error::other(
282 error::ErrorKind::OpenFileNotWritable("stdin"),
283 )),
284 Self::Stdout(f) => f.write(buf),
285 Self::Stderr(f) => f.write(buf),
286 Self::File(f) => f.as_ref().write(buf),
289 Self::PipeReader(_) => Err(std::io::Error::other(
290 error::ErrorKind::OpenFileNotWritable("pipe reader"),
291 )),
292 Self::PipeWriter(writer) => writer.as_ref().write(buf),
293 Self::Stream(s) => s.write(buf),
294 }
295 }
296
297 fn flush(&mut self) -> std::io::Result<()> {
298 match self {
299 Self::Stdin(_) => Ok(()),
300 Self::Stdout(f) => f.flush(),
301 Self::Stderr(f) => f.flush(),
302 Self::File(f) => f.as_ref().flush(),
303 Self::PipeReader(_) => Ok(()),
304 Self::PipeWriter(writer) => writer.as_ref().flush(),
305 Self::Stream(s) => s.flush(),
306 }
307 }
308}
309
310pub enum OpenFileEntry<'a> {
312 Open(&'a OpenFile),
314 NotPresent,
317 NotSpecified,
320}
321
322#[derive(Clone, Default)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
325pub struct OpenFiles {
326 files: HashMap<ShellFd, Option<OpenFile>>,
328}
329
330impl OpenFiles {
331 pub const STDIN_FD: ShellFd = 0;
333 pub const STDOUT_FD: ShellFd = 1;
335 pub const STDERR_FD: ShellFd = 2;
337
338 const FIRST_NON_STDIO_FD: ShellFd = 3;
340 const MAX_FD: ShellFd = 1024;
342
343 pub(crate) fn new() -> Self {
346 Self {
347 files: HashMap::from([
348 (Self::STDIN_FD, Some(std::io::stdin().into())),
349 (Self::STDOUT_FD, Some(std::io::stdout().into())),
350 (Self::STDERR_FD, Some(std::io::stderr().into())),
351 ]),
352 }
353 }
354
355 pub fn update_from(&mut self, files: impl Iterator<Item = (ShellFd, OpenFile)>) {
364 for (fd, file) in files {
365 let _ = self.files.insert(fd, Some(file));
366 }
367 }
368
369 pub fn try_stdin(&self) -> Option<&OpenFile> {
371 self.files.get(&Self::STDIN_FD).and_then(|f| f.as_ref())
372 }
373
374 pub fn try_stdout(&self) -> Option<&OpenFile> {
376 self.files.get(&Self::STDOUT_FD).and_then(|f| f.as_ref())
377 }
378
379 pub fn try_stderr(&self) -> Option<&OpenFile> {
381 self.files.get(&Self::STDERR_FD).and_then(|f| f.as_ref())
382 }
383
384 pub fn remove_fd(&mut self, fd: ShellFd) -> Option<OpenFile> {
392 self.files.insert(fd, None).and_then(|f| f)
393 }
394
395 pub fn try_fd(&self, fd: ShellFd) -> Option<&OpenFile> {
402 self.files.get(&fd).and_then(|f| f.as_ref())
403 }
404
405 pub fn fd_entry(&self, fd: ShellFd) -> OpenFileEntry<'_> {
413 self.files
414 .get(&fd)
415 .map_or(OpenFileEntry::NotSpecified, |opt_file| match opt_file {
416 Some(f) => OpenFileEntry::Open(f),
417 None => OpenFileEntry::NotPresent,
418 })
419 }
420
421 pub fn contains_fd(&self, fd: ShellFd) -> bool {
423 self.files.contains_key(&fd)
424 }
425
426 pub fn set_fd(&mut self, fd: ShellFd, file: OpenFile) -> Option<OpenFile> {
435 self.files.insert(fd, Some(file)).and_then(|f| f)
436 }
437
438 pub fn iter_fds(&self) -> impl Iterator<Item = (ShellFd, &OpenFile)> {
440 self.files
441 .iter()
442 .filter_map(|(fd, file)| file.as_ref().map(|f| (*fd, f)))
443 }
444
445 pub fn add(&mut self, file: OpenFile) -> Result<ShellFd, error::Error> {
451 let mut fd = Self::FIRST_NON_STDIO_FD;
453 while self.files.contains_key(&fd) {
454 if fd >= Self::MAX_FD {
455 return Err(error::ErrorKind::TooManyOpenFiles.into());
456 }
457
458 fd += 1;
459 }
460
461 self.files.insert(fd, Some(file));
462 Ok(fd)
463 }
464}
465
466impl<I> From<I> for OpenFiles
467where
468 I: Iterator<Item = (ShellFd, OpenFile)>,
469{
470 fn from(iter: I) -> Self {
471 let files = iter.map(|(fd, file)| (fd, Some(file))).collect();
472 Self { files }
473 }
474}