use std::{collections::HashMap, io::IsTerminal, process::Stdio};
use crate::{ShellFd, error, ioutils, sys};
pub trait Stream: std::io::Read + std::io::Write + Send + Sync {
fn clone_box(&self) -> Box<dyn Stream>;
#[cfg(unix)]
fn try_clone_to_owned(&self) -> Result<std::os::fd::OwnedFd, error::Error>;
#[cfg(unix)]
fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error>;
}
pub enum OpenFile {
Stdin(std::io::Stdin),
Stdout(std::io::Stdout),
Stderr(std::io::Stderr),
File(std::fs::File),
PipeReader(std::io::PipeReader),
PipeWriter(std::io::PipeWriter),
Stream(Box<dyn Stream>),
}
#[cfg(feature = "serde")]
impl serde::Serialize for OpenFile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Stdin(_) => serializer.serialize_str("stdin"),
Self::Stdout(_) => serializer.serialize_str("stdout"),
Self::Stderr(_) => serializer.serialize_str("stderr"),
Self::File(_) => serializer.serialize_str("file"),
Self::PipeReader(_) => serializer.serialize_str("pipe_reader"),
Self::PipeWriter(_) => serializer.serialize_str("pipe_writer"),
Self::Stream(_) => serializer.serialize_str("stream"),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for OpenFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match String::deserialize(deserializer)?.as_str() {
"stdin" => return Ok(std::io::stdin().into()),
"stdout" => return Ok(std::io::stdout().into()),
"stderr" => return Ok(std::io::stderr().into()),
"file" => (),
"pipe_reader" => (),
"pipe_writer" => (),
"stream" => (),
_ => return Err(serde::de::Error::custom("invalid open file")),
}
null().map_err(serde::de::Error::custom)
}
}
pub fn null() -> Result<OpenFile, error::Error> {
let file = sys::fs::open_null_file()?;
Ok(OpenFile::File(file))
}
impl Clone for OpenFile {
fn clone(&self) -> Self {
self.try_clone().unwrap_or_else(|_err| {
ioutils::FailingReaderWriter::new("failed to duplicate open file").into()
})
}
}
impl std::fmt::Display for OpenFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Stdin(_) => write!(f, "stdin"),
Self::Stdout(_) => write!(f, "stdout"),
Self::Stderr(_) => write!(f, "stderr"),
Self::File(_) => write!(f, "file"),
Self::PipeReader(_) => write!(f, "pipe reader"),
Self::PipeWriter(_) => write!(f, "pipe writer"),
Self::Stream(_) => write!(f, "stream"),
}
}
}
impl OpenFile {
pub fn try_clone(&self) -> Result<Self, std::io::Error> {
let result = match self {
Self::Stdin(_) => std::io::stdin().into(),
Self::Stdout(_) => std::io::stdout().into(),
Self::Stderr(_) => std::io::stderr().into(),
Self::File(f) => f.try_clone()?.into(),
Self::PipeReader(f) => f.try_clone()?.into(),
Self::PipeWriter(f) => f.try_clone()?.into(),
Self::Stream(s) => Self::Stream(s.clone_box()),
};
Ok(result)
}
#[cfg(unix)]
pub(crate) fn try_clone_to_owned(self) -> Result<std::os::fd::OwnedFd, error::Error> {
use std::os::fd::AsFd as _;
match self {
Self::Stdin(f) => Ok(f.as_fd().try_clone_to_owned()?),
Self::Stdout(f) => Ok(f.as_fd().try_clone_to_owned()?),
Self::Stderr(f) => Ok(f.as_fd().try_clone_to_owned()?),
Self::File(f) => Ok(f.into()),
Self::PipeReader(r) => Ok(std::os::fd::OwnedFd::from(r)),
Self::PipeWriter(w) => Ok(std::os::fd::OwnedFd::from(w)),
Self::Stream(s) => s.try_clone_to_owned(),
}
}
#[cfg(unix)]
pub fn try_borrow_as_fd(&self) -> Result<std::os::fd::BorrowedFd<'_>, error::Error> {
use std::os::fd::AsFd as _;
match self {
Self::Stdin(f) => Ok(f.as_fd()),
Self::Stdout(f) => Ok(f.as_fd()),
Self::Stderr(f) => Ok(f.as_fd()),
Self::File(f) => Ok(f.as_fd()),
Self::PipeReader(r) => Ok(r.as_fd()),
Self::PipeWriter(w) => Ok(w.as_fd()),
Self::Stream(s) => s.try_borrow_as_fd(),
}
}
pub(crate) fn into_stdio(self) -> Result<Stdio, error::Error> {
#[cfg(unix)]
{
let owned_fd = self.try_clone_to_owned()?;
Ok(Stdio::from(std::fs::File::from(owned_fd)))
}
#[cfg(not(unix))]
{
let stdio = match self {
Self::Stdin(_) => Stdio::inherit(),
Self::Stdout(_) => Stdio::inherit(),
Self::Stderr(_) => Stdio::inherit(),
Self::File(f) => f.into(),
Self::PipeReader(f) => f.into(),
Self::PipeWriter(f) => f.into(),
Self::Stream(_) => return Err(error::ErrorKind::CannotConvertToNativeFd.into()),
};
Ok(stdio)
}
}
pub(crate) fn is_dir(&self) -> bool {
match self {
Self::Stdin(_) | Self::Stdout(_) | Self::Stderr(_) => false,
Self::File(file) => file.metadata().is_ok_and(|m| m.is_dir()),
Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
}
}
pub fn is_terminal(&self) -> bool {
match self {
Self::Stdin(f) => f.is_terminal(),
Self::Stdout(f) => f.is_terminal(),
Self::Stderr(f) => f.is_terminal(),
Self::File(f) => f.is_terminal(),
Self::PipeReader(_) | Self::PipeWriter(_) | Self::Stream(_) => false,
}
}
}
impl From<std::io::Stdin> for OpenFile {
fn from(stdin: std::io::Stdin) -> Self {
Self::Stdin(stdin)
}
}
impl From<std::io::Stdout> for OpenFile {
fn from(stdout: std::io::Stdout) -> Self {
Self::Stdout(stdout)
}
}
impl From<std::io::Stderr> for OpenFile {
fn from(stderr: std::io::Stderr) -> Self {
Self::Stderr(stderr)
}
}
impl From<std::fs::File> for OpenFile {
fn from(file: std::fs::File) -> Self {
Self::File(file)
}
}
impl From<std::io::PipeReader> for OpenFile {
fn from(reader: std::io::PipeReader) -> Self {
Self::PipeReader(reader)
}
}
impl From<std::io::PipeWriter> for OpenFile {
fn from(writer: std::io::PipeWriter) -> Self {
Self::PipeWriter(writer)
}
}
impl std::io::Read for OpenFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
Self::Stdin(f) => f.read(buf),
Self::Stdout(_) => {
Err(std::io::Error::other(error::ErrorKind::OpenFileNotReadable("stdout")))
},
Self::Stderr(_) => {
Err(std::io::Error::other(error::ErrorKind::OpenFileNotReadable("stderr")))
},
Self::File(f) => f.read(buf),
Self::PipeReader(reader) => reader.read(buf),
Self::PipeWriter(_) => {
Err(std::io::Error::other(error::ErrorKind::OpenFileNotReadable("pipe writer")))
},
Self::Stream(s) => s.read(buf),
}
}
}
impl std::io::Write for OpenFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Self::Stdin(_) => {
Err(std::io::Error::other(error::ErrorKind::OpenFileNotWritable("stdin")))
},
Self::Stdout(f) => f.write(buf),
Self::Stderr(f) => f.write(buf),
Self::File(f) => f.write(buf),
Self::PipeReader(_) => {
Err(std::io::Error::other(error::ErrorKind::OpenFileNotWritable("pipe reader")))
},
Self::PipeWriter(writer) => writer.write(buf),
Self::Stream(s) => s.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Self::Stdin(_) => Ok(()),
Self::Stdout(f) => f.flush(),
Self::Stderr(f) => f.flush(),
Self::File(f) => f.flush(),
Self::PipeReader(_) => Ok(()),
Self::PipeWriter(writer) => writer.flush(),
Self::Stream(s) => s.flush(),
}
}
}
pub enum OpenFileEntry<'a> {
Open(&'a OpenFile),
NotPresent,
NotSpecified,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OpenFiles {
files: HashMap<ShellFd, Option<OpenFile>>,
}
impl OpenFiles {
const FIRST_NON_STDIO_FD: ShellFd = 3;
const MAX_FD: ShellFd = 1024;
pub const STDERR_FD: ShellFd = 2;
pub const STDIN_FD: ShellFd = 0;
pub const STDOUT_FD: ShellFd = 1;
pub(crate) fn new() -> Self {
Self {
files: HashMap::from([
(Self::STDIN_FD, Some(std::io::stdin().into())),
(Self::STDOUT_FD, Some(std::io::stdout().into())),
(Self::STDERR_FD, Some(std::io::stderr().into())),
]),
}
}
pub fn update_from(&mut self, files: impl Iterator<Item = (ShellFd, OpenFile)>) {
for (fd, file) in files {
let _ = self.files.insert(fd, Some(file));
}
}
pub fn try_stdin(&self) -> Option<&OpenFile> {
self.files.get(&Self::STDIN_FD).and_then(|f| f.as_ref())
}
pub fn try_stdout(&self) -> Option<&OpenFile> {
self.files.get(&Self::STDOUT_FD).and_then(|f| f.as_ref())
}
pub fn try_stderr(&self) -> Option<&OpenFile> {
self.files.get(&Self::STDERR_FD).and_then(|f| f.as_ref())
}
pub fn remove_fd(&mut self, fd: ShellFd) -> Option<OpenFile> {
self.files.insert(fd, None).and_then(|f| f)
}
pub fn try_fd(&self, fd: ShellFd) -> Option<&OpenFile> {
self.files.get(&fd).and_then(|f| f.as_ref())
}
pub fn fd_entry(&self, fd: ShellFd) -> OpenFileEntry<'_> {
self
.files
.get(&fd)
.map_or(OpenFileEntry::NotSpecified, |opt_file| match opt_file {
Some(f) => OpenFileEntry::Open(f),
None => OpenFileEntry::NotPresent,
})
}
pub fn contains_fd(&self, fd: ShellFd) -> bool {
self.files.contains_key(&fd)
}
pub fn set_fd(&mut self, fd: ShellFd, file: OpenFile) -> Option<OpenFile> {
self.files.insert(fd, Some(file)).and_then(|f| f)
}
pub fn iter_fds(&self) -> impl Iterator<Item = (ShellFd, &OpenFile)> {
self
.files
.iter()
.filter_map(|(fd, file)| file.as_ref().map(|f| (*fd, f)))
}
pub fn add(&mut self, file: OpenFile) -> Result<ShellFd, error::Error> {
let mut fd = Self::FIRST_NON_STDIO_FD;
while self.files.contains_key(&fd) {
if fd >= Self::MAX_FD {
return Err(error::ErrorKind::TooManyOpenFiles.into());
}
fd += 1;
}
self.files.insert(fd, Some(file));
Ok(fd)
}
}
impl<I> From<I> for OpenFiles
where
I: Iterator<Item = (ShellFd, OpenFile)>,
{
fn from(iter: I) -> Self {
let files = iter.map(|(fd, file)| (fd, Some(file))).collect();
Self { files }
}
}