use alloc::{boxed::Box, string::ToString, vec::Vec};
use irid_syscall::{SystemError, ffi::{StringSlice, thread::{ThreadID, syscall_join, syscall_spawn_process}}, io::{FileDescriptor, OpenFlags, open}};
use crate::{ffi::OsStr, fs::File, io::{self, Stderr, Stdin, Stdout, WithPath}, path::Path};
#[derive(Debug)]
pub struct Command {
program: Box<str>,
args: Vec<Box<str>>,
current_dir: Option<Box<str>>,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
}
impl Command {
pub fn new(program: impl AsRef<OsStr>) -> Self {
Self {
program: program.as_ref().to_str().unwrap().to_string().into_boxed_str(),
args: Vec::new(),
current_dir: None,
stdin: Stdio { inner: StdioInner::Inherit },
stdout: Stdio { inner: StdioInner::Inherit },
stderr: Stdio { inner: StdioInner::Inherit },
}
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.args.push(arg.as_ref().to_str().unwrap().to_string().into_boxed_str());
self
}
pub fn args<S: AsRef<OsStr>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args.extend(args.into_iter().map(|x| x.as_ref().to_str().unwrap().to_string().into_boxed_str()));
self
}
pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
self.current_dir = Some(dir.as_ref().to_str().unwrap().to_string().into_boxed_str());
self
}
pub fn stdin(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.stdin = cfg.into();
self
}
pub fn stdout(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.stdout = cfg.into();
self
}
pub fn stderr(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
self.stderr = cfg.into();
self
}
pub fn spawn(&mut self) -> Result<Child, io::Error> {
let args: Vec<StringSlice> = self.args.iter()
.map(|string| StringSlice {
data: string.as_ptr(),
length: string.len(),
})
.collect();
let files = [
self.stdin.desc().unwrap_or(0),
self.stdout.desc().unwrap_or(1),
self.stderr.desc().unwrap_or(2)
];
let result: Result<ThreadID, SystemError> = syscall_spawn_process(
self.program.as_ptr(),
self.program.len(),
args.as_ptr(),
args.len(), files.as_ptr(), 3).into();
let thread = result.map_err(io::Error::from).with_path(&self.program)?;
Ok(Child {
thread
})
}
pub fn status(&mut self) -> Result<ExitStatus, io::Error> {
self.spawn()?.wait()
}
}
#[derive(Debug)]
pub struct Stdio {
inner: StdioInner
}
impl Stdio {
pub fn inherit() -> Self {
Self {
inner: StdioInner::Inherit
}
}
pub fn null() -> Self {
let null = open("/dev/null", OpenFlags::READ | OpenFlags::WRITE).expect("failed to open /dev/null");
Self {
inner: StdioInner::Desc(null)
}
}
fn desc(&self) -> Option<FileDescriptor> {
match self.inner {
StdioInner::Inherit => None,
StdioInner::Desc(desc) => Some(desc),
}
}
}
impl From<File> for Stdio {
fn from(value: File) -> Self {
Self {
inner: StdioInner::Desc(value.into_raw())
}
}
}
impl From<Stdin> for Stdio {
fn from(value: Stdin) -> Self {
Self {
inner: StdioInner::Desc(0)
}
}
}
impl From<Stdout> for Stdio {
fn from(value: Stdout) -> Self {
Self {
inner: StdioInner::Desc(1)
}
}
}
impl From<Stderr> for Stdio {
fn from(value: Stderr) -> Self {
Self {
inner: StdioInner::Desc(2)
}
}
}
#[derive(Debug)]
enum StdioInner {
Inherit,
Desc(FileDescriptor)
}
#[derive(Debug)]
pub struct Child {
thread: ThreadID
}
impl Child {
pub fn kill(&mut self) -> Result<(), io::Error> {
todo!()
}
pub fn id(&self) -> u32 {
self.thread as u32
}
pub fn wait(&mut self) -> Result<ExitStatus, io::Error> {
Ok(ExitStatus(syscall_join(self.thread)))
}
}
#[derive(Clone, Debug, Default, PartialEq, Copy, Eq)]
pub struct ExitStatus(isize);
impl ExitStatus {
pub fn success(&self) -> bool {
self.0 == 0
}
pub fn code(&self) -> Option<i32> {
if self.0 > 128 {
None
} else {
Some(self.0 as i32)
}
}
}