use crate::error::Error; use crate::file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile}; use bitflags::bitflags; use cap_fs_ext::SystemTimeSpec; use std::any::Any; use std::cell::Ref; use std::convert::TryInto; use std::ops::Deref; use std::path::{Path, PathBuf}; pub trait WasiDir { fn as_any(&self) -> &dyn Any; fn open_file( &self, symlink_follow: bool, path: &str, oflags: OFlags, caps: FileCaps, fdflags: FdFlags, ) -> Result, Error>; fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error>; fn create_dir(&self, path: &str) -> Result<(), Error>; fn readdir( &self, cursor: ReaddirCursor, ) -> Result>>, Error>; fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>; fn remove_dir(&self, path: &str) -> Result<(), Error>; fn unlink_file(&self, path: &str) -> Result<(), Error>; fn read_link(&self, path: &str) -> Result; fn get_filestat(&self) -> Result; fn get_path_filestat(&self, path: &str) -> Result; fn rename(&self, path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error>; fn hard_link( &self, path: &str, symlink_follow: bool, target_dir: &dyn WasiDir, target_path: &str, ) -> Result<(), Error>; fn set_times( &self, path: &str, atime: Option, mtime: Option, ) -> Result<(), Error>; } pub(crate) struct DirEntry { caps: DirCaps, file_caps: FileCaps, preopen_path: Option, // precondition: PathBuf is valid unicode dir: Box, } impl DirEntry { pub fn new( caps: DirCaps, file_caps: FileCaps, preopen_path: Option, dir: Box, ) -> Self { DirEntry { caps, file_caps, preopen_path, dir, } } pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> { if self.caps.contains(caps) { Ok(()) } else { Err(Error::DirNotCapable { desired: caps, has: self.caps, }) } } pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> { if self.file_caps.contains(caps) { Ok(()) } else { Err(Error::FileNotCapable { desired: caps, has: self.file_caps, }) } } pub fn drop_caps_to(&mut self, caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> { self.capable_of_dir(caps)?; self.capable_of_file(file_caps)?; self.caps = caps; self.file_caps = file_caps; Ok(()) } pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps { self.caps & desired_caps } pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps { self.file_caps & desired_caps } pub fn get_dir_fdstat(&self) -> DirFdStat { DirFdStat { dir_caps: self.caps, file_caps: self.file_caps, } } pub fn preopen_path(&self) -> &Option { &self.preopen_path } } pub trait DirEntryExt<'a> { fn get_cap(self, caps: DirCaps) -> Result, Error>; } impl<'a> DirEntryExt<'a> for Ref<'a, DirEntry> { fn get_cap(self, caps: DirCaps) -> Result, Error> { self.capable_of_dir(caps)?; Ok(Ref::map(self, |r| r.dir.deref())) } } bitflags! { pub struct DirCaps: u32 { const CREATE_DIRECTORY = 0b1; const CREATE_FILE = 0b10; const LINK_SOURCE = 0b100; const LINK_TARGET = 0b1000; const OPEN = 0b10000; const READDIR = 0b100000; const READLINK = 0b1000000; const RENAME_SOURCE = 0b10000000; const RENAME_TARGET = 0b100000000; const SYMLINK = 0b1000000000; const REMOVE_DIRECTORY = 0b10000000000; const UNLINK_FILE = 0b100000000000; const PATH_FILESTAT_GET = 0b1000000000000; const PATH_FILESTAT_SET_TIMES = 0b10000000000000; const FILESTAT_GET = 0b100000000000000; const FILESTAT_SET_TIMES = 0b1000000000000000; } } #[derive(Debug, Clone)] pub struct DirFdStat { pub file_caps: FileCaps, pub dir_caps: DirCaps, } pub(crate) trait TableDirExt { fn get_dir(&self, fd: u32) -> Result, Error>; fn is_preopen(&self, fd: u32) -> bool; } impl TableDirExt for crate::table::Table { fn get_dir(&self, fd: u32) -> Result, Error> { self.get(fd) } fn is_preopen(&self, fd: u32) -> bool { if self.is::(fd) { let dir_entry: std::cell::Ref = self.get(fd).unwrap(); dir_entry.preopen_path.is_some() } else { false } } } pub struct ReaddirEntity { pub next: ReaddirCursor, pub inode: u64, pub namelen: u32, pub filetype: FileType, } #[derive(Debug, Copy, Clone)] pub struct ReaddirCursor(u64); impl From for ReaddirCursor { fn from(c: u64) -> ReaddirCursor { ReaddirCursor(c) } } impl From for u64 { fn from(c: ReaddirCursor) -> u64 { c.0 } } impl WasiDir for cap_std::fs::Dir { fn as_any(&self) -> &dyn Any { self } fn open_file( &self, symlink_follow: bool, path: &str, oflags: OFlags, caps: FileCaps, fdflags: FdFlags, ) -> Result, Error> { use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; let mut opts = cap_std::fs::OpenOptions::new(); if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) { opts.create_new(true); opts.write(true); } else if oflags.contains(OFlags::CREATE) { opts.create(true); opts.write(true); } if oflags.contains(OFlags::TRUNCATE) { opts.truncate(true); } if caps.contains(FileCaps::WRITE) || caps.contains(FileCaps::DATASYNC) || caps.contains(FileCaps::ALLOCATE) || caps.contains(FileCaps::FILESTAT_SET_SIZE) { opts.write(true); } else { // If not opened write, open read. This way the OS lets us open the file. // If FileCaps::READ is not set, read calls will be rejected at the // get_cap check. opts.read(true); } if caps.contains(FileCaps::READ) { opts.read(true); } if fdflags.contains(FdFlags::APPEND) { opts.append(true); } // XXX what about rest of fdflags - dsync, sync become oflags. // what do we do with nonblock? // what do we do with rsync? if symlink_follow { opts.follow(FollowSymlinks::Yes); } else { opts.follow(FollowSymlinks::No); } let f = self.open_with(Path::new(path), &opts)?; Ok(Box::new(f)) } fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { let d = if symlink_follow { self.open_dir(Path::new(path))? } else { use cap_fs_ext::DirExt; self.open_dir_nofollow(Path::new(path))? }; Ok(Box::new(d)) } fn create_dir(&self, path: &str) -> Result<(), Error> { self.create_dir(Path::new(path))?; Ok(()) } fn readdir( &self, cursor: ReaddirCursor, ) -> Result>>, Error> { use cap_fs_ext::MetadataExt; // cap_std's read_dir does not include . and .., we should prepend these. // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't // have enough info to make a ReaddirEntity yet. let dir_meta = self.dir_metadata()?; let rd = vec![ { let name = ".".to_owned(); let namelen = name.as_bytes().len().try_into().expect("1 wont overflow"); Ok((FileType::Directory, dir_meta.ino(), namelen, name)) }, { // XXX if parent dir is mounted it *might* be possible to give its inode, but we // don't know that in this context. let name = "..".to_owned(); let namelen = name.as_bytes().len().try_into().expect("2 wont overflow"); Ok((FileType::Directory, dir_meta.ino(), namelen, name)) }, ] .into_iter() .chain( // Now process the `DirEntry`s: self.entries()?.map(|entry| { let entry = entry?; let meta = entry.metadata()?; let inode = meta.ino(); let filetype = FileType::from(&meta.file_type()); let name = entry.file_name().into_string().map_err(|_| Error::Ilseq)?; let namelen = name.as_bytes().len().try_into()?; Ok((filetype, inode, namelen, name)) }), ) // Enumeration of the iterator makes it possible to define the ReaddirCursor .enumerate() .map(|(ix, r)| match r { Ok((filetype, inode, namelen, name)) => Ok(( ReaddirEntity { next: ReaddirCursor::from(ix as u64 + 1), filetype, inode, namelen, }, name, )), Err(e) => Err(e), }) .skip(u64::from(cursor) as usize); Ok(Box::new(rd)) } fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { self.symlink(Path::new(src_path), Path::new(dest_path))?; Ok(()) } fn remove_dir(&self, path: &str) -> Result<(), Error> { self.remove_dir(Path::new(path))?; Ok(()) } fn unlink_file(&self, path: &str) -> Result<(), Error> { self.remove_file(Path::new(path))?; Ok(()) } fn read_link(&self, path: &str) -> Result { let link = self.read_link(Path::new(path))?; Ok(link) } fn get_filestat(&self) -> Result { let meta = self.dir_metadata()?; use cap_fs_ext::MetadataExt; Ok(Filestat { device_id: meta.dev(), inode: meta.ino(), filetype: FileType::from(&meta.file_type()), nlink: meta.nlink(), size: meta.len(), atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), }) } fn get_path_filestat(&self, path: &str) -> Result { let meta = self.metadata(Path::new(path))?; use cap_fs_ext::MetadataExt; Ok(Filestat { device_id: meta.dev(), inode: meta.ino(), filetype: FileType::from(&meta.file_type()), nlink: meta.nlink(), size: meta.len(), atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), }) } fn rename(&self, src_path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error> { let dest_dir = dest_dir .as_any() .downcast_ref::() .ok_or(Error::NotCapable)?; self.rename(Path::new(src_path), dest_dir, Path::new(dest_path))?; Ok(()) } fn hard_link( &self, src_path: &str, symlink_follow: bool, target_dir: &dyn WasiDir, target_path: &str, ) -> Result<(), Error> { let target_dir = target_dir .as_any() .downcast_ref::() .ok_or(Error::NotCapable)?; let src_path = Path::new(src_path); let target_path = Path::new(target_path); self.hard_link(src_path, target_dir, target_path)?; Ok(()) } fn set_times( &self, path: &str, atime: Option, mtime: Option, ) -> Result<(), Error> { cap_fs_ext::DirExt::set_times(self, Path::new(path), atime, mtime)?; Ok(()) } }