use std::{fmt::Debug, io::Result, os::unix::io::AsRawFd, process::Stdio};
use async_trait::async_trait;
use nix::unistd::{Gid, Uid};
use tokio::fs::OpenOptions;
pub use crate::Io;
use crate::{Command, Pipe, PipedIo};
#[derive(Debug, Clone)]
pub struct IOOption {
pub open_stdin: bool,
pub open_stdout: bool,
pub open_stderr: bool,
}
impl Default for IOOption {
fn default() -> Self {
Self {
open_stdin: true,
open_stdout: true,
open_stderr: true,
}
}
}
impl PipedIo {
pub fn new(uid: u32, gid: u32, opts: &IOOption) -> std::io::Result<Self> {
Ok(Self {
stdin: if opts.open_stdin {
Self::create_pipe(uid, gid, true)?
} else {
None
},
stdout: if opts.open_stdout {
Self::create_pipe(uid, gid, true)?
} else {
None
},
stderr: if opts.open_stderr {
Self::create_pipe(uid, gid, true)?
} else {
None
},
})
}
fn create_pipe(uid: u32, gid: u32, stdin: bool) -> std::io::Result<Option<Pipe>> {
let pipe = Pipe::new()?;
let uid = Some(Uid::from_raw(uid));
let gid = Some(Gid::from_raw(gid));
if stdin {
let rd = pipe.rd.try_clone()?;
nix::unistd::fchown(rd.as_raw_fd(), uid, gid)?;
} else {
let wr = pipe.wr.try_clone()?;
nix::unistd::fchown(wr.as_raw_fd(), uid, gid)?;
}
Ok(Some(pipe))
}
}
#[derive(Debug)]
pub struct NullIo {
dev_null: std::sync::Mutex<Option<std::fs::File>>,
}
impl NullIo {
pub fn new() -> std::io::Result<Self> {
let f = std::fs::OpenOptions::new().read(true).open("/dev/null")?;
let dev_null = std::sync::Mutex::new(Some(f));
Ok(Self { dev_null })
}
}
#[async_trait]
impl Io for NullIo {
async fn set(&self, cmd: &mut Command) -> std::io::Result<()> {
if let Some(null) = self.dev_null.lock().unwrap().as_ref() {
cmd.stdout(null.try_clone()?);
cmd.stderr(null.try_clone()?);
}
Ok(())
}
async fn close_after_start(&self) {
let mut m = self.dev_null.lock().unwrap();
let _ = m.take();
}
}
#[derive(Debug)]
pub struct InheritedStdIo {}
impl InheritedStdIo {
pub fn new() -> std::io::Result<Self> {
Ok(InheritedStdIo {})
}
}
#[async_trait]
impl Io for InheritedStdIo {
async fn set(&self, cmd: &mut Command) -> std::io::Result<()> {
cmd.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
Ok(())
}
async fn close_after_start(&self) {}
}
#[derive(Debug)]
pub struct PipedStdIo {}
impl PipedStdIo {
pub fn new() -> std::io::Result<Self> {
Ok(PipedStdIo {})
}
}
#[async_trait]
impl Io for PipedStdIo {
async fn set(&self, cmd: &mut Command) -> std::io::Result<()> {
cmd.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
Ok(())
}
async fn close_after_start(&self) {}
}
#[derive(Debug)]
pub struct FIFO {
pub stdin: Option<String>,
pub stdout: Option<String>,
pub stderr: Option<String>,
}
#[async_trait]
impl Io for FIFO {
async fn set(&self, cmd: &mut Command) -> Result<()> {
if let Some(path) = self.stdin.as_ref() {
let stdin = OpenOptions::new()
.read(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
.await?;
cmd.stdin(stdin.into_std().await);
}
if let Some(path) = self.stdout.as_ref() {
let stdout = OpenOptions::new().write(true).open(path).await?;
cmd.stdout(stdout.into_std().await);
}
if let Some(path) = self.stderr.as_ref() {
let stderr = OpenOptions::new().write(true).open(path).await?;
cmd.stderr(stderr.into_std().await);
}
Ok(())
}
async fn close_after_start(&self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(target_os = "macos"))]
#[test]
fn test_io_option() {
let opts = IOOption {
open_stdin: false,
open_stdout: false,
open_stderr: false,
};
let io = PipedIo::new(1000, 1000, &opts).unwrap();
assert!(io.stdin().is_none());
assert!(io.stdout().is_none());
assert!(io.stderr().is_none());
}
#[tokio::test]
async fn test_null_io() {
let io = NullIo::new().unwrap();
assert!(io.stdin().is_none());
assert!(io.stdout().is_none());
assert!(io.stderr().is_none());
}
}