use alloc::{borrow::ToOwned, boxed::Box, collections::btree_map::BTreeMap, string::{String, ToString}, vec::Vec, format};
use irid_syscall::{SystemError, ffi::{StringSlice, thread::{ThreadID, syscall_join, syscall_spawn_process}}, io::{FileDescriptor, OpenFlags, open}};
use crate::{env::{current_dir, var, vars}, ffi::OsStr, fs::File, io::{self, Stderr, Stdin, Stdout, convert_error}, path::{Path, PathBuf}};
#[derive(Debug)]
pub struct Command {
program: Box<str>,
args: Vec<Box<str>>,
current_dir: PathBuf,
vars: BTreeMap<String, String>,
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: current_dir().unwrap_or_else(|_| Path::new("/").to_path_buf()),
stdin: Stdio { inner: StdioInner::Inherit },
stdout: Stdio { inner: StdioInner::Inherit },
stderr: Stdio { inner: StdioInner::Inherit },
vars: vars().collect()
}
}
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.env("PWD", dir.as_ref().display().to_string());
self.current_dir = dir.as_ref().to_path_buf();
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 env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
self.vars.insert(key.as_ref().as_str().to_owned(), value.as_ref().as_str().to_owned());
self
}
pub fn envs<K: AsRef<OsStr>, V: AsRef<OsStr>>(&mut self, vars: impl IntoIterator<Item = (K, V)>) -> &mut Self {
for (key, value) in vars {
self.vars.insert(key.as_ref().as_str().to_owned(), value.as_ref().as_str().to_owned());
}
self
}
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
self.vars.remove(&key.as_ref().as_str().to_owned());
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.vars.clear();
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 vars1: Vec<String> = self.vars.iter()
.map(|(key, value)| format!("{key}={value}"))
.collect();
let vars: Vec<StringSlice> = vars1.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 current_dir = self.current_dir.display().to_string();
let path = var("PATH").unwrap_or("".to_string());
let result: Result<ThreadID, SystemError> = syscall_spawn_process(
self.program.as_ptr(),
self.program.len(),
args.as_ptr(), args.len(),
files.as_ptr(), files.len(),
vars.as_ptr(), vars.len(),
current_dir.as_ptr(), current_dir.len(),
path.as_ptr(), path.len()).into();
drop(vars1);
let thread = result.map_err(convert_error)?;
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)
}
}
}