diff --git a/crates/wasi-c2/src/ctx.rs b/crates/wasi-c2/src/ctx.rs index 3b823cbfe9..b4febbb5a8 100644 --- a/crates/wasi-c2/src/ctx.rs +++ b/crates/wasi-c2/src/ctx.rs @@ -1,4 +1,4 @@ -use crate::dir::{DirEntry, WasiDir}; +use crate::dir::{DirCaps, DirEntry, WasiDir}; use crate::file::{FileCaps, FileEntry, WasiFile}; use crate::table::Table; use std::cell::{RefCell, RefMut}; @@ -31,9 +31,17 @@ impl WasiCtx { self.table().insert_at(fd, e); } - pub fn insert_dir(&self, fd: u32, dir: Box, flags: u32, path: PathBuf) { + pub fn insert_dir( + &self, + fd: u32, + dir: Box, + base_caps: DirCaps, + inheriting_caps: DirCaps, + path: PathBuf, + ) { let e = DirEntry { - flags, + base_caps, + inheriting_caps, preopen_path: Some(path), dir, }; diff --git a/crates/wasi-c2/src/dir.rs b/crates/wasi-c2/src/dir.rs index ab1531dc0c..62bddebe21 100644 --- a/crates/wasi-c2/src/dir.rs +++ b/crates/wasi-c2/src/dir.rs @@ -1,15 +1,75 @@ // this file is extremely wip #![allow(dead_code, unused_variables)] +use crate::error::Error; +use crate::file::{self, WasiFile}; +use std::ops::Deref; use std::path::PathBuf; -pub trait WasiDir {} +pub trait WasiDir { + fn open_file( + &self, + symlink_follow: bool, + path: &str, + oflags: file::OFlags, + fdflags: file::FdFlags, + ) -> Result, Error>; + + fn open_dir( + &self, + symlink_follow: bool, + path: &str, + create: bool, + ) -> Result, Error>; +} pub(crate) struct DirEntry { - pub(crate) flags: u32, + pub(crate) base_caps: DirCaps, + pub(crate) inheriting_caps: DirCaps, pub(crate) preopen_path: Option, // precondition: PathBuf is valid unicode pub(crate) dir: Box, } +impl DirEntry { + pub fn get_cap(&self, caps: DirCaps) -> Result<&dyn WasiDir, Error> { + if self.base_caps.contains(&caps) && self.inheriting_caps.contains(&caps) { + Ok(self.dir.deref()) + } else { + Err(Error::DirNotCapable(caps)) + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct DirCaps { + flags: u32, +} + +impl DirCaps { + pub fn empty() -> Self { + DirCaps { flags: 0 } + } + + /// Checks if `other` is a subset of those capabilties: + pub fn contains(&self, other: &Self) -> bool { + self.flags & other.flags == other.flags + } + + pub const OPEN: Self = DirCaps { flags: 1 }; + pub const READDIR: Self = DirCaps { flags: 2 }; + pub const READLINK: Self = DirCaps { flags: 4 }; + pub const RENAME_SOURCE: Self = DirCaps { flags: 8 }; + pub const RENAME_TARGET: Self = DirCaps { flags: 16 }; + pub const SYMLINK: Self = DirCaps { flags: 32 }; + pub const REMOVE_DIRECTORY: Self = DirCaps { flags: 64 }; + pub const UNLINK_FILE: Self = DirCaps { flags: 128 }; +} + +impl std::fmt::Display for DirCaps { + fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { + todo!() + } +} + pub trait TableDirExt { fn is_preopen(&self, fd: u32) -> bool; } diff --git a/crates/wasi-c2/src/error.rs b/crates/wasi-c2/src/error.rs index e45e459504..f4a940283d 100644 --- a/crates/wasi-c2/src/error.rs +++ b/crates/wasi-c2/src/error.rs @@ -1,3 +1,4 @@ +use crate::dir::DirCaps; use crate::file::FileCaps; use thiserror::Error; @@ -19,6 +20,14 @@ pub enum Error { #[error("File not capable: {0}")] FileNotCapable(FileCaps), + /// Errno::Notcapable: Extension: Capabilities insufficient + #[error("Directory not capable: {0}")] + DirNotCapable(DirCaps), + + /// Idk what the deal with this guy is yet + #[error("Table overflow")] + TableOverflow, + /// The host OS may return an io error that doesn't match one of the /// wasi errno variants we expect. We do not expose the details of this /// error to the user. diff --git a/crates/wasi-c2/src/snapshots/preview_1.rs b/crates/wasi-c2/src/snapshots/preview_1.rs index edea23d845..7bd1bc923e 100644 --- a/crates/wasi-c2/src/snapshots/preview_1.rs +++ b/crates/wasi-c2/src/snapshots/preview_1.rs @@ -1,5 +1,5 @@ #![allow(unused_variables)] -use crate::dir::{DirEntry, TableDirExt}; +use crate::dir::{DirCaps, DirEntry, TableDirExt}; use crate::file::{ FdFlags, FdStat, FileCaps, FileEntry, Filestat, FilestatSetTime, Filetype, OFlags, }; @@ -73,7 +73,9 @@ impl From for types::Errno { Error::Perm => Errno::Perm, Error::Spipe => Errno::Spipe, Error::FileNotCapable { .. } => Errno::Notcapable, + Error::DirNotCapable { .. } => Errno::Notcapable, Error::NotCapable => Errno::Notcapable, + Error::TableOverflow => Errno::Overflow, } } } @@ -201,7 +203,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let table = self.table(); let file_entry: RefMut = table.get(u32::from(fd))?; let f = file_entry.get_cap(FileCaps::FDSTAT_SET_FLAGS)?; - f.set_oflags(OFlags::try_from(flags)?)?; + f.set_oflags(OFlags::try_from(&flags)?)?; Ok(()) } @@ -547,7 +549,45 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { fs_rights_inheriting: types::Rights, fdflags: types::Fdflags, ) -> Result { - unimplemented!() + let mut table = self.table(); + let dir_entry: RefMut = table.get(u32::from(dirfd))?; + let dir = dir_entry.get_cap(DirCaps::OPEN)?; + let symlink_follow = dirflags.contains(&types::Lookupflags::SYMLINK_FOLLOW); + let path = path.as_str()?; + if oflags.contains(&types::Oflags::DIRECTORY) { + let create = oflags.contains(&types::Oflags::CREAT); + let child_dir = dir.open_dir(symlink_follow, path.deref(), create)?; + + // XXX go back and check these caps conversions - probably need to validate them + // against ??? + let base_caps = DirCaps::try_from(&fs_rights_base)?; + let inheriting_caps = DirCaps::try_from(&fs_rights_inheriting)?; + drop(dir); + drop(dir_entry); + let fd = table.push(DirEntry { + dir: child_dir, + base_caps, + inheriting_caps, + preopen_path: None, + })?; + Ok(types::Fd::from(fd)) + } else { + let oflags = OFlags::try_from(&oflags)?; + let fdflags = FdFlags::try_from(&fdflags)?; + let file = dir.open_file(symlink_follow, path.deref(), oflags, fdflags)?; + // XXX go back and check these caps conversions - probably need to validate them + // against ??? + let base_caps = FileCaps::try_from(&fs_rights_base)?; + let inheriting_caps = FileCaps::try_from(&fs_rights_inheriting)?; + drop(dir); + drop(dir_entry); + let fd = table.push(FileEntry { + file, + base_caps, + inheriting_caps, + })?; + Ok(types::Fd::from(fd)) + } } fn path_readlink( @@ -678,6 +718,14 @@ impl TryFrom<&types::Rights> for FileCaps { } } +// DirCaps are a subset of wasi Rights - not all Rights have a valid representation as DirCaps +impl TryFrom<&types::Rights> for DirCaps { + type Error = Error; + fn try_from(rights: &types::Rights) -> Result { + todo!("translate Rights flags to DirCaps flags") + } +} + impl From<&Filetype> for types::Filetype { fn from(ft: &Filetype) -> types::Filetype { match ft { @@ -695,9 +743,26 @@ impl From<&FdFlags> for types::Fdflags { } } -impl TryFrom for OFlags { +impl TryFrom<&types::Oflags> for OFlags { type Error = Error; - fn try_from(fdflags: types::Fdflags) -> Result { + fn try_from(oflags: &types::Oflags) -> Result { + if oflags.contains(&types::Oflags::DIRECTORY) { + return Err(Error::Inval); + } + todo!("rest of oflags translation should be trivial - creat excl trunc") + } +} + +impl TryFrom<&types::Fdflags> for FdFlags { + type Error = Error; + fn try_from(fdflags: &types::Fdflags) -> Result { + todo!() + } +} + +impl TryFrom<&types::Fdflags> for OFlags { + type Error = Error; + fn try_from(fdflags: &types::Fdflags) -> Result { todo!() } } diff --git a/crates/wasi-c2/src/table.rs b/crates/wasi-c2/src/table.rs index c97721a584..0f13d20893 100644 --- a/crates/wasi-c2/src/table.rs +++ b/crates/wasi-c2/src/table.rs @@ -20,15 +20,17 @@ impl Table { self.map.insert(key, RefCell::new(Box::new(a))); } - pub fn push(&mut self, a: impl Any + Sized) -> u32 { + pub fn push(&mut self, a: impl Any + Sized) -> Result { loop { let key = self.next_key; - self.next_key += 1; + // XXX this is not correct. The table may still have empty entries, but our + // linear search strategy is quite bad + self.next_key = self.next_key.checked_add(1).ok_or(Error::TableOverflow)?; if self.map.contains_key(&key) { continue; } self.map.insert(key, RefCell::new(Box::new(a))); - return key; + return Ok(key); } }