irid-std 0.3.1

A replacement for std when running without a filesystem on the irid kernel
Documentation
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();

        // Ensure vars1 lives this long.
        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)
        }
    }
}