irid-std 0.1.0

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