irid-std 0.1.1

A replacement for std when running without a filesystem on the irid kernel
Documentation
use alloc::{borrow::ToOwned, string::String, vec::Vec};
use irid_syscall::{SystemError, ffi::io::{FileInfo, FilePermissions}, io::{FileDescriptor, OpenFlags, close, file_info, open, read}};

use crate::{io::{Error, Read}, path::{Path, PathBuf}};

use irid_syscall::ffi::io::FileType as NativeFileType;

mod dir;
mod file;

pub use dir::DirEntry;
pub use file::{File, OpenOptions, TryLockError};

pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
    todo!()
}

pub fn copy(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> Result<u64, SystemError> {
    todo!()
}

pub fn create_dir(path: impl AsRef<Path>) -> Result<(), Error> {
    irid_syscall::io::create_directory(path.as_ref().as_str())?;

    Ok(())
}

pub fn create_dir_all(path: impl AsRef<Path>) -> Result<(), Error> {
    let path = path.as_ref();

    for path in path.ancestors().collect::<Vec<_>>().into_iter().rev().chain(Some(path.as_ref())) {
        match create_dir(path) {
            Ok(_) => (),
            // We have hit the first parent directory to exist and we can safely exit the loop.
            Err(Error::AlreadyExists) => (),
            Err(err) => Err(err)?,
        }
    }

    Ok(())
}

pub fn exists(path: impl AsRef<Path>) -> Result<bool, Error> {
    match irid_syscall::io::open(path.as_ref().as_str(), OpenFlags::empty()) {
        Ok(file) => {
            irid_syscall::io::close(file)?;

            Ok(true)
        }
        Err(SystemError(2)) => Ok(false),
        Err(err) => Err(err)?
    }
}

pub fn hard_link(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<(), Error> {
    todo!()
}

pub fn soft_link(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<(), Error> {
    let path = canonicalize(original)?;

    let file = irid_syscall::io::open(link.as_ref().as_str(), OpenFlags::WRITE | OpenFlags::SYMBOLIC_LINK | OpenFlags::CREATE)?;

    let result = irid_syscall::io::write(file, path.as_str().as_bytes());

    irid_syscall::io::close(file)?;

    Ok(result.map(|_| ())?)
}

pub fn metadata(path: impl AsRef<Path>) -> Result<Metadata, Error> {
    Ok(Metadata { info: file_info(path.as_ref().as_str(), true)? })
}

pub fn symlink_metadata(path: impl AsRef<Path>) -> Result<Metadata, Error> {
    Ok(Metadata { info: file_info(path.as_ref().as_str(), false)? })
}

pub struct Metadata {
    info: FileInfo,
}

impl Metadata {
    pub fn file_type(&self) -> FileType {
        FileType { inner: self.info.file_type  }
    }

    pub fn is_dir(&self) -> bool {
        self.file_type().is_dir()
    }
    
    pub fn is_file(&self) -> bool {
        self.file_type().is_file()
    }
    
    pub fn is_symlink(&self) -> bool {
        self.file_type().is_symlink()
    }

    pub fn length(&self) -> u64 {
        todo!()
    }

    pub fn permissions(&self) -> Permissions {
        Permissions { permissions: self.info.access.permissions.clone() }
    }
}

pub fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<(), Error> {
    Ok(irid_syscall::io::move_file(from.as_ref().as_str(), to.as_ref().as_str())?)
}

pub fn read_link(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
    todo!()
}

pub fn read_dir(path: impl AsRef<Path>) -> Result<impl Iterator<Item = Result<DirEntry, Error>>, Error> {
    let mut file = AutoFile::open(&path, OpenFlags::REQUIRE_DIRECTORY | OpenFlags::READ)?;

    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)?;
    let text = unsafe { String::from_utf8_unchecked(buffer) };

    Ok(text.split("\n").filter(|s| !s.is_empty()).map(|name| {
        let mut path = path.as_ref().to_owned();

        path.push(Path::new(name));

        Ok(DirEntry {
            path
        })
    }).collect::<Vec<_>>().into_iter())
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FileType {
    inner: NativeFileType
}

impl FileType {
    pub fn is_dir(&self) -> bool {
        self.inner == NativeFileType::Directory
    }
    
    pub fn is_file(&self) -> bool {
        !self.is_dir() && !self.is_symlink()
    }
    
    pub fn is_symlink(&self) -> bool {
        self.inner == NativeFileType::SymbolicLink
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Permissions {
    permissions: FilePermissions
}

impl Permissions {
    pub fn readonly(&self) -> bool {
        !(self.permissions.contains(FilePermissions::WRITE_USER)
            | self.permissions.contains(FilePermissions::WRITE_GROUP)
            | self.permissions.contains(FilePermissions::WRITE_OTHER))
    }

    pub fn set_readonly(&mut self, readonly: bool) {
        if readonly {
            self.permissions -= FilePermissions::WRITE_USER | FilePermissions::WRITE_OTHER | FilePermissions::WRITE_GROUP;
        } else {
            self.permissions |= FilePermissions::WRITE_USER | FilePermissions::WRITE_OTHER | FilePermissions::WRITE_GROUP;
        }
    }
}

struct AutoFile {
    desc: FileDescriptor
}

impl AutoFile {
    pub fn open(path: impl AsRef<Path>, flags: OpenFlags) -> Result<Self, Error> {
        Ok(Self {
            desc: open(path.as_ref().as_str(), flags)?
        })
    }
}

impl Read for AutoFile {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
        Ok(read(self.desc, buf)?)
    }
}

impl Drop for AutoFile {
    fn drop(&mut self) {
        close(self.desc).expect("failed to close file");
    }
}