diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index 76374402..8ea2da6d 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -177,6 +177,8 @@ pub mod solid; pub mod trusty; #[cfg(target_os = "uefi")] pub mod uefi; +#[cfg(target_os = "vibix")] +pub mod vibix; #[cfg(target_os = "vita")] pub mod vita; #[cfg(target_os = "vxworks")] diff --git a/library/std/src/os/vibix/ffi.rs b/library/std/src/os/vibix/ffi.rs new file mode 100644 index 00000000..76f98185 --- /dev/null +++ b/library/std/src/os/vibix/ffi.rs @@ -0,0 +1,9 @@ +//! vibix-specific extension to the primitives in the `std::ffi` module. + +#![stable(feature = "rust1", since = "1.0.0")] + +#[path = "../../os/unix/ffi/os_str.rs"] +mod os_str; + +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::os_str::{OsStrExt, OsStringExt}; diff --git a/library/std/src/os/vibix/mod.rs b/library/std/src/os/vibix/mod.rs new file mode 100644 index 00000000..a1ac3a2c --- /dev/null +++ b/library/std/src/os/vibix/mod.rs @@ -0,0 +1,5 @@ +//! vibix-specific definitions. + +#![stable(feature = "rust1", since = "1.0.0")] + +pub mod ffi; diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0c297c57..0e0f09f2 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -41,6 +41,10 @@ cfg_select! { mod uefi; use uefi as imp; } + target_os = "vibix" => { + mod vibix; + use vibix as imp; + } target_os = "vexos" => { mod vexos; use vexos as imp; diff --git a/library/std/src/sys/fs/vibix.rs b/library/std/src/sys/fs/vibix.rs new file mode 100644 index 00000000..924002a5 --- /dev/null +++ b/library/std/src/sys/fs/vibix.rs @@ -0,0 +1,761 @@ +//! Filesystem implementation for vibix. +//! +//! Backed by vibix_abi syscall wrappers around openat, read, write, fstat, +//! getdents64, lseek, close, rename, unlink, mkdir, rmdir, chmod, chown, +//! utimensat. + +use crate::ffi::{CStr, OsStr, OsString}; +use crate::fmt; +use crate::fs::TryLockError; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::os::vibix::ffi::{OsStrExt, OsStringExt}; +use crate::path::{Path, PathBuf}; +use crate::sync::Arc; +pub use crate::sys::fs::common::Dir; +use crate::sys::helpers::run_path_with_cstr; +use crate::sys::time::SystemTime; +use crate::sys::unsupported; + +use vibix_abi::fs::{ + self as vfs, AT_FDCWD, AT_SYMLINK_NOFOLLOW, DT_DIR, DT_LNK, DT_REG, DT_UNKNOWN, O_APPEND, + O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, SEEK_CUR, + SEEK_END, SEEK_SET, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, UTIME_NOW, UTIME_OMIT, +}; + +/// Convert a negative syscall return to an io::Error. +fn cvt(ret: i64) -> io::Result { + if ret < 0 { Err(io::Error::from_raw_os_error(-ret as i32)) } else { Ok(ret) } +} + +pub struct File { + fd: i32, +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { + vfs::close(self.fd); + } + } +} + +#[derive(Clone)] +pub struct FileAttr { + stat: vfs::Stat, +} + +struct InnerReadDir { + root: PathBuf, + buf: Vec, +} + +pub struct ReadDir { + inner: Arc, + pos: usize, +} + +pub struct DirEntry { + root: PathBuf, + ino: u64, + type_: u8, + name: OsString, +} + +#[derive(Clone, Debug)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + mode: u32, +} + +#[derive(Copy, Clone, Debug)] +pub struct FileTimes { + accessed: Option, + modified: Option, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + mode: u32, +} + +#[derive(Copy, Clone, Eq, Debug)] +pub struct FileType { + mode: u32, +} + +impl PartialEq for FileType { + fn eq(&self, other: &Self) -> bool { + self.mode == other.mode + } +} + +impl core::hash::Hash for FileType { + fn hash(&self, state: &mut H) { + self.mode.hash(state); + } +} + +#[derive(Debug)] +pub struct DirBuilder { + mode: u32, +} + +// --- FileAttr --- + +impl FileAttr { + pub fn size(&self) -> u64 { + self.stat.st_size as u64 + } + + pub fn perm(&self) -> FilePermissions { + FilePermissions { mode: self.stat.st_mode & 0o7777 } + } + + pub fn file_type(&self) -> FileType { + FileType { mode: self.stat.st_mode & S_IFMT } + } + + pub fn modified(&self) -> io::Result { + SystemTime::new(self.stat.st_mtime, self.stat.st_mtime_nsec) + } + + pub fn accessed(&self) -> io::Result { + SystemTime::new(self.stat.st_atime, self.stat.st_atime_nsec) + } + + pub fn created(&self) -> io::Result { + SystemTime::new(self.stat.st_ctime, self.stat.st_ctime_nsec) + } +} + +// --- FilePermissions --- + +impl FilePermissions { + pub fn readonly(&self) -> bool { + self.mode & 0o222 == 0 + } + + pub fn set_readonly(&mut self, readonly: bool) { + if readonly { + self.mode &= !0o222; + } else { + self.mode |= 0o222; + } + } + + #[allow(dead_code)] + pub fn mode(&self) -> u32 { + self.mode + } +} + +// --- FileTimes --- + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.accessed = Some(t); + } + pub fn set_modified(&mut self, t: SystemTime) { + self.modified = Some(t); + } +} + +impl Default for FileTimes { + fn default() -> Self { + FileTimes { accessed: None, modified: None } + } +} + +// --- FileType --- + +impl FileType { + pub fn is_dir(&self) -> bool { + self.mode == S_IFDIR + } + pub fn is_file(&self) -> bool { + self.mode == S_IFREG + } + pub fn is_symlink(&self) -> bool { + self.mode == S_IFLNK + } +} + +// --- ReadDir --- + +impl fmt::Debug for ReadDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.inner.root, f) + } +} + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + loop { + if self.pos >= self.inner.buf.len() { + return None; + } + + let buf = &self.inner.buf[self.pos..]; + let name_offset = core::mem::offset_of!(vfs::Dirent64, d_name); + if buf.len() < name_offset + 1 { + return None; + } + + // Use read_unaligned to avoid UB from misaligned reference to + // Dirent64 (the u8 buffer may not be 8-byte aligned at self.pos). + let dirent = + unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const vfs::Dirent64) }; + let reclen = dirent.d_reclen as usize; + if reclen == 0 || reclen <= name_offset || self.pos + reclen > self.inner.buf.len() { + return None; + } + + self.pos += reclen; + + // The name starts after the fixed fields of dirent64. + let name_bytes = &buf[name_offset..reclen]; + // Find the null terminator. + let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(name_bytes.len()); + let name = OsString::from_vec(name_bytes[..name_len].to_vec()); + + // Skip "." and ".." + if name == "." || name == ".." { + continue; + } + + return Some(Ok(DirEntry { + root: self.inner.root.clone(), + ino: dirent.d_ino, + type_: dirent.d_type, + name, + })); + } + } +} + +// --- DirEntry --- + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.root.join(self.file_name_os_str()) + } + + pub fn file_name(&self) -> OsString { + self.file_name_os_str().to_os_string() + } + + pub fn metadata(&self) -> io::Result { + lstat(&self.path()) + } + + pub fn file_type(&self) -> io::Result { + match self.type_ { + DT_DIR => Ok(FileType { mode: S_IFDIR }), + DT_REG => Ok(FileType { mode: S_IFREG }), + DT_LNK => Ok(FileType { mode: S_IFLNK }), + DT_UNKNOWN => { + // Fall back to stat. + self.metadata().map(|m| m.file_type()) + } + _ => Ok(FileType { mode: 0 }), + } + } + + pub fn file_name_os_str(&self) -> &OsStr { + self.name.as_os_str() + } +} + +// --- OpenOptions --- + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + mode: 0o666, + } + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + pub fn write(&mut self, write: bool) { + self.write = write; + } + pub fn append(&mut self, append: bool) { + self.append = append; + } + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + pub fn create(&mut self, create: bool) { + self.create = create; + } + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } + + fn get_access_mode(&self) -> io::Result { + match (self.read, self.write, self.append) { + (true, false, false) => Ok(O_RDONLY), + (false, true, false) => Ok(O_WRONLY), + (true, true, false) => Ok(O_RDWR), + (false, _, true) => Ok(O_WRONLY | O_APPEND), + (true, _, true) => Ok(O_RDWR | O_APPEND), + (false, false, false) => { + Err(io::const_error!(io::ErrorKind::InvalidInput, "invalid access mode")) + } + } + } + + fn get_creation_mode(&self) -> io::Result { + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "invalid creation mode" + )); + } + } + (_, true) => { + if self.truncate && !self.create_new { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "invalid creation mode" + )); + } + } + } + + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => 0, + (true, false, false) => O_CREAT, + (false, true, false) => O_TRUNC, + (true, true, false) => O_CREAT | O_TRUNC, + (_, _, true) => O_CREAT | O_EXCL, + }) + } +} + +// --- File --- + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + run_path_with_cstr(path, &|path| File::open_c(path, opts)) + } + + fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result { + let flags = opts.get_access_mode()? | opts.get_creation_mode()? | O_CLOEXEC; + let mode = if flags & O_CREAT != 0 { opts.mode } else { 0 }; + let fd = cvt(unsafe { vfs::openat(AT_FDCWD, path.as_ptr() as *const u8, flags, mode) })?; + Ok(File { fd: fd as i32 }) + } + + pub fn file_attr(&self) -> io::Result { + let mut stat = unsafe { core::mem::zeroed::() }; + cvt(unsafe { vfs::fstat(self.fd, &mut stat) })?; + Ok(FileAttr { stat }) + } + + pub fn fsync(&self) -> io::Result<()> { + cvt(unsafe { vfs::fsync(self.fd) })?; + Ok(()) + } + + pub fn datasync(&self) -> io::Result<()> { + // vibix doesn't have fdatasync yet, fall back to fsync + self.fsync() + } + + pub fn lock(&self) -> io::Result<()> { + unsupported() + } + + pub fn lock_shared(&self) -> io::Result<()> { + unsupported() + } + + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(io::const_error!( + io::ErrorKind::Unsupported, + "file locking not supported on vibix yet" + ))) + } + + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(io::const_error!( + io::ErrorKind::Unsupported, + "file locking not supported on vibix yet" + ))) + } + + pub fn unlock(&self) -> io::Result<()> { + unsupported() + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + if size > i64::MAX as u64 { + return Err(io::const_error!(io::ErrorKind::InvalidInput, "file size is too large")); + } + cvt(unsafe { vfs::ftruncate(self.fd, size as i64) })?; + Ok(()) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + let ret = cvt(unsafe { vfs::read(self.fd, buf.as_mut_ptr(), buf.len()) })?; + Ok(ret as usize) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + // Read into the first non-empty buffer (no kernel readv yet). + let buf = bufs.iter_mut().find(|b| !b.is_empty()); + match buf { + Some(buf) => self.read(buf), + None => Ok(0), + } + } + + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { + let ret = cvt(unsafe { + vfs::read(self.fd, cursor.as_mut().as_mut_ptr() as *mut u8, cursor.capacity()) + })?; + // SAFETY: Exactly `ret` bytes have been filled. + unsafe { cursor.advance(ret as usize) }; + Ok(()) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + let ret = cvt(unsafe { vfs::write(self.fd, buf.as_ptr(), buf.len()) })?; + Ok(ret as usize) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + // Write the first non-empty buffer. + let buf = bufs.iter().find(|b| !b.is_empty()); + match buf { + Some(buf) => self.write(buf), + None => Ok(0), + } + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + #[inline] + pub fn flush(&self) -> io::Result<()> { + Ok(()) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result { + let (whence, offset) = match pos { + SeekFrom::Start(off) => { + if off > i64::MAX as u64 { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "seek position is too large" + )); + } + (SEEK_SET, off as i64) + } + SeekFrom::End(off) => (SEEK_END, off), + SeekFrom::Current(off) => (SEEK_CUR, off), + }; + let ret = cvt(unsafe { vfs::lseek(self.fd, offset, whence) })?; + Ok(ret as u64) + } + + pub fn size(&self) -> Option> { + Some(self.file_attr().map(|a| a.size())) + } + + pub fn tell(&self) -> io::Result { + self.seek(SeekFrom::Current(0)) + } + + pub fn duplicate(&self) -> io::Result { + let new_fd = cvt(unsafe { vfs::fcntl(self.fd, vfs::F_DUPFD_CLOEXEC, 0) })?; + Ok(File { fd: new_fd as i32 }) + } + + pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { + cvt(unsafe { vfs::fchmod(self.fd, perm.mode) })?; + Ok(()) + } + + pub fn set_times(&self, times: FileTimes) -> io::Result<()> { + let mut ts = [ + vfs::Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }, + vfs::Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }, + ]; + if let Some(accessed) = times.accessed { + let (sec, nsec) = accessed.to_timespec(); + ts[0] = vfs::Timespec { tv_sec: sec, tv_nsec: nsec }; + } + if let Some(modified) = times.modified { + let (sec, nsec) = modified.to_timespec(); + ts[1] = vfs::Timespec { tv_sec: sec, tv_nsec: nsec }; + } + // Use utimensat with empty path + AT_FDCWD trick won't work; + // we need the /proc/self/fd/N path or a direct futimens. + // For now, use the fd-based approach via utimensat with null path. + // Actually, Linux allows utimensat(fd, NULL, ...) to operate on the fd. + cvt(unsafe { + vfs::utimensat(self.fd, core::ptr::null(), ts.as_ptr(), 0) + })?; + Ok(()) + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("File").field("fd", &self.fd).finish() + } +} + +// --- DirBuilder --- + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder { mode: 0o777 } + } + + pub fn mkdir(&self, path: &Path) -> io::Result<()> { + run_path_with_cstr(path, &|path| { + cvt(unsafe { vfs::mkdir(path.as_ptr() as *const u8, self.mode) })?; + Ok(()) + }) + } + + #[allow(dead_code)] + pub fn set_mode(&mut self, mode: u32) { + self.mode = mode; + } +} + +// --- Free functions --- + +pub fn readdir(path: &Path) -> io::Result { + run_path_with_cstr(path, &|p| { + let fd = cvt(unsafe { + vfs::openat(AT_FDCWD, p.as_ptr() as *const u8, O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0) + })? as i32; + + let result = (|| { + let mut buf = Vec::new(); + let mut tmp = vec![0u8; 4096]; + loop { + let n = cvt(unsafe { vfs::getdents64(fd, tmp.as_mut_ptr(), tmp.len()) })?; + if n == 0 { + break; + } + buf.extend_from_slice(&tmp[..n as usize]); + } + Ok(buf) + })(); + + unsafe { + vfs::close(fd); + } + + let buf = result?; + let root = path.to_path_buf(); + Ok(ReadDir { inner: Arc::new(InnerReadDir { root, buf }), pos: 0 }) + }) +} + +pub fn unlink(path: &Path) -> io::Result<()> { + run_path_with_cstr(path, &|p| { + cvt(unsafe { vfs::unlink(p.as_ptr() as *const u8) })?; + Ok(()) + }) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + run_path_with_cstr(old, &|old| { + run_path_with_cstr(new, &|new| { + cvt(unsafe { vfs::rename(old.as_ptr() as *const u8, new.as_ptr() as *const u8) })?; + Ok(()) + }) + }) +} + +pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { + run_path_with_cstr(path, &|p| { + cvt(unsafe { vfs::chmod(p.as_ptr() as *const u8, perm.mode) })?; + Ok(()) + }) +} + +pub fn set_times(path: &Path, times: FileTimes) -> io::Result<()> { + run_path_with_cstr(path, &|p| { + let ts = times_to_timespec(×); + cvt(unsafe { + vfs::utimensat(AT_FDCWD, p.as_ptr() as *const u8, ts.as_ptr(), 0) + })?; + Ok(()) + }) +} + +pub fn set_times_nofollow(path: &Path, times: FileTimes) -> io::Result<()> { + run_path_with_cstr(path, &|p| { + let ts = times_to_timespec(×); + cvt(unsafe { + vfs::utimensat( + AT_FDCWD, + p.as_ptr() as *const u8, + ts.as_ptr(), + AT_SYMLINK_NOFOLLOW, + ) + })?; + Ok(()) + }) +} + +pub fn rmdir(path: &Path) -> io::Result<()> { + run_path_with_cstr(path, &|p| { + cvt(unsafe { vfs::rmdir(p.as_ptr() as *const u8) })?; + Ok(()) + }) +} + +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + crate::sys::fs::common::remove_dir_all(path) +} + +pub fn readlink(path: &Path) -> io::Result { + run_path_with_cstr(path, &|p| { + let mut buf = vec![0u8; 256]; + loop { + let n = + cvt(unsafe { vfs::readlink(p.as_ptr() as *const u8, buf.as_mut_ptr(), buf.len()) })? + as usize; + if n < buf.len() { + buf.truncate(n); + return Ok(PathBuf::from(OsString::from_vec(buf))); + } + // Buffer was too small, double and retry. + buf.resize(buf.len() * 2, 0); + } + }) +} + +pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { + run_path_with_cstr(original, &|original| { + run_path_with_cstr(link, &|link| { + cvt(unsafe { + vfs::symlink(original.as_ptr() as *const u8, link.as_ptr() as *const u8) + })?; + Ok(()) + }) + }) +} + +pub fn link(src: &Path, dst: &Path) -> io::Result<()> { + run_path_with_cstr(src, &|src| { + run_path_with_cstr(dst, &|dst| { + cvt(unsafe { vfs::link(src.as_ptr() as *const u8, dst.as_ptr() as *const u8) })?; + Ok(()) + }) + }) +} + +pub fn stat(path: &Path) -> io::Result { + run_path_with_cstr(path, &|p| { + let mut st = unsafe { core::mem::zeroed::() }; + cvt(unsafe { vfs::stat(p.as_ptr() as *const u8, &mut st) })?; + Ok(FileAttr { stat: st }) + }) +} + +pub fn lstat(path: &Path) -> io::Result { + run_path_with_cstr(path, &|p| { + let mut st = unsafe { core::mem::zeroed::() }; + cvt(unsafe { vfs::lstat(p.as_ptr() as *const u8, &mut st) })?; + Ok(FileAttr { stat: st }) + }) +} + +pub fn canonicalize(path: &Path) -> io::Result { + // vibix does not have realpath; do a simple absolute-path resolution. + let mut result = if path.is_absolute() { + PathBuf::new() + } else { + crate::env::current_dir()? + }; + for component in path.components() { + match component { + crate::path::Component::RootDir => { + result.push("/"); + } + crate::path::Component::ParentDir => { + result.pop(); + } + crate::path::Component::CurDir => {} + crate::path::Component::Normal(c) => { + result.push(c); + // Check that the path component exists and resolve symlinks. + let meta = lstat(&result)?; + if meta.file_type().is_symlink() { + let target = readlink(&result)?; + result.pop(); + if target.is_absolute() { + result = target; + } else { + result.push(target); + } + } + } + crate::path::Component::Prefix(_) => unreachable!(), + } + } + Ok(result) +} + +pub fn copy(from: &Path, to: &Path) -> io::Result { + crate::sys::fs::common::copy(from, to) +} + +pub fn exists(path: &Path) -> io::Result { + match stat(path) { + Ok(_) => Ok(true), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(e), + } +} + +// --- Helpers --- + +fn times_to_timespec(times: &FileTimes) -> [vfs::Timespec; 2] { + let mut ts = [ + vfs::Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }, + vfs::Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }, + ]; + if let Some(accessed) = times.accessed { + let (sec, nsec) = accessed.to_timespec(); + ts[0] = vfs::Timespec { tv_sec: sec, tv_nsec: nsec }; + } + if let Some(modified) = times.modified { + let (sec, nsec) = modified.to_timespec(); + ts[1] = vfs::Timespec { tv_sec: sec, tv_nsec: nsec }; + } + ts +} diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index 46f4ebf6..b18216c9 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -15,6 +15,10 @@ cfg_select! { mod motor; use motor as imp; } + target_os = "vibix" => { + mod vibix; + use vibix as imp; + } _ => { mod unsupported; use unsupported as imp; diff --git a/library/std/src/sys/process/vibix.rs b/library/std/src/sys/process/vibix.rs new file mode 100644 index 00000000..44b25a4c --- /dev/null +++ b/library/std/src/sys/process/vibix.rs @@ -0,0 +1,405 @@ +//! Process implementation for vibix. +//! +//! Uses fork+execve for Command::spawn() and wait4 for Child::wait(). + +use super::env::{CommandEnv, CommandEnvs}; +pub use crate::ffi::OsString as EnvKey; +use crate::ffi::{CString, OsStr, OsString}; +use crate::num::NonZero; +use crate::os::vibix::ffi::OsStrExt; +use crate::path::Path; +use crate::process::StdioPipes; +use crate::sys::fs::File; +use crate::sys::pipe::Pipe; +use crate::sys::unsupported; +use crate::{fmt, io}; + +use vibix_abi::process as vproc; + +/// Convert a negative syscall return to an io::Error. +fn cvt(ret: i64) -> io::Result { + if ret < 0 { Err(io::Error::from_raw_os_error(-ret as i32)) } else { Ok(ret) } +} + +//////////////////////////////////////////////////////////////////////////////// +// Command +//////////////////////////////////////////////////////////////////////////////// + +pub struct Command { + program: OsString, + args: Vec, + env: CommandEnv, + cwd: Option, + stdin: Option, + stdout: Option, + stderr: Option, +} + +#[derive(Debug)] +pub enum Stdio { + Inherit, + Null, + MakePipe, + ParentStdout, + ParentStderr, + #[allow(dead_code)] + InheritFile(File), +} + +impl Command { + pub fn new(program: &OsStr) -> Command { + Command { + program: program.to_owned(), + args: vec![program.to_owned()], + env: Default::default(), + cwd: None, + stdin: None, + stdout: None, + stderr: None, + } + } + + pub fn arg(&mut self, arg: &OsStr) { + self.args.push(arg.to_owned()); + } + + pub fn env_mut(&mut self) -> &mut CommandEnv { + &mut self.env + } + + pub fn cwd(&mut self, dir: &OsStr) { + self.cwd = Some(dir.to_owned()); + } + + pub fn stdin(&mut self, stdin: Stdio) { + self.stdin = Some(stdin); + } + + pub fn stdout(&mut self, stdout: Stdio) { + self.stdout = Some(stdout); + } + + pub fn stderr(&mut self, stderr: Stdio) { + self.stderr = Some(stderr); + } + + pub fn get_program(&self) -> &OsStr { + &self.program + } + + pub fn get_args(&self) -> CommandArgs<'_> { + let mut iter = self.args.iter(); + iter.next(); + CommandArgs { iter } + } + + pub fn get_envs(&self) -> CommandEnvs<'_> { + self.env.iter() + } + + pub fn get_env_clear(&self) -> bool { + self.env.does_clear() + } + + pub fn get_current_dir(&self) -> Option<&Path> { + self.cwd.as_ref().map(|cs| Path::new(cs)) + } + + pub fn spawn( + &mut self, + _default: Stdio, + _needs_stdin: bool, + ) -> io::Result<(Process, StdioPipes)> { + // Build null-terminated argument list. + let prog_c = CString::new(self.program.as_bytes()) + .map_err(|_| io::const_error!(io::ErrorKind::InvalidInput, "nul in program name"))?; + + let args_c: Vec = self + .args + .iter() + .map(|a| { + CString::new(a.as_bytes()) + .map_err(|_| io::const_error!(io::ErrorKind::InvalidInput, "nul in argument")) + }) + .collect::>>()?; + + let mut argv_ptrs: Vec<*const u8> = + args_c.iter().map(|a| a.as_ptr() as *const u8).collect(); + argv_ptrs.push(core::ptr::null()); + + // Build environment (inherit current env for now, with modifications). + // For simplicity, pass a minimal envp with just a NULL terminator. + // Full env support would require iterating current env + applying changes. + let envp: [*const u8; 1] = [core::ptr::null()]; + + let pid = cvt(unsafe { vproc::fork() })?; + + if pid == 0 { + // Child process. + // Change directory if requested. + if let Some(ref dir) = self.cwd { + if let Ok(dir_c) = CString::new(dir.as_bytes()) { + let ret = unsafe { vibix_abi::fs::chdir(dir_c.as_ptr() as *const u8) }; + if ret < 0 { + unsafe { vproc::exit(127) }; + } + } else { + // Path contains NUL byte -- cannot chdir. + unsafe { vproc::exit(127) }; + } + } + + // exec + unsafe { + vproc::execve(prog_c.as_ptr() as *const u8, argv_ptrs.as_ptr(), envp.as_ptr()); + } + // If execve returns, it failed. Exit with error code. + unsafe { vproc::exit(127) }; + } + + // Parent process. + let pipes = StdioPipes { stdin: None, stdout: None, stderr: None }; + Ok((Process { pid: pid as u32 }, pipes)) + } +} + +pub fn output(cmd: &mut Command) -> io::Result<(ExitStatus, Vec, Vec)> { + let (mut process, _pipes) = cmd.spawn(Stdio::Inherit, false)?; + let status = process.wait()?; + Ok((status, Vec::new(), Vec::new())) +} + +impl From for Stdio { + fn from(pipe: ChildPipe) -> Stdio { + pipe.diverge() + } +} + +impl From for Stdio { + fn from(_: io::Stdout) -> Stdio { + Stdio::ParentStdout + } +} + +impl From for Stdio { + fn from(_: io::Stderr) -> Stdio { + Stdio::ParentStderr + } +} + +impl From for Stdio { + fn from(file: File) -> Stdio { + Stdio::InheritFile(file) + } +} + +impl fmt::Debug for Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + let mut debug_command = f.debug_struct("Command"); + debug_command.field("program", &self.program).field("args", &self.args); + if !self.env.is_unchanged() { + debug_command.field("env", &self.env); + } + if self.cwd.is_some() { + debug_command.field("cwd", &self.cwd); + } + debug_command.finish() + } else { + if let Some(ref cwd) = self.cwd { + write!(f, "cd {cwd:?} && ")?; + } + if self.env.does_clear() { + write!(f, "env -i ")?; + } else { + let mut any_removed = false; + for (key, value_opt) in self.get_envs() { + if value_opt.is_none() { + if !any_removed { + write!(f, "env ")?; + any_removed = true; + } + write!(f, "-u {} ", key.to_string_lossy())?; + } + } + } + for (key, value_opt) in self.get_envs() { + if let Some(value) = value_opt { + write!(f, "{}={value:?} ", key.to_string_lossy())?; + } + } + if self.program != self.args[0] { + write!(f, "[{:?}] ", self.program)?; + } + write!(f, "{:?}", self.args[0])?; + for arg in &self.args[1..] { + write!(f, " {:?}", arg)?; + } + Ok(()) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ExitStatus +//////////////////////////////////////////////////////////////////////////////// + +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +pub struct ExitStatus(i32); + +impl ExitStatus { + pub fn exit_ok(&self) -> Result<(), ExitStatusError> { + if self.0 == 0 { Ok(()) } else { Err(ExitStatusError(self.0)) } + } + + pub fn code(&self) -> Option { + // If exited normally (bits 0-6 of wait status == 0), the exit code is bits 8-15. + if self.0 & 0x7f == 0 { Some((self.0 >> 8) & 0xff) } else { None } + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(code) = self.code() { + write!(f, "exit status: {code}") + } else { + write!(f, "signal: {}", self.0 & 0x7f) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ExitStatusError +//////////////////////////////////////////////////////////////////////////////// + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct ExitStatusError(i32); + +impl Into for ExitStatusError { + fn into(self) -> ExitStatus { + ExitStatus(self.0) + } +} + +impl ExitStatusError { + pub fn code(self) -> Option> { + let code = (self.0 >> 8) & 0xff; + NonZero::new(code) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ExitCode +//////////////////////////////////////////////////////////////////////////////// + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitCode(u8); + +impl ExitCode { + pub const SUCCESS: ExitCode = ExitCode(0); + pub const FAILURE: ExitCode = ExitCode(1); + + pub fn as_i32(&self) -> i32 { + self.0 as i32 + } +} + +impl From for ExitCode { + fn from(code: u8) -> Self { + Self(code) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Process +//////////////////////////////////////////////////////////////////////////////// + +pub struct Process { + pid: u32, +} + +impl Process { + pub fn id(&self) -> u32 { + self.pid + } + + pub fn kill(&mut self) -> io::Result<()> { + cvt(unsafe { vproc::kill(self.pid as i32, vproc::SIGKILL) })?; + Ok(()) + } + + pub fn wait(&mut self) -> io::Result { + let mut status: i32 = 0; + loop { + let ret = cvt(unsafe { vproc::wait4(self.pid as i32, &mut status, 0, 0) })?; + if ret == self.pid as i64 { + return Ok(ExitStatus(status)); + } + } + } + + pub fn try_wait(&mut self) -> io::Result> { + let mut status: i32 = 0; + let ret = cvt(unsafe { vproc::wait4(self.pid as i32, &mut status, vproc::WNOHANG, 0) })?; + if ret == 0 { + Ok(None) + } else if ret == self.pid as i64 { + Ok(Some(ExitStatus(status))) + } else { + // Unexpected pid returned; treat as not yet exited. + Ok(None) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// CommandArgs +//////////////////////////////////////////////////////////////////////////////// + +pub struct CommandArgs<'a> { + iter: crate::slice::Iter<'a, OsString>, +} + +impl<'a> Iterator for CommandArgs<'a> { + type Item = &'a OsStr; + fn next(&mut self) -> Option<&'a OsStr> { + self.iter.next().map(|os| &**os) + } + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a> ExactSizeIterator for CommandArgs<'a> { + fn len(&self) -> usize { + self.iter.len() + } + fn is_empty(&self) -> bool { + self.iter.is_empty() + } +} + +impl<'a> fmt::Debug for CommandArgs<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter.clone()).finish() + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Pipe / getpid +//////////////////////////////////////////////////////////////////////////////// + +pub type ChildPipe = Pipe; + +pub fn read_output( + out: ChildPipe, + _stdout: &mut Vec, + _err: ChildPipe, + _stderr: &mut Vec, +) -> io::Result<()> { + match out.diverge() {} +} + +pub fn getpid() -> u32 { + unsafe { vproc::getpid() as u32 } +} diff --git a/library/std/src/sys/time/mod.rs b/library/std/src/sys/time/mod.rs index 6cd1850e..84468b66 100644 --- a/library/std/src/sys/time/mod.rs +++ b/library/std/src/sys/time/mod.rs @@ -18,6 +18,10 @@ cfg_select! { mod uefi; use uefi as imp; } + target_os = "vibix" => { + mod vibix; + use vibix as imp; + } any( target_os = "teeos", target_family = "unix", diff --git a/library/std/src/sys/time/vibix.rs b/library/std/src/sys/time/vibix.rs new file mode 100644 index 00000000..14527a15 --- /dev/null +++ b/library/std/src/sys/time/vibix.rs @@ -0,0 +1,102 @@ +//! Time implementation for vibix. +//! +//! Provides SystemTime backed by seconds+nanoseconds from epoch (Unix time), +//! and Instant backed on clock_gettime(CLOCK_MONOTONIC) when available. + +use crate::time::Duration; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime { + tv_sec: i64, + tv_nsec: i64, +} + +pub const UNIX_EPOCH: SystemTime = SystemTime { tv_sec: 0, tv_nsec: 0 }; + +impl Instant { + pub fn now() -> Instant { + // vibix doesn't have clock_gettime wired yet; panic for now. + // Phase 3 will add CLOCK_MONOTONIC support. + panic!("Instant::now() not yet supported on vibix") + } + + pub fn checked_sub_instant(&self, other: &Instant) -> Option { + self.0.checked_sub(other.0) + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_add(*other)?)) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_sub(*other)?)) + } +} + +impl SystemTime { + pub const MAX: SystemTime = SystemTime { tv_sec: i64::MAX, tv_nsec: 999_999_999 }; + + pub const MIN: SystemTime = SystemTime { tv_sec: i64::MIN, tv_nsec: 0 }; + + /// Create a SystemTime from seconds and nanoseconds since the Unix epoch. + pub fn new(tv_sec: i64, tv_nsec: i64) -> Result { + if tv_nsec >= 0 && tv_nsec < 1_000_000_000 { + Ok(SystemTime { tv_sec, tv_nsec }) + } else { + Err(crate::io::const_error!( + crate::io::ErrorKind::InvalidData, + "invalid timestamp" + )) + } + } + + pub fn now() -> SystemTime { + // vibix doesn't have clock_gettime wired for userspace yet. + panic!("SystemTime::now() not yet supported on vibix") + } + + pub fn sub_time(&self, other: &SystemTime) -> Result { + if self >= other { + let sec_diff = (self.tv_sec - other.tv_sec) as u64; + let nsec_diff = self.tv_nsec - other.tv_nsec; + if nsec_diff >= 0 { + Ok(Duration::new(sec_diff, nsec_diff as u32)) + } else { + Ok(Duration::new(sec_diff - 1, (nsec_diff + 1_000_000_000) as u32)) + } + } else { + match other.sub_time(self) { + Ok(d) => Err(d), + Err(d) => Ok(d), + } + } + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + let mut secs = self.tv_sec.checked_add(other.as_secs() as i64)?; + let mut nsec = self.tv_nsec + other.subsec_nanos() as i64; + if nsec >= 1_000_000_000 { + nsec -= 1_000_000_000; + secs = secs.checked_add(1)?; + } + Some(SystemTime { tv_sec: secs, tv_nsec: nsec }) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + let mut secs = self.tv_sec.checked_sub(other.as_secs() as i64)?; + let mut nsec = self.tv_nsec - other.subsec_nanos() as i64; + if nsec < 0 { + nsec += 1_000_000_000; + secs = secs.checked_sub(1)?; + } + Some(SystemTime { tv_sec: secs, tv_nsec: nsec }) + } + + /// Convert to (seconds, nanoseconds) for use with utimensat. + pub fn to_timespec(&self) -> (i64, i64) { + (self.tv_sec, self.tv_nsec) + } +} diff --git a/userspace/vibix_abi/src/fs.rs b/userspace/vibix_abi/src/fs.rs new file mode 100644 index 00000000..60a934d1 --- /dev/null +++ b/userspace/vibix_abi/src/fs.rs @@ -0,0 +1,315 @@ +//! Filesystem syscall wrappers for vibix. +//! +//! These provide safe-ish wrappers around the raw syscall interface for use by +//! std's filesystem implementation. + +use crate::syscall; + +// Syscall numbers (Linux x86_64 ABI). +const SYS_READ: u64 = 0; +const SYS_WRITE: u64 = 1; +const SYS_CLOSE: u64 = 3; +const SYS_STAT: u64 = 4; +const SYS_FSTAT: u64 = 5; +const SYS_LSTAT: u64 = 6; +const SYS_LSEEK: u64 = 8; +const SYS_IOCTL: u64 = 16; +const SYS_ACCESS: u64 = 21; +const SYS_DUP: u64 = 32; +const SYS_FCNTL: u64 = 72; +const SYS_FSYNC: u64 = 74; +const SYS_FTRUNCATE: u64 = 77; +const SYS_GETCWD: u64 = 79; +const SYS_CHDIR: u64 = 80; +const SYS_MKDIR: u64 = 83; +const SYS_RMDIR: u64 = 84; +const SYS_LINK: u64 = 86; +const SYS_UNLINK: u64 = 87; +const SYS_SYMLINK: u64 = 88; +const SYS_READLINK: u64 = 89; +const SYS_CHMOD: u64 = 90; +const SYS_FCHMOD: u64 = 91; +const SYS_CHOWN: u64 = 92; +const SYS_FCHOWN: u64 = 93; +const SYS_LCHOWN: u64 = 94; +const SYS_RENAME: u64 = 82; +const SYS_GETDENTS64: u64 = 217; +const SYS_OPENAT: u64 = 257; +const SYS_UTIMENSAT: u64 = 280; + +// Open flags (Linux x86_64). +pub const O_RDONLY: i32 = 0; +pub const O_WRONLY: i32 = 1; +pub const O_RDWR: i32 = 2; +pub const O_CREAT: i32 = 0o100; +pub const O_EXCL: i32 = 0o200; +pub const O_TRUNC: i32 = 0o1000; +pub const O_APPEND: i32 = 0o2000; +pub const O_DIRECTORY: i32 = 0o200000; +pub const O_NOFOLLOW: i32 = 0o400000; +pub const O_CLOEXEC: i32 = 0o2000000; + +// lseek whence values. +pub const SEEK_SET: i32 = 0; +pub const SEEK_CUR: i32 = 1; +pub const SEEK_END: i32 = 2; + +// AT_* constants. +pub const AT_FDCWD: i32 = -100; +pub const AT_SYMLINK_NOFOLLOW: i32 = 0x100; + +// fcntl commands. +pub const F_DUPFD_CLOEXEC: i32 = 1030; + +// Stat mode flags. +pub const S_IFMT: u32 = 0o170000; +pub const S_IFDIR: u32 = 0o040000; +pub const S_IFREG: u32 = 0o100000; +pub const S_IFLNK: u32 = 0o120000; +pub const S_IFBLK: u32 = 0o060000; +pub const S_IFCHR: u32 = 0o020000; +pub const S_IFIFO: u32 = 0o010000; +pub const S_IFSOCK: u32 = 0o140000; + +// d_type values for dirents. +pub const DT_UNKNOWN: u8 = 0; +pub const DT_DIR: u8 = 4; +pub const DT_REG: u8 = 8; +pub const DT_LNK: u8 = 10; + +/// Linux x86_64 stat structure. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Stat { + pub st_dev: u64, + pub st_ino: u64, + pub st_nlink: u64, + pub st_mode: u32, + pub st_uid: u32, + pub st_gid: u32, + pub __pad0: u32, + pub st_rdev: u64, + pub st_size: i64, + pub st_blksize: i64, + pub st_blocks: i64, + pub st_atime: i64, + pub st_atime_nsec: i64, + pub st_mtime: i64, + pub st_mtime_nsec: i64, + pub st_ctime: i64, + pub st_ctime_nsec: i64, + pub __unused: [i64; 3], +} + +/// Linux dirent64 structure (variable-length). +#[repr(C)] +pub struct Dirent64 { + pub d_ino: u64, + pub d_off: i64, + pub d_reclen: u16, + pub d_type: u8, + pub d_name: [u8; 0], // flexible array member +} + +/// Timespec for utimensat. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Timespec { + pub tv_sec: i64, + pub tv_nsec: i64, +} + +pub const UTIME_NOW: i64 = (1 << 30) - 1; +pub const UTIME_OMIT: i64 = (1 << 30) - 2; + +/// Open a file relative to a directory fd. +#[inline] +pub unsafe fn openat(dirfd: i32, path: *const u8, flags: i32, mode: u32) -> i64 { + syscall::syscall4( + SYS_OPENAT, + dirfd as u64, + path as u64, + flags as u64, + mode as u64, + ) +} + +/// Read from a file descriptor. +#[inline] +pub unsafe fn read(fd: i32, buf: *mut u8, count: usize) -> i64 { + syscall::syscall3(SYS_READ, fd as u64, buf as u64, count as u64) +} + +/// Write to a file descriptor. +#[inline] +pub unsafe fn write(fd: i32, buf: *const u8, count: usize) -> i64 { + syscall::syscall3(SYS_WRITE, fd as u64, buf as u64, count as u64) +} + +/// Close a file descriptor. +#[inline] +pub unsafe fn close(fd: i32) -> i64 { + syscall::syscall1(SYS_CLOSE, fd as u64) +} + +/// Get file status. +#[inline] +pub unsafe fn fstat(fd: i32, statbuf: *mut Stat) -> i64 { + syscall::syscall2(SYS_FSTAT, fd as u64, statbuf as u64) +} + +/// Get file status by path. +#[inline] +pub unsafe fn stat(path: *const u8, statbuf: *mut Stat) -> i64 { + syscall::syscall2(SYS_STAT, path as u64, statbuf as u64) +} + +/// Get symbolic link status. +#[inline] +pub unsafe fn lstat(path: *const u8, statbuf: *mut Stat) -> i64 { + syscall::syscall2(SYS_LSTAT, path as u64, statbuf as u64) +} + +/// Reposition file offset. +#[inline] +pub unsafe fn lseek(fd: i32, offset: i64, whence: i32) -> i64 { + syscall::syscall3(SYS_LSEEK, fd as u64, offset as u64, whence as u64) +} + +/// Read directory entries. +#[inline] +pub unsafe fn getdents64(fd: i32, buf: *mut u8, count: usize) -> i64 { + syscall::syscall3(SYS_GETDENTS64, fd as u64, buf as u64, count as u64) +} + +/// Remove a file. +#[inline] +pub unsafe fn unlink(path: *const u8) -> i64 { + syscall::syscall1(SYS_UNLINK, path as u64) +} + +/// Create a directory. +#[inline] +pub unsafe fn mkdir(path: *const u8, mode: u32) -> i64 { + syscall::syscall2(SYS_MKDIR, path as u64, mode as u64) +} + +/// Remove a directory. +#[inline] +pub unsafe fn rmdir(path: *const u8) -> i64 { + syscall::syscall1(SYS_RMDIR, path as u64) +} + +/// Rename a file. +#[inline] +pub unsafe fn rename(old: *const u8, new: *const u8) -> i64 { + syscall::syscall2(SYS_RENAME, old as u64, new as u64) +} + +/// Create a hard link. +#[inline] +pub unsafe fn link(old: *const u8, new: *const u8) -> i64 { + syscall::syscall2(SYS_LINK, old as u64, new as u64) +} + +/// Create a symbolic link. +#[inline] +pub unsafe fn symlink(target: *const u8, linkpath: *const u8) -> i64 { + syscall::syscall2(SYS_SYMLINK, target as u64, linkpath as u64) +} + +/// Read the target of a symbolic link. +#[inline] +pub unsafe fn readlink(path: *const u8, buf: *mut u8, bufsiz: usize) -> i64 { + syscall::syscall3(SYS_READLINK, path as u64, buf as u64, bufsiz as u64) +} + +/// Change file mode. +#[inline] +pub unsafe fn chmod(path: *const u8, mode: u32) -> i64 { + syscall::syscall2(SYS_CHMOD, path as u64, mode as u64) +} + +/// Change file mode by fd. +#[inline] +pub unsafe fn fchmod(fd: i32, mode: u32) -> i64 { + syscall::syscall2(SYS_FCHMOD, fd as u64, mode as u64) +} + +/// Change file owner. +#[inline] +pub unsafe fn chown(path: *const u8, uid: u32, gid: u32) -> i64 { + syscall::syscall3(SYS_CHOWN, path as u64, uid as u64, gid as u64) +} + +/// Change file owner by fd. +#[inline] +pub unsafe fn fchown(fd: i32, uid: u32, gid: u32) -> i64 { + syscall::syscall3(SYS_FCHOWN, fd as u64, uid as u64, gid as u64) +} + +/// Change file owner (no symlink follow). +#[inline] +pub unsafe fn lchown(path: *const u8, uid: u32, gid: u32) -> i64 { + syscall::syscall3(SYS_LCHOWN, path as u64, uid as u64, gid as u64) +} + +/// Change file timestamps. +#[inline] +pub unsafe fn utimensat(dirfd: i32, path: *const u8, times: *const Timespec, flags: i32) -> i64 { + syscall::syscall4( + SYS_UTIMENSAT, + dirfd as u64, + path as u64, + times as u64, + flags as u64, + ) +} + +/// Truncate a file by fd. +#[inline] +pub unsafe fn ftruncate(fd: i32, length: i64) -> i64 { + syscall::syscall2(SYS_FTRUNCATE, fd as u64, length as u64) +} + +/// Sync file to disk. +#[inline] +pub unsafe fn fsync(fd: i32) -> i64 { + syscall::syscall1(SYS_FSYNC, fd as u64) +} + +/// Duplicate a file descriptor. +#[inline] +pub unsafe fn dup(fd: i32) -> i64 { + syscall::syscall1(SYS_DUP, fd as u64) +} + +/// fcntl operations. +#[inline] +pub unsafe fn fcntl(fd: i32, cmd: i32, arg: u64) -> i64 { + syscall::syscall3(SYS_FCNTL, fd as u64, cmd as u64, arg) +} + +/// Check file access. +#[inline] +pub unsafe fn access(path: *const u8, mode: i32) -> i64 { + syscall::syscall2(SYS_ACCESS, path as u64, mode as u64) +} + +/// Get current working directory. +#[inline] +pub unsafe fn getcwd(buf: *mut u8, size: usize) -> i64 { + syscall::syscall2(SYS_GETCWD, buf as u64, size as u64) +} + +/// Change current directory. +#[inline] +pub unsafe fn chdir(path: *const u8) -> i64 { + syscall::syscall1(SYS_CHDIR, path as u64) +} + +/// ioctl. +#[inline] +pub unsafe fn ioctl(fd: i32, request: u64, arg: u64) -> i64 { + syscall::syscall3(SYS_IOCTL, fd as u64, request, arg) +} diff --git a/userspace/vibix_abi/src/lib.rs b/userspace/vibix_abi/src/lib.rs index d9358c4c..88888e66 100644 --- a/userspace/vibix_abi/src/lib.rs +++ b/userspace/vibix_abi/src/lib.rs @@ -9,5 +9,7 @@ pub mod alloc; pub mod errno; +pub mod fs; +pub mod process; pub mod stdio; pub mod syscall; diff --git a/userspace/vibix_abi/src/process.rs b/userspace/vibix_abi/src/process.rs new file mode 100644 index 00000000..186394f4 --- /dev/null +++ b/userspace/vibix_abi/src/process.rs @@ -0,0 +1,67 @@ +//! Process management syscall wrappers for vibix. + +use crate::syscall; + +const SYS_FORK: u64 = 57; +const SYS_EXECVE: u64 = 59; +const SYS_EXIT: u64 = 60; +const SYS_WAIT4: u64 = 61; +const SYS_KILL: u64 = 62; +const SYS_GETPID: u64 = 39; +const SYS_EXIT_GROUP: u64 = 231; + +/// Fork the current process. +#[inline] +pub unsafe fn fork() -> i64 { + syscall::syscall0(SYS_FORK) +} + +/// Execute a program. +#[inline] +pub unsafe fn execve(path: *const u8, argv: *const *const u8, envp: *const *const u8) -> i64 { + syscall::syscall3(SYS_EXECVE, path as u64, argv as u64, envp as u64) +} + +/// Exit the current thread. +#[inline] +pub unsafe fn exit(status: i32) -> ! { + syscall::syscall1(SYS_EXIT, status as u64); + // unreachable + loop { + core::hint::spin_loop(); + } +} + +/// Exit all threads in the process. +#[inline] +pub unsafe fn exit_group(status: i32) -> ! { + syscall::syscall1(SYS_EXIT_GROUP, status as u64); + loop { + core::hint::spin_loop(); + } +} + +/// Wait for a child process. +/// Returns pid on success, negative errno on failure. +#[inline] +pub unsafe fn wait4(pid: i32, status: *mut i32, options: i32, rusage: u64) -> i64 { + syscall::syscall4(SYS_WAIT4, pid as u64, status as u64, options as u64, rusage) +} + +/// Send a signal to a process. +#[inline] +pub unsafe fn kill(pid: i32, sig: i32) -> i64 { + syscall::syscall2(SYS_KILL, pid as u64, sig as u64) +} + +/// Get the current process ID. +#[inline] +pub unsafe fn getpid() -> i64 { + syscall::syscall0(SYS_GETPID) +} + +// Signal numbers. +pub const SIGKILL: i32 = 9; + +// wait4 options. +pub const WNOHANG: i32 = 1;