1use std::collections::HashMap;
4use std::io::IsTerminal;
5use std::process::Stdio;
6
7use crate::ShellFd;
8use crate::error;
9use crate::ioutils;
10use crate::sys;
11
12pub trait Stream: std::io::Read + std::io::Write + Send + Sync {
18 fn clone_box(&self) -> Box<dyn Stream>;
20
21 #[cfg(unix)]
24 fn try_clone_to_owned(&self) -> Result<std::os::fd::OwnedFd, error::Error>;
25
26 #[cfg(unix)]
29 fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error>;
30}
31
32pub enum OpenFile {
34 Stdin(std::io::Stdin),
36 Stdout(std::io::Stdout),
38 Stderr(std::io::Stderr),
40 File(std::fs::File),
42 PipeReader(std::io::PipeReader),
44 PipeWriter(std::io::PipeWriter),
46 Stream(Box<dyn Stream>),
48}
49
50#[cfg(feature = "serde")]
51impl serde::Serialize for OpenFile {
52 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
53 where
54 S: serde::Serializer,
55 {
56 match self {
57 Self::Stdin(_) => serializer.serialize_str("stdin"),
58 Self::Stdout(_) => serializer.serialize_str("stdout"),
59 Self::Stderr(_) => serializer.serialize_str("stderr"),
60 Self::File(_) => serializer.serialize_str("file"),
61 Self::PipeReader(_) => serializer.serialize_str("pipe_reader"),
62 Self::PipeWriter(_) => serializer.serialize_str("pipe_writer"),
63 Self::Stream(_) => serializer.serialize_str("stream"),
64 }
65 }
66}
67
68#[cfg(feature = "serde")]
69impl<'de> serde::Deserialize<'de> for OpenFile {
70 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
71 where
72 D: serde::Deserializer<'de>,
73 {
74 match String::deserialize(deserializer)?.as_str() {
75 "stdin" => return Ok(std::io::stdin().into()),
76 "stdout" => return Ok(std::io::stdout().into()),
77 "stderr" => return Ok(std::io::stderr().into()),
78 "file" => (),
79 "pipe_reader" => (),
80 "pipe_writer" => (),
81 "stream" => (),
82 _ => return Err(serde::de::Error::custom("invalid open file")),
83 }
84
85 null().map_err(serde::de::Error::custom)
87 }
88}
89
90pub fn null() -> Result<OpenFile, error::Error> {
92 let file = sys::fs::open_null_file()?;
93 Ok(OpenFile::File(file))
94}
95
96impl Clone for OpenFile {
97 fn clone(&self) -> Self {
98 self.try_clone().unwrap_or_else(|_err| {
101 ioutils::FailingReaderWriter::new("failed to duplicate open file").into()
102 })
103 }
104}
105
106impl std::fmt::Display for OpenFile {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 match self {
109 Self::Stdin(_) => write!(f, "stdin"),
110 Self::Stdout(_) => write!(f, "stdout"),
111 Self::Stderr(_) => write!(f, "stderr"),
112 Self::File(_) => write!(f, "file"),
113 Self::PipeReader(_) => write!(f, "pipe reader"),
114 Self::PipeWriter(_) => write!(f, "pipe writer"),
115 Self::Stream(_) => write!(f, "stream"),
116 }
117 }
118}
119
120impl OpenFile {
121 pub fn try_clone(&self) -> Result<Self, std::io::Error> {
123 let result = match self {
124 Self::Stdin(_) => std::io::stdin().into(),
125 Self::Stdout(_) => std::io::stdout().into(),
126 Self::Stderr(_) => std::io::stderr().into(),
127 Self::File(f) => f.try_clone()?.into(),
128 Self::PipeReader(f) => f.try_clone()?.into(),
129 Self::PipeWriter(f) => f.try_clone()?.into(),
130 Self::Stream(s) => Self::Stream(s.clone_box()),
131 };
132
133 Ok(result)
134 }
135
136 #[cfg(unix)]
138 pub(crate) fn try_clone_to_owned(self) -> Result<std::os::fd::OwnedFd, error::Error> {
139 use std::os::fd::AsFd as _;
140
141 match self {
142 Self::Stdin(f) => Ok(f.as_fd().try_clone_to_owned()?),
143 Self::Stdout(f) => Ok(f.as_fd().try_clone_to_owned()?),
144 Self::Stderr(f) => Ok(f.as_fd().try_clone_to_owned()?),
145 Self::File(f) => Ok(f.into()),
146 Self::PipeReader(r) => Ok(std::os::fd::OwnedFd::from(r)),
147 Self::PipeWriter(w) => Ok(std::os::fd::OwnedFd::from(w)),
148 Self::Stream(s) => s.try_clone_to_owned(),
149 }
150 }
151
152 #[cfg(unix)]
158 pub fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error> {
159 use std::os::fd::AsFd as _;
160
161 match self {
162 Self::Stdin(f) => Ok(f.as_fd()),
163 Self::Stdout(f) => Ok(f.as_fd()),
164 Self::Stderr(f) => Ok(f.as_fd()),
165 Self::File(f) => Ok(f.as_fd()),
166 Self::PipeReader(r) => Ok(r.as_fd()),
167 Self::PipeWriter(w) => Ok(w.as_fd()),
168 Self::Stream(s) => s.try_borrow_as_fd(),
169 }
170 }
171
172 pub(crate) fn is_dir(&self) -> bool {
173 match self {
174 Self::Stdin(_) | Self::Stdout(_) | Self::Stderr(_) => false,
175 Self::File(file) => file.metadata().is_ok_and(|m| m.is_dir()),
176 Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
177 }
178 }
179
180 pub fn is_terminal(&self) -> bool {
182 match self {
183 Self::Stdin(f) => f.is_terminal(),
184 Self::Stdout(f) => f.is_terminal(),
185 Self::Stderr(f) => f.is_terminal(),
186 Self::File(f) => f.is_terminal(),
187 Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
188 }
189 }
190}
191
192impl From<std::io::Stdin> for OpenFile {
193 fn from(stdin: std::io::Stdin) -> Self {
195 Self::Stdin(stdin)
196 }
197}
198
199impl From<std::io::Stdout> for OpenFile {
200 fn from(stdout: std::io::Stdout) -> Self {
202 Self::Stdout(stdout)
203 }
204}
205
206impl From<std::io::Stderr> for OpenFile {
207 fn from(stderr: std::io::Stderr) -> Self {
209 Self::Stderr(stderr)
210 }
211}
212
213impl From<std::fs::File> for OpenFile {
214 fn from(file: std::fs::File) -> Self {
215 Self::File(file)
216 }
217}
218
219impl From<std::io::PipeReader> for OpenFile {
220 fn from(reader: std::io::PipeReader) -> Self {
221 Self::PipeReader(reader)
222 }
223}
224
225impl From<std::io::PipeWriter> for OpenFile {
226 fn from(writer: std::io::PipeWriter) -> Self {
227 Self::PipeWriter(writer)
228 }
229}
230
231impl From<OpenFile> for Stdio {
232 fn from(open_file: OpenFile) -> Self {
233 match open_file {
234 OpenFile::Stdin(_) => Self::inherit(),
235 OpenFile::Stdout(_) => Self::inherit(),
236 OpenFile::Stderr(_) => Self::inherit(),
237 OpenFile::File(f) => f.into(),
238 OpenFile::PipeReader(f) => f.into(),
239 OpenFile::PipeWriter(f) => f.into(),
240 OpenFile::Stream(_) => Self::null(),
243 }
244 }
245}
246
247impl std::io::Read for OpenFile {
248 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
249 match self {
250 Self::Stdin(f) => f.read(buf),
251 Self::Stdout(_) => Err(std::io::Error::other(
252 error::ErrorKind::OpenFileNotReadable("stdout"),
253 )),
254 Self::Stderr(_) => Err(std::io::Error::other(
255 error::ErrorKind::OpenFileNotReadable("stderr"),
256 )),
257 Self::File(f) => f.read(buf),
258 Self::PipeReader(reader) => reader.read(buf),
259 Self::PipeWriter(_) => Err(std::io::Error::other(
260 error::ErrorKind::OpenFileNotReadable("pipe writer"),
261 )),
262 Self::Stream(s) => s.read(buf),
263 }
264 }
265}
266
267impl std::io::Write for OpenFile {
268 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
269 match self {
270 Self::Stdin(_) => Err(std::io::Error::other(
271 error::ErrorKind::OpenFileNotWritable("stdin"),
272 )),
273 Self::Stdout(f) => f.write(buf),
274 Self::Stderr(f) => f.write(buf),
275 Self::File(f) => f.write(buf),
276 Self::PipeReader(_) => Err(std::io::Error::other(
277 error::ErrorKind::OpenFileNotWritable("pipe reader"),
278 )),
279 Self::PipeWriter(writer) => writer.write(buf),
280 Self::Stream(s) => s.write(buf),
281 }
282 }
283
284 fn flush(&mut self) -> std::io::Result<()> {
285 match self {
286 Self::Stdin(_) => Ok(()),
287 Self::Stdout(f) => f.flush(),
288 Self::Stderr(f) => f.flush(),
289 Self::File(f) => f.flush(),
290 Self::PipeReader(_) => Ok(()),
291 Self::PipeWriter(writer) => writer.flush(),
292 Self::Stream(s) => s.flush(),
293 }
294 }
295}
296
297pub enum OpenFileEntry<'a> {
299 Open(&'a OpenFile),
301 NotPresent,
303 NotSpecified,
306}
307
308#[derive(Clone, Default)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311pub struct OpenFiles {
312 files: HashMap<ShellFd, Option<OpenFile>>,
314}
315
316impl OpenFiles {
317 pub const STDIN_FD: ShellFd = 0;
319 pub const STDOUT_FD: ShellFd = 1;
321 pub const STDERR_FD: ShellFd = 2;
323
324 const FIRST_NON_STDIO_FD: ShellFd = 3;
326 const MAX_FD: ShellFd = 1024;
328
329 pub(crate) fn new() -> Self {
332 Self {
333 files: HashMap::from([
334 (Self::STDIN_FD, Some(std::io::stdin().into())),
335 (Self::STDOUT_FD, Some(std::io::stdout().into())),
336 (Self::STDERR_FD, Some(std::io::stderr().into())),
337 ]),
338 }
339 }
340
341 pub fn update_from(&mut self, files: impl Iterator<Item = (ShellFd, OpenFile)>) {
348 for (fd, file) in files {
349 let _ = self.files.insert(fd, Some(file));
350 }
351 }
352
353 pub fn try_stdin(&self) -> Option<&OpenFile> {
355 self.files.get(&Self::STDIN_FD).and_then(|f| f.as_ref())
356 }
357
358 pub fn try_stdout(&self) -> Option<&OpenFile> {
360 self.files.get(&Self::STDOUT_FD).and_then(|f| f.as_ref())
361 }
362
363 pub fn try_stderr(&self) -> Option<&OpenFile> {
365 self.files.get(&Self::STDERR_FD).and_then(|f| f.as_ref())
366 }
367
368 pub fn remove_fd(&mut self, fd: ShellFd) -> Option<OpenFile> {
376 self.files.insert(fd, None).and_then(|f| f)
377 }
378
379 pub fn try_fd(&self, fd: ShellFd) -> Option<&OpenFile> {
386 self.files.get(&fd).and_then(|f| f.as_ref())
387 }
388
389 pub fn fd_entry(&self, fd: ShellFd) -> OpenFileEntry<'_> {
396 self.files
397 .get(&fd)
398 .map_or(OpenFileEntry::NotSpecified, |opt_file| match opt_file {
399 Some(f) => OpenFileEntry::Open(f),
400 None => OpenFileEntry::NotPresent,
401 })
402 }
403
404 pub fn contains_fd(&self, fd: ShellFd) -> bool {
406 self.files.contains_key(&fd)
407 }
408
409 pub fn set_fd(&mut self, fd: ShellFd, file: OpenFile) -> Option<OpenFile> {
418 self.files.insert(fd, Some(file)).and_then(|f| f)
419 }
420
421 pub fn iter_fds(&self) -> impl Iterator<Item = (ShellFd, &OpenFile)> {
423 self.files
424 .iter()
425 .filter_map(|(fd, file)| file.as_ref().map(|f| (*fd, f)))
426 }
427
428 pub fn add(&mut self, file: OpenFile) -> Result<ShellFd, error::Error> {
434 let mut fd = Self::FIRST_NON_STDIO_FD;
436 while self.files.contains_key(&fd) {
437 if fd >= Self::MAX_FD {
438 return Err(error::ErrorKind::TooManyOpenFiles.into());
439 }
440
441 fd += 1;
442 }
443
444 self.files.insert(fd, Some(file));
445 Ok(fd)
446 }
447}
448
449impl<I> From<I> for OpenFiles
450where
451 I: Iterator<Item = (ShellFd, OpenFile)>,
452{
453 fn from(iter: I) -> Self {
454 let files = iter.map(|(fd, file)| (fd, Some(file))).collect();
455 Self { files }
456 }
457}