diff --git a/src/fd/file.rs b/src/fd/file.rs index 16a02d2caa..b66fff58ae 100644 --- a/src/fd/file.rs +++ b/src/fd/file.rs @@ -2,10 +2,10 @@ use alloc::boxed::Box; use core::{isize, slice}; use crate::fd::{ - uhyve_send, ObjectInterface, SysClose, SysLseek, SysRead, SysWrite, UHYVE_PORT_CLOSE, - UHYVE_PORT_LSEEK, UHYVE_PORT_READ, UHYVE_PORT_WRITE, + uhyve_send, DirectoryEntry, ObjectInterface, SysClose, SysLseek, SysRead, SysWrite, + UHYVE_PORT_CLOSE, UHYVE_PORT_LSEEK, UHYVE_PORT_READ, UHYVE_PORT_WRITE, }; -use crate::syscalls::fs::{self, PosixFile, SeekWhence}; +use crate::syscalls::fs::{self, FileAttr, PosixFile, SeekWhence}; #[derive(Debug, Clone)] pub struct UhyveFile(i32); @@ -98,6 +98,35 @@ impl ObjectInterface for GenericFile { ret as isize } + + /// `fstat` + fn fstat(&self, stat: *mut FileAttr) -> i32 { + debug!("fstat ! {}", self.0); + let mut result = 0; + let mut fs = fs::FILESYSTEM.lock(); + fs.fd_op(self.0, |file: &mut Box| { + result = file + .fstat(stat) + .map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0); + }); + + result + } + + fn readdir(&self) -> DirectoryEntry { + debug!("readdir ! {}", self.0); + + let mut fs = fs::FILESYSTEM.lock(); + let mut ret = DirectoryEntry::Invalid(-crate::errno::EINVAL); + fs.fd_op(self.0, |file: &mut Box| { + match file.readdir() { + Ok(dir_ptr) => ret = DirectoryEntry::Valid(dir_ptr), + Err(e) => ret = DirectoryEntry::Invalid(-num::ToPrimitive::to_i32(&e).unwrap()), + } + }); + + ret + } } impl Drop for GenericFile { diff --git a/src/fd/mod.rs b/src/fd/mod.rs index 72b86d276f..3632b1fab4 100644 --- a/src/fd/mod.rs +++ b/src/fd/mod.rs @@ -13,7 +13,7 @@ use crate::env; use crate::errno::*; use crate::fd::file::{GenericFile, UhyveFile}; use crate::fd::stdio::*; -use crate::syscalls::fs::{self, FilePerms, SeekWhence}; +use crate::syscalls::fs::{self, Dirent, FileAttr, FilePerms, SeekWhence}; #[cfg(all(any(feature = "tcp", feature = "udp"), not(feature = "newlib")))] use crate::syscalls::net::*; @@ -181,6 +181,13 @@ fn open_flags_to_perm(flags: i32, mode: u32) -> FilePerms { perms } +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub enum DirectoryEntry { + Invalid(i32), + Valid(*const Dirent), +} + pub trait ObjectInterface: Sync + Send + core::fmt::Debug + DynClone { /// `read` attempts to read `len` bytes from the object references /// by the descriptor @@ -199,11 +206,33 @@ pub trait ObjectInterface: Sync + Send + core::fmt::Debug + DynClone { (-EINVAL).try_into().unwrap() } - /// `unlink` removes directory entry + /// `fstat` + fn fstat(&self, _stat: *mut FileAttr) -> i32 { + -EINVAL + } + + /// `unlink` removes file entry fn unlink(&self, _name: *const u8) -> i32 { -EINVAL } + /// `rmdir` removes directory entry + fn rmdir(&self, _name: *const u8) -> i32 { + -EINVAL + } + + /// 'readdir' returns a pointer to a dirent structure + /// representing the next directory entry in the directory stream + /// pointed to by the file descriptor + fn readdir(&self) -> DirectoryEntry { + DirectoryEntry::Invalid(-ENOSYS) + } + + /// `mkdir` creates a directory entry + fn mkdir(&self, _name: *const u8, _mode: u32) -> i32 { + -EINVAL + } + /// `accept` a connection on a socket #[cfg(all(any(feature = "tcp", feature = "udp"), not(feature = "newlib")))] fn accept(&self, _addr: *mut sockaddr, _addrlen: *mut socklen_t) -> i32 { @@ -319,6 +348,37 @@ pub(crate) fn open(name: *const u8, flags: i32, mode: i32) -> Result Result { + if env::is_uhyve() { + Err(-EINVAL) + } else { + #[cfg(target_arch = "x86_64")] + { + let name = unsafe { CStr::from_ptr(name as _) }.to_str().unwrap(); + debug!("Open directory {}", name); + + let mut fs = fs::FILESYSTEM.lock(); + if let Ok(filesystem_fd) = fs.opendir(name) { + let fd = FD_COUNTER.fetch_add(1, Ordering::SeqCst); + // Would a GenericDir make sense? + let file = GenericFile::new(filesystem_fd); + if OBJECT_MAP.write().try_insert(fd, Arc::new(file)).is_err() { + Err(-EINVAL) + } else { + Ok(fd as FileDescriptor) + } + } else { + Err(-EINVAL) + } + } + #[cfg(not(target_arch = "x86_64"))] + { + Err(-ENOSYS) + } + } +} + pub(crate) fn get_object(fd: FileDescriptor) -> Result, i32> { Ok((*(OBJECT_MAP.read().get(&fd).ok_or(-EINVAL)?)).clone()) } diff --git a/src/fs/fuse.rs b/src/fs/fuse.rs index 9d2f19b446..c9789e3559 100644 --- a/src/fs/fuse.rs +++ b/src/fs/fuse.rs @@ -10,7 +10,9 @@ use crate::arch::kernel::mmio::get_filesystem_driver; #[cfg(feature = "pci")] use crate::drivers::pci::get_filesystem_driver; use crate::drivers::virtio::virtqueue::AsSliceU8; -use crate::syscalls::fs::{self, FileError, FilePerms, PosixFile, PosixFileSystem, SeekWhence}; +use crate::syscalls::fs::{ + self, Dirent, FileAttr, FileError, FilePerms, PosixFile, PosixFileSystem, SeekWhence, +}; // response out layout eg @ https://github.com/zargony/fuse-rs/blob/bf6d1cf03f3277e35b580f3c7b9999255d72ecf3/src/ll/request.rs#L44 // op in/out sizes/layout: https://github.com/hanwen/go-fuse/blob/204b45dba899dfa147235c255908236d5fde2d32/fuse/opcode.go#L439 @@ -20,6 +22,23 @@ const FUSE_ROOT_ID: u64 = 1; const MAX_READ_LEN: usize = 1024 * 64; const MAX_WRITE_LEN: usize = 1024 * 64; +const U64_SIZE: u32 = ::core::mem::size_of::() as u32; + +const S_IFLNK: u32 = 40960; +const S_IFMT: u32 = 61440; + +const FUSE_GETATTR_FH: u32 = 1 << 0; + +#[repr(C)] +#[derive(Debug)] +pub struct fuse_dirent { + pub d_ino: u64, + pub d_off: u64, + pub d_namelen: u32, + pub d_type: u32, + pub d_name: [u8; 0], +} + pub trait FuseInterface { fn send_command(&mut self, cmd: &Cmd, rsp: &mut Rsp) where @@ -74,6 +93,39 @@ impl PosixFileSystem for Fuse { Ok(Box::new(file)) } + fn opendir(&self, path: &str) -> Result, FileError> { + debug!("FUSE opendir: {}", path); + + let mut readdir = FuseDir { + fuse_nid: None, + fuse_fh: None, + offset: 0, + response: None, + buffer_offset: 0, + }; + + // Lookup nodeid + + readdir.fuse_nid = self.lookup(path); + + if readdir.fuse_nid.is_none() { + warn!("Fuse lookup seems to have failed!"); + return Err(FileError::ENOENT); + } + + // Opendir + // Flag 0x10000 for O_DIRECTORY might not be necessary + let (mut cmd, mut rsp) = create_open(readdir.fuse_nid.unwrap(), 0x10000); + cmd.header.opcode = Opcode::FUSE_OPENDIR as u32; + get_filesystem_driver() + .ok_or(FileError::ENOSYS)? + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + readdir.fuse_fh = Some(unsafe { rsp.rsp.assume_init().fh }); + + Ok(Box::new(readdir)) + } + fn unlink(&self, path: &str) -> core::result::Result<(), FileError> { let (cmd, mut rsp) = create_unlink(path); get_filesystem_driver() @@ -84,6 +136,71 @@ impl PosixFileSystem for Fuse { Ok(()) } + + fn rmdir(&self, path: &str) -> core::result::Result<(), FileError> { + let (cmd, mut rsp) = create_rmdir(path); + get_filesystem_driver() + .ok_or(FileError::ENOSYS)? + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + trace!("rmdir answer {:?}", rsp); + + Ok(()) + } + + fn mkdir(&self, name: &str, mode: u32) -> Result { + let (cmd, mut rsp) = create_mkdir(name, mode); + + get_filesystem_driver() + .ok_or(FileError::ENOSYS)? + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + Ok(unsafe { rsp.rsp.assume_init().nodeid.try_into().unwrap() }) + } + + fn stat(&self, path: &str, stat: *mut FileAttr) -> Result<(), FileError> { + debug!("FUSE stat: {}", path); + + // Is there a better way to implement this? + let (cmd, mut rsp) = create_lookup(path); + get_filesystem_driver() + .unwrap() + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + + if rsp.header.error != 0 { + // TODO: Correct error handling + return Err(FileError::EIO); + } + + let rsp = unsafe { rsp.rsp.assume_init() }; + let attr = rsp.attr; + + if attr.mode & S_IFMT != S_IFLNK { + unsafe { + attr.fill_stat(stat); + } + Ok(()) + } else { + self.stat(&self.readlink(rsp.nodeid)?, stat) + } + } + + fn lstat(&self, path: &str, stat: *mut FileAttr) -> Result<(), FileError> { + let (cmd, mut rsp) = create_lookup(path); + get_filesystem_driver() + .unwrap() + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + + let attr = unsafe { rsp.rsp.assume_init().attr }; + + unsafe { + attr.fill_stat(stat); + } + + Ok(()) + } } impl Fuse { @@ -106,7 +223,36 @@ impl Fuse { .unwrap() .lock() .send_command(cmd.as_ref(), rsp.as_mut()); - Some(unsafe { rsp.rsp.assume_init().nodeid }) + if rsp.header.error == 0 { + Some(unsafe { rsp.rsp.assume_init().nodeid }) + } else { + None + } + } + + pub fn readlink(&self, nid: u64) -> Result { + let len = MAX_READ_LEN as u32; + let (cmd, mut rsp) = create_readlink(nid, len); + get_filesystem_driver() + .unwrap() + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + let len: usize = if rsp.header.len as usize + - ::core::mem::size_of::() + - ::core::mem::size_of::() + >= len.try_into().unwrap() + { + len.try_into().unwrap() + } else { + rsp.header.len as usize + - ::core::mem::size_of::() + - ::core::mem::size_of::() + }; + + Ok(String::from_utf8(unsafe { + MaybeUninit::slice_assume_init_ref(&rsp.extra_buffer[..len]).to_vec() + }) + .unwrap()) } } @@ -122,6 +268,115 @@ struct FuseFile { offset: usize, } +struct FuseDir { + fuse_nid: Option, + fuse_fh: Option, + offset: u64, + response: Option>>, + buffer_offset: u32, +} + +impl PosixFile for FuseDir { + fn close(&mut self) -> Result<(), FileError> { + let (mut cmd, mut rsp) = create_release(self.fuse_nid.unwrap(), self.fuse_fh.unwrap()); + cmd.header.opcode = Opcode::FUSE_RELEASEDIR as u32; + get_filesystem_driver() + .ok_or(FileError::ENOSYS)? + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + + Ok(()) + } + fn read(&mut self, _len: u32) -> Result, FileError> { + // Use readdir instead + Err(FileError::EISDIR) + } + fn write(&mut self, _buf: &[u8]) -> Result { + // Not opened for writing + Err(FileError::EBADF) + } + fn lseek(&mut self, _offset: isize, _whence: SeekWhence) -> Result { + Err(FileError::ENOSYS) + } + + fn readdir(&mut self) -> Result<*const Dirent, FileError> { + // Check if we have to read the directory via FUSE or still have a direntry in the last respnse + let resp: &_ = match &mut self.response { + Some(resp) + if resp.header.len - self.buffer_offset + > core::mem::size_of::().try_into().unwrap() => + { + resp + } + option => { + debug!("FUSE read from dirfile"); + // Linux seems to allocate a single page to store the dirfile + let len = MAX_READ_LEN as u32; + + let (Some(nid), Some(fh)) = (self.fuse_nid, self.fuse_fh) else { + warn!("FUSE dir not open, cannot read!"); + return Err(FileError::EBADF); + }; + + let (mut cmd, mut rsp) = create_read(nid, fh, len, self.offset); + cmd.header.opcode = Opcode::FUSE_READDIR as u32; + if let Some(fs_driver) = get_filesystem_driver() { + fs_driver.lock().send_command(cmd.as_ref(), rsp.as_mut()); + } else { + return Err(FileError::ENOSYS); + } + + let len: usize = if rsp.header.len as usize + - ::core::mem::size_of::() + - ::core::mem::size_of::() + >= len.try_into().unwrap() + { + len.try_into().unwrap() + } else { + rsp.header.len as usize + - ::core::mem::size_of::() + - ::core::mem::size_of::() + }; + + if len <= core::mem::size_of::() { + debug!("FUSE no new dirs"); + return Ok(core::ptr::null()); + } + + // Keep smart pointer to response + let rsp = option.insert(rsp); + self.buffer_offset = 0; + + debug!("FUSE new buffer len: {}", len); + rsp + } + }; + + let return_ptr: *const u8 = unsafe { + resp.extra_buffer + .as_ptr() + .byte_add(self.buffer_offset.try_into().unwrap()) as _ + }; + + let dirent = unsafe { &*(return_ptr as *const fuse_dirent) }; + + self.offset = dirent.d_off; + + self.buffer_offset += core::mem::size_of::() as u32 + dirent.d_namelen; + + // Allign to dirent struct + self.buffer_offset = ((self.buffer_offset) + U64_SIZE - 1) & (!(U64_SIZE - 1)); + + // Check alignment + assert!(return_ptr.is_aligned_to(U64_SIZE.try_into().unwrap())); + Ok(return_ptr.cast()) + } + + fn fstat(&self, _stat: *mut FileAttr) -> Result<(), FileError> { + Err(FileError::ENOSYS) + } +} + impl PosixFile for FuseFile { fn close(&mut self) -> Result<(), FileError> { let (cmd, mut rsp) = create_release(self.fuse_nid.unwrap(), self.fuse_fh.unwrap()); @@ -166,7 +421,7 @@ impl PosixFile for FuseFile { } fn write(&mut self, buf: &[u8]) -> Result { - debug!("fuse write!"); + debug!("FUSE write!"); let mut len = buf.len(); if len > MAX_WRITE_LEN { debug!( @@ -202,7 +457,7 @@ impl PosixFile for FuseFile { } fn lseek(&mut self, offset: isize, whence: SeekWhence) -> Result { - debug!("fuse lseek"); + debug!("FUSE lseek"); if let (Some(nid), Some(fh)) = (self.fuse_nid, self.fuse_fh) { let (cmd, mut rsp) = create_lseek(nid, fh, offset, whence); @@ -222,6 +477,30 @@ impl PosixFile for FuseFile { Err(FileError::EIO) } } + + fn readdir(&mut self) -> Result<*const Dirent, FileError> { + Err(FileError::EBADF) + } + + fn fstat(&self, stat: *mut FileAttr) -> Result<(), FileError> { + if let (Some(nid), Some(fh)) = (self.fuse_nid, self.fuse_fh) { + let (cmd, mut rsp) = create_getattr(nid, fh, FUSE_GETATTR_FH); + get_filesystem_driver() + .ok_or(FileError::ENOSYS)? + .lock() + .send_command(cmd.as_ref(), rsp.as_mut()); + + if rsp.header.error < 0 { + return Err(FileError::EIO); + } + + let attr = unsafe { rsp.rsp.assume_init().attr }; + unsafe { attr.fill_stat(stat) }; + Ok(()) + } else { + Err(FileError::EIO) + } + } } #[repr(u32)] @@ -381,6 +660,60 @@ fn create_init() -> (Box>, Box>) { (cmd, rsp) } +fn create_getattr( + nid: u64, + fh: u64, + flags: u32, +) -> (Box>, Box>) { + let len = core::mem::size_of::() + core::mem::size_of::(); + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let cmd = unsafe { + let data = alloc(layout); + let raw = core::ptr::slice_from_raw_parts_mut(data, 0) as *mut Cmd; + (*raw).header = create_in_header::(nid, Opcode::FUSE_GETATTR); + (*raw).cmd = fuse_getattr_in { + getattr_flags: flags, + dummy: 0, + fh, + }; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*cmd)); + + let len = core::mem::size_of::() + core::mem::size_of::(); + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let rsp = unsafe { + let data = alloc(layout); + let raw = core::ptr::slice_from_raw_parts_mut(data, 0) as *mut Rsp; + (*raw).header = fuse_out_header { + len: len.try_into().unwrap(), + ..Default::default() + }; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*rsp)); + + (cmd, rsp) +} + fn create_lookup(name: &str) -> (Box>, Box>) { let slice = name.as_bytes(); let len = core::mem::size_of::() @@ -434,6 +767,58 @@ fn create_lookup(name: &str) -> (Box>, Box (Box>, Box>) { + let len = core::mem::size_of::() + core::mem::size_of::(); + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let cmd = unsafe { + let data = alloc(layout); + let raw = core::ptr::slice_from_raw_parts_mut(data, 0) as *mut Cmd; + (*raw).header = create_in_header::(nid, Opcode::FUSE_READLINK); + (*raw).header.len = len.try_into().unwrap(); + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*cmd)); + + let len = core::mem::size_of::() + + core::mem::size_of::() + + usize::try_from(size).unwrap(); + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let rsp = unsafe { + let data = alloc(layout); + let raw = core::ptr::slice_from_raw_parts_mut(data, size.try_into().unwrap()) + as *mut Rsp; + (*raw).header = fuse_out_header { + len: len.try_into().unwrap(), + ..Default::default() + }; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*rsp)); + + (cmd, rsp) +} + #[repr(C)] #[derive(Debug, Default)] pub struct fuse_in_header { @@ -862,6 +1247,38 @@ fn create_release(nid: u64, fh: u64) -> (Box>, Box (Box>, Box>) { + let slice = path.as_bytes(); + let len = core::mem::size_of::() + + core::mem::size_of::() + + slice.len() + + 1; + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let cmd = unsafe { + let data = alloc(layout); + let raw = + core::ptr::slice_from_raw_parts_mut(data, slice.len() + 1) as *mut Cmd; + (*raw).header = create_in_header::(FUSE_ROOT_ID, Opcode::FUSE_MKDIR); + (*raw).header.len = len.try_into().unwrap(); + (*raw).cmd = fuse_mkdir_in { + mode, + ..Default::default() + }; + (*raw).extra_buffer[..slice.len()].copy_from_slice(slice); + (*raw).extra_buffer[slice.len()] = 0; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*cmd)); + + let len = core::mem::size_of::() + core::mem::size_of::(); + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let rsp = unsafe { + let data = alloc(layout); + let raw = core::ptr::slice_from_raw_parts_mut(data, 0) as *mut Rsp; + (*raw).header = fuse_out_header { + len: len.try_into().unwrap(), + ..Default::default() + }; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*rsp)); + + (cmd, rsp) +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct fuse_rmdir_in {} +unsafe impl FuseIn for fuse_rmdir_in {} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct fuse_rmdir_out {} +unsafe impl FuseOut for fuse_rmdir_out {} + +fn create_rmdir(name: &str) -> (Box>, Box>) { + let slice = name.as_bytes(); + let len = core::mem::size_of::() + + core::mem::size_of::() + + slice.len() + + 1; + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let cmd = unsafe { + let data = alloc(layout); + let raw = + core::ptr::slice_from_raw_parts_mut(data, slice.len() + 1) as *mut Cmd; + (*raw).header = create_in_header::(FUSE_ROOT_ID, Opcode::FUSE_RMDIR); + (*raw).header.len = len.try_into().unwrap(); + (*raw).extra_buffer[..slice.len()].copy_from_slice(slice); + (*raw).extra_buffer[slice.len()] = 0; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*cmd)); + + let len = core::mem::size_of::() + core::mem::size_of::(); + let layout = Layout::from_size_align( + len, + core::cmp::max( + core::mem::align_of::(), + core::mem::align_of::(), + ), + ) + .unwrap() + .pad_to_align(); + let rsp = unsafe { + let data = alloc(layout); + let raw = core::ptr::slice_from_raw_parts_mut(data, 0) as *mut Rsp; + (*raw).header = fuse_out_header { + len: len.try_into().unwrap(), + ..Default::default() + }; + + Box::from_raw(raw) + }; + assert_eq!(layout, Layout::for_value(&*rsp)); + + (cmd, rsp) +} + pub fn init() { if let Some(driver) = get_filesystem_driver() { // Instantiate global fuse object diff --git a/src/lib.rs b/src/lib.rs index 0f87964021..85dbb06b2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,8 @@ #![feature(maybe_uninit_slice)] #![feature(naked_functions)] #![feature(noop_waker)] +#![feature(pointer_byte_offsets)] +#![feature(pointer_is_aligned)] #![cfg_attr(target_arch = "aarch64", feature(specialization))] #![feature(strict_provenance)] #![cfg_attr(target_os = "none", no_std)] diff --git a/src/syscalls/fs.rs b/src/syscalls/fs.rs index de588603b1..7958715020 100644 --- a/src/syscalls/fs.rs +++ b/src/syscalls/fs.rs @@ -42,6 +42,11 @@ use hermit_sync::TicketMutex; /// TODO: /// - FileDescriptor newtype use crate::env::is_uhyve; +use crate::errno; +#[cfg(feature = "fs")] +pub use crate::fs::fuse::fuse_dirent as Dirent; +#[cfg(not(feature = "fs"))] +pub struct Dirent; // TODO: lazy static could be replaced with explicit init on OS boot. pub static FILESYSTEM: TicketMutex = TicketMutex::new(Filesystem::new()); @@ -96,7 +101,7 @@ impl Filesystem { pathsplit.next(); // empty, since first char is / let mount = pathsplit.next().unwrap(); - let internal_path = pathsplit.next().unwrap(); + let internal_path = pathsplit.next().unwrap_or("/"); if let Some(fs) = self.mounts.get(mount) { return Ok((fs.deref(), internal_path)); } @@ -111,7 +116,7 @@ impl Filesystem { } else { "." }; - let internal_path = pathsplit.next().unwrap(); + let internal_path = pathsplit.next().unwrap_or("/"); debug!( "Assume that the directory '{}' is used as mount point!", @@ -141,6 +146,15 @@ impl Filesystem { Ok(self.add_file(file)) } + /// Similar to open + #[allow(dead_code)] + pub fn opendir(&mut self, path: &str) -> Result { + debug!("Opening dir {}", path); + let (fs, internal_path) = self.parse_path(path)?; + let file = fs.opendir(internal_path)?; + Ok(self.add_file(file)) + } + /// Closes a file with given fd. /// If the file is currently open, closes it /// Remove the file from map of open files @@ -160,6 +174,42 @@ impl Filesystem { Ok(()) } + /// Remove directory given by path + #[allow(dead_code)] + pub fn rmdir(&mut self, path: &str) -> Result<(), FileError> { + debug!("Removing directory {}", path); + let (fs, internal_path) = self.parse_path(path)?; + fs.rmdir(internal_path)?; + Ok(()) + } + + /// Create directory given by path + #[allow(dead_code)] + pub fn mkdir(&mut self, path: &str, mode: u32) -> Result<(), FileError> { + debug!("Removing directory {}", path); + let (fs, internal_path) = self.parse_path(path)?; + fs.mkdir(internal_path, mode)?; + Ok(()) + } + + /// stat + #[allow(dead_code)] + pub fn stat(&mut self, path: &str, stat: *mut FileAttr) -> Result<(), FileError> { + debug!("Getting stats {}", path); + let (fs, internal_path) = self.parse_path(path)?; + fs.stat(internal_path, stat)?; + Ok(()) + } + + /// lstat + #[allow(dead_code)] + pub fn lstat(&mut self, path: &str, stat: *mut FileAttr) -> Result<(), FileError> { + debug!("Getting lstats {}", path); + let (fs, internal_path) = self.parse_path(path)?; + fs.lstat(internal_path, stat)?; + Ok(()) + } + /// Create new backing-fs at mountpoint mntpath #[cfg(feature = "fs")] pub fn mount( @@ -196,19 +246,30 @@ impl Filesystem { } } +// TODO: Integrate with src/errno.rs ? #[allow(clippy::upper_case_acronyms)] -#[derive(Debug)] +#[derive(Debug, FromPrimitive, ToPrimitive)] pub enum FileError { - ENOENT, - #[cfg(feature = "fs")] - ENOSYS, - #[cfg(feature = "fs")] - EIO, + ENOENT = errno::ENOENT as isize, + #[cfg(any(feature = "fs", feature = "pci"))] + ENOSYS = errno::ENOSYS as isize, + #[cfg(any(feature = "fs", feature = "pci"))] + EIO = errno::EIO as isize, + #[cfg(feature = "pci")] + EBADF = errno::EBADF as isize, + #[cfg(feature = "pci")] + EISDIR = errno::EISDIR as isize, } pub trait PosixFileSystem { fn open(&self, _path: &str, _perms: FilePerms) -> Result, FileError>; + fn opendir(&self, path: &str) -> Result, FileError>; fn unlink(&self, _path: &str) -> Result<(), FileError>; + + fn rmdir(&self, _path: &str) -> Result<(), FileError>; + fn mkdir(&self, name: &str, mode: u32) -> Result; + fn stat(&self, _path: &str, stat: *mut FileAttr) -> Result<(), FileError>; + fn lstat(&self, _path: &str, stat: *mut FileAttr) -> Result<(), FileError>; } pub trait PosixFile { @@ -216,6 +277,43 @@ pub trait PosixFile { fn read(&mut self, len: u32) -> Result, FileError>; fn write(&mut self, buf: &[u8]) -> Result; fn lseek(&mut self, offset: isize, whence: SeekWhence) -> Result; + + fn readdir(&mut self) -> Result<*const Dirent, FileError>; + fn fstat(&self, stat: *mut FileAttr) -> Result<(), FileError>; +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct FileAttr { + 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 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, +} + +#[derive(Debug, FromPrimitive, ToPrimitive)] +pub enum PosixFileType { + Unknown = 0, // DT_UNKNOWN + Fifo = 1, // DT_FIFO + CharacterDevice = 2, // DT_CHR + Directory = 4, // DT_DIR + BlockDevice = 6, // DT_BLK + RegularFile = 8, // DT_REG + SymbolicLink = 10, // DT_LNK + Socket = 12, // DT_SOCK + Whiteout = 14, // DT_WHT } // TODO: raw is partially redundant, create nicer interface diff --git a/src/syscalls/interfaces/mod.rs b/src/syscalls/interfaces/mod.rs index 584176fb0b..6b8be563fc 100644 --- a/src/syscalls/interfaces/mod.rs +++ b/src/syscalls/interfaces/mod.rs @@ -4,8 +4,9 @@ use core::ffi::CStr; pub use self::generic::*; pub use self::uhyve::*; -use crate::errno::*; -use crate::syscalls::fs::{self}; +#[cfg(not(target_arch = "x86_64"))] +use crate::errno::ENOSYS; +use crate::syscalls::fs::{self, FileAttr}; use crate::{arch, env}; mod generic; @@ -62,12 +63,62 @@ pub trait SyscallInterface: Send + Sync { fs::FILESYSTEM .lock() .unlink(name) - .expect("Unlinking failed!"); // TODO: error handling - 0 + .map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0) } - fn stat(&self, _file: *const u8, _st: usize) -> i32 { - info!("stat is unimplemented"); + #[cfg(target_arch = "x86_64")] + fn rmdir(&self, name: *const u8) -> i32 { + let name = unsafe { CStr::from_ptr(name as _) }.to_str().unwrap(); + debug!("rmdir {}", name); + + fs::FILESYSTEM + .lock() + .rmdir(name) + .map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0) + } + + #[cfg(target_arch = "x86_64")] + fn mkdir(&self, name: *const u8, mode: u32) -> i32 { + let name = unsafe { CStr::from_ptr(name as _) }.to_str().unwrap(); + debug!("mkdir {}, mode {}", name, mode); + + fs::FILESYSTEM + .lock() + .mkdir(name, mode) + .map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0) + } + + #[cfg(not(target_arch = "x86_64"))] + fn stat(&self, _name: *const u8, _stat: *mut FileAttr) -> i32 { + debug!("stat is unimplemented, returning -ENOSYS"); -ENOSYS } + + #[cfg(target_arch = "x86_64")] + fn stat(&self, name: *const u8, stat: *mut FileAttr) -> i32 { + let name = unsafe { CStr::from_ptr(name as _) }.to_str().unwrap(); + debug!("stat {}", name); + + fs::FILESYSTEM + .lock() + .stat(name, stat) + .map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0) + } + + #[cfg(not(target_arch = "x86_64"))] + fn lstat(&self, _name: *const u8, _stat: *mut FileAttr) -> i32 { + debug!("lstat is unimplemented, returning -ENOSYS"); + -ENOSYS + } + + #[cfg(target_arch = "x86_64")] + fn lstat(&self, name: *const u8, stat: *mut FileAttr) -> i32 { + let name = unsafe { CStr::from_ptr(name as _) }.to_str().unwrap(); + debug!("lstat {}", name); + + fs::FILESYSTEM + .lock() + .lstat(name, stat) + .map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0) + } } diff --git a/src/syscalls/mod.rs b/src/syscalls/mod.rs index f69658fdd4..5c090aab38 100644 --- a/src/syscalls/mod.rs +++ b/src/syscalls/mod.rs @@ -16,7 +16,8 @@ pub use self::system::*; pub use self::tasks::*; pub use self::timer::*; use crate::env; -use crate::fd::{dup_object, get_object, remove_object, FileDescriptor}; +use crate::fd::{dup_object, get_object, remove_object, DirectoryEntry, FileDescriptor}; +use crate::syscalls::fs::FileAttr; use crate::syscalls::interfaces::SyscallInterface; #[cfg(target_os = "none")] use crate::{__sys_free, __sys_malloc, __sys_realloc}; @@ -107,6 +108,73 @@ pub extern "C" fn sys_unlink(name: *const u8) -> i32 { kernel_function!(__sys_unlink(name)) } +#[cfg(target_arch = "x86_64")] +extern "C" fn __sys_mkdir(name: *const u8, mode: u32) -> i32 { + SYS.mkdir(name, mode) +} + +#[cfg(not(target_arch = "x86_64"))] +extern "C" fn __sys_mkdir(_name: *const u8, _mode: u32) -> i32 { + -crate::errno::ENOSYS +} + +#[no_mangle] +pub extern "C" fn sys_mkdir(name: *const u8, mode: u32) -> i32 { + kernel_function!(__sys_mkdir(name, mode)) +} + +#[cfg(target_arch = "x86_64")] +extern "C" fn __sys_rmdir(name: *const u8) -> i32 { + SYS.rmdir(name) +} + +#[cfg(not(target_arch = "x86_64"))] +extern "C" fn __sys_rmdir(_name: *const u8) -> i32 { + -crate::errno::ENOSYS +} + +#[no_mangle] +pub extern "C" fn sys_rmdir(name: *const u8) -> i32 { + kernel_function!(__sys_rmdir(name)) +} + +extern "C" fn __sys_stat(name: *const u8, stat: *mut FileAttr) -> i32 { + SYS.stat(name, stat) +} + +#[no_mangle] +pub extern "C" fn sys_stat(name: *const u8, stat: *mut FileAttr) -> i32 { + kernel_function!(__sys_stat(name, stat)) +} + +extern "C" fn __sys_lstat(name: *const u8, stat: *mut FileAttr) -> i32 { + SYS.lstat(name, stat) +} + +#[no_mangle] +pub extern "C" fn sys_lstat(name: *const u8, stat: *mut FileAttr) -> i32 { + kernel_function!(__sys_lstat(name, stat)) +} + +extern "C" fn __sys_fstat(fd: FileDescriptor, stat: *mut FileAttr) -> i32 { + let obj = get_object(fd); + obj.map_or_else(|e| e, |v| (*v).fstat(stat)) +} + +#[no_mangle] +pub extern "C" fn sys_fstat(fd: FileDescriptor, stat: *mut FileAttr) -> i32 { + kernel_function!(__sys_fstat(fd, stat)) +} + +extern "C" fn __sys_opendir(name: *const u8) -> FileDescriptor { + crate::fd::opendir(name).map_or_else(|e| e, |v| v) +} + +#[no_mangle] +pub extern "C" fn sys_opendir(name: *const u8) -> FileDescriptor { + kernel_function!(__sys_opendir(name)) +} + extern "C" fn __sys_open(name: *const u8, flags: i32, mode: i32) -> FileDescriptor { crate::fd::open(name, flags, mode).map_or_else(|e| e, |v| v) } @@ -169,13 +237,16 @@ pub extern "C" fn sys_lseek(fd: FileDescriptor, offset: isize, whence: i32) -> i kernel_function!(__sys_lseek(fd, offset, whence)) } -extern "C" fn __sys_stat(file: *const u8, st: usize) -> i32 { - SYS.stat(file, st) +extern "C" fn __sys_readdir(fd: FileDescriptor) -> DirectoryEntry { + let obj = get_object(fd); + obj.map_or(DirectoryEntry::Invalid(-crate::errno::EINVAL), |v| { + (*v).readdir() + }) } #[no_mangle] -pub extern "C" fn sys_stat(file: *const u8, st: usize) -> i32 { - kernel_function!(__sys_stat(file, st)) +pub extern "C" fn sys_readdir(fd: FileDescriptor) -> DirectoryEntry { + kernel_function!(__sys_readdir(fd)) } extern "C" fn __sys_dup(fd: i32) -> i32 {