diff --git a/Cargo.lock b/Cargo.lock index ae8c74214e..d015e15345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2263,6 +2263,7 @@ dependencies = [ "target-lexicon", "tempfile", "wasi-c2", + "wasi-c2-cap-std-sync", "wasi-c2-wasmtime", "wasmtime", "wat", @@ -2529,6 +2530,22 @@ dependencies = [ "yanix 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wasi-c2-cap-std-sync" +version = "0.22.0" +dependencies = [ + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "libc", + "system-interface", + "tracing", + "wasi-c2", + "yanix 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi-c2-wasmtime" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index e4821d5d6f..bd3831db3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ members = [ "crates/wiggle/wasmtime", "crates/wasi-c2", "crates/wasi-c2/wasmtime", + "crates/wasi-c2/cap-std-sync", "examples/fib-debug/wasm", "examples/wasi/wasm", "examples/wasi-fs/wasm", diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 8de4c6fa96..59e0a0bd5c 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -13,6 +13,7 @@ cfg-if = "1.0" [dev-dependencies] wasi-c2 = { path = "../wasi-c2", version = "0.22.0" } wasi-c2-wasmtime = { path = "../wasi-c2/wasmtime", version = "0.22.0" } +wasi-c2-cap-std-sync = { path = "../wasi-c2/cap-std-sync", version = "0.22.0" } wasmtime = { path = "../wasmtime", version = "0.22.0" } target-lexicon = "0.11.0" pretty_env_logger = "0.4.0" diff --git a/crates/test-programs/tests/wasm_tests/runtime.rs b/crates/test-programs/tests/wasm_tests/runtime.rs index 0fae446bdc..8ceaddcc5a 100644 --- a/crates/test-programs/tests/wasm_tests/runtime.rs +++ b/crates/test-programs/tests/wasm_tests/runtime.rs @@ -1,30 +1,34 @@ use anyhow::Context; use std::fs::File; use std::path::Path; -use wasi_c2::virt::pipe::{ReadPipe, WritePipe}; +use wasi_c2::pipe::{ReadPipe, WritePipe}; +use wasi_c2_cap_std_sync::WasiCtxBuilder; use wasmtime::{Linker, Module, Store}; pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { - /* let stdout = WritePipe::new_in_memory(); let stderr = WritePipe::new_in_memory(); - */ let r = { let store = Store::default(); // Create our wasi context. // Additionally register any preopened directories if we have them. - let mut builder = wasi_c2::WasiCtx::builder(); + let mut builder = WasiCtxBuilder::new(); - builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio(); + builder = builder + .arg(bin_name)? + .arg(".")? + .stdin(Box::new(ReadPipe::from(Vec::new()))) + .stdout(Box::new(stdout.clone())) + .stderr(Box::new(stderr.clone())); if let Some(workspace) = workspace { println!("preopen: {:?}", workspace); let dirfd = File::open(workspace).context(format!("error while preopening {:?}", workspace))?; let preopen_dir = unsafe { cap_std::fs::Dir::from_std_file(dirfd) }; - builder = builder.preopened_dir(Box::new(preopen_dir), ".")?; + builder = builder.preopened_dir(preopen_dir, ".")?; } let snapshot1 = wasi_c2_wasmtime::Wasi::new(&store, builder.build()?); @@ -43,7 +47,6 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any match r { Ok(()) => Ok(()), Err(trap) => { - /* let stdout = stdout .try_into_inner() .expect("sole ref to stdout") @@ -58,7 +61,6 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any if !stderr.is_empty() { println!("guest stderr:\n{}\n===", String::from_utf8_lossy(&stderr)); } - */ Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))) } } diff --git a/crates/wasi-c2/cap-std-sync/Cargo.toml b/crates/wasi-c2/cap-std-sync/Cargo.toml new file mode 100644 index 0000000000..3aaaf65314 --- /dev/null +++ b/crates/wasi-c2/cap-std-sync/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "wasi-c2-cap-std-sync" +version = "0.22.0" +authors = ["The Wasmtime Project Developers"] +description = "WASI implementation in Rust" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/bytecodealliance/wasmtime" +readme = "README.md" +edition = "2018" +include = ["src/**/*", "LICENSE" ] +publish = false + +[dependencies] +wasi-c2 = { path = "../" } +cap-std = "0.9" +cap-fs-ext = "0.9" +cap-time-ext = "0.9" +cap-rand = "0.9" +fs-set-times = "0.2.2" +yanix = "0.22" +system-interface = { version = "0.5", features = ["cap_std_impls"] } +tracing = "0.1.19" +libc = "0.2" diff --git a/crates/wasi-c2/cap-std-sync/src/dir.rs b/crates/wasi-c2/cap-std-sync/src/dir.rs new file mode 100644 index 0000000000..bf9990f3db --- /dev/null +++ b/crates/wasi-c2/cap-std-sync/src/dir.rs @@ -0,0 +1,222 @@ +use crate::file::File; +use cap_fs_ext::{DirExt, MetadataExt, SystemTimeSpec}; +use std::any::Any; +use std::convert::TryInto; +use std::path::{Path, PathBuf}; +use wasi_c2::{ + dir::{ReaddirCursor, ReaddirEntity, WasiDir}, + file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile}, + Error, +}; + +pub struct Dir(cap_std::fs::Dir); + +impl Dir { + pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self { + Dir(dir) + } +} + +impl WasiDir for 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.0.open_with(Path::new(path), &opts)?; + Ok(Box::new(File::from_cap_std(f))) + } + + fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { + let d = if symlink_follow { + self.0.open_dir(Path::new(path))? + } else { + self.0.open_dir_nofollow(Path::new(path))? + }; + Ok(Box::new(Dir::from_cap_std(d))) + } + + fn create_dir(&self, path: &str) -> Result<(), Error> { + self.0.create_dir(Path::new(path))?; + Ok(()) + } + fn readdir( + &self, + cursor: ReaddirCursor, + ) -> Result>>, Error> { + // 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.0.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.0.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.0.symlink(src_path, dest_path)?; + Ok(()) + } + fn remove_dir(&self, path: &str) -> Result<(), Error> { + self.0.remove_dir(Path::new(path))?; + Ok(()) + } + + fn unlink_file(&self, path: &str) -> Result<(), Error> { + self.0.remove_file(Path::new(path))?; + Ok(()) + } + fn read_link(&self, path: &str) -> Result { + let link = self.0.read_link(Path::new(path))?; + Ok(link) + } + fn get_filestat(&self) -> Result { + let meta = self.0.dir_metadata()?; + 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.0.metadata(Path::new(path))?; + 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.0 + .rename(Path::new(src_path), &dest_dir.0, 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.0.hard_link(src_path, &target_dir.0, target_path)?; + Ok(()) + } + fn set_times( + &self, + path: &str, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0.set_times(Path::new(path), atime, mtime)?; + Ok(()) + } +} diff --git a/crates/wasi-c2/cap-std-sync/src/file.rs b/crates/wasi-c2/cap-std-sync/src/file.rs new file mode 100644 index 0000000000..d29804c1a6 --- /dev/null +++ b/crates/wasi-c2/cap-std-sync/src/file.rs @@ -0,0 +1,136 @@ +use cap_fs_ext::MetadataExt; +use fs_set_times::SetTimes; +use std::any::Any; +use std::io; +use system_interface::fs::{Advice, FileIoExt}; +use system_interface::io::ReadReady; +use wasi_c2::{ + file::{FdFlags, FileType, Filestat, WasiFile}, + Error, +}; + +pub struct File(cap_std::fs::File); + +impl File { + pub fn from_cap_std(file: cap_std::fs::File) -> Self { + File(file) + } +} + +impl WasiFile for File { + fn as_any(&self) -> &dyn Any { + self + } + fn datasync(&self) -> Result<(), Error> { + self.0.sync_data()?; + Ok(()) + } + fn sync(&self) -> Result<(), Error> { + self.0.sync_all()?; + Ok(()) + } + fn get_filetype(&self) -> Result { + let meta = self.0.metadata()?; + Ok(FileType::from(&meta.file_type())) + } + fn get_fdflags(&self) -> Result { + // XXX get_fdflags is not implemented but lets lie rather than panic: + Ok(FdFlags::empty()) + } + fn set_fdflags(&self, _fdflags: FdFlags) -> Result<(), Error> { + todo!("set_fdflags is not implemented") + } + fn get_filestat(&self) -> Result { + let meta = self.0.metadata()?; + 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 set_filestat_size(&self, size: u64) -> Result<(), Error> { + self.0.set_len(size)?; + Ok(()) + } +} + +impl FileIoExt for File { + fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> { + self.0.advise(offset, len, advice) + } + fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + self.0.allocate(offset, len) + } + fn read(&self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + fn read_exact(&self, buf: &mut [u8]) -> io::Result<()> { + self.0.read_exact(buf) + } + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + self.0.read_at(buf, offset) + } + fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> { + self.0.read_exact_at(buf, offset) + } + fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> io::Result { + self.0.read_vectored(bufs) + } + fn read_to_end(&self, buf: &mut Vec) -> io::Result { + self.0.read_to_end(buf) + } + fn read_to_string(&self, buf: &mut String) -> io::Result { + self.0.read_to_string(buf) + } + fn write(&self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + fn write_all(&self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + self.0.write_at(buf, offset) + } + fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> { + self.0.write_all_at(buf, offset) + } + fn write_vectored(&self, bufs: &[io::IoSlice]) -> io::Result { + self.0.write_vectored(bufs) + } + fn write_fmt(&self, fmt: std::fmt::Arguments) -> io::Result<()> { + self.0.write_fmt(fmt) + } + fn flush(&self) -> io::Result<()> { + self.0.flush() + } + fn seek(&self, pos: std::io::SeekFrom) -> io::Result { + self.0.seek(pos) + } + fn stream_position(&self) -> io::Result { + self.0.stream_position() + } + fn peek(&self, buf: &mut [u8]) -> io::Result { + self.0.peek(buf) + } +} + +impl SetTimes for File { + fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> io::Result<()> { + self.0.set_times(atime, mtime) + } +} + +impl ReadReady for File { + fn num_ready_bytes(&self) -> io::Result { + self.0.num_ready_bytes() + } +} diff --git a/crates/wasi-c2/cap-std-sync/src/lib.rs b/crates/wasi-c2/cap-std-sync/src/lib.rs new file mode 100644 index 0000000000..75e5ed4e71 --- /dev/null +++ b/crates/wasi-c2/cap-std-sync/src/lib.rs @@ -0,0 +1,68 @@ +pub mod dir; +pub mod file; +pub mod sched; +pub mod stdio; + +use cap_rand::RngCore; +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; +use wasi_c2::{clocks::WasiClocks, table::Table, Error, WasiCtx, WasiFile}; + +pub struct WasiCtxBuilder(wasi_c2::WasiCtxBuilder); + +impl WasiCtxBuilder { + pub fn new() -> Self { + WasiCtxBuilder(WasiCtx::builder( + random(), + clocks(), + Box::new(sched::SyncSched), + Rc::new(RefCell::new(Table::new())), + )) + } + pub fn arg(self, arg: &str) -> Result { + let s = self.0.arg(arg)?; + Ok(WasiCtxBuilder(s)) + } + pub fn stdin(self, f: Box) -> Self { + WasiCtxBuilder(self.0.stdin(f)) + } + pub fn stdout(self, f: Box) -> Self { + WasiCtxBuilder(self.0.stdout(f)) + } + pub fn stderr(self, f: Box) -> Self { + WasiCtxBuilder(self.0.stderr(f)) + } + pub fn inherit_stdio(self) -> Self { + self.stdin(Box::new(crate::stdio::stdin())) + .stdout(Box::new(crate::stdio::stdout())) + .stderr(Box::new(crate::stdio::stderr())) + } + pub fn preopened_dir( + self, + dir: cap_std::fs::Dir, + path: impl AsRef, + ) -> Result { + let dir = Box::new(crate::dir::Dir::from_cap_std(dir)); + Ok(WasiCtxBuilder(self.0.preopened_dir(dir, path)?)) + } + pub fn build(self) -> Result { + self.0.build() + } +} + +pub fn clocks() -> WasiClocks { + let system = Box::new(unsafe { cap_std::time::SystemClock::new() }); + let monotonic = unsafe { cap_std::time::MonotonicClock::new() }; + let creation_time = monotonic.now(); + let monotonic = Box::new(monotonic); + WasiClocks { + system, + monotonic, + creation_time, + } +} + +pub fn random() -> RefCell> { + RefCell::new(Box::new(unsafe { cap_rand::rngs::OsRng::default() })) +} diff --git a/crates/wasi-c2/src/sched/sync.rs b/crates/wasi-c2/cap-std-sync/src/sched.rs similarity index 80% rename from crates/wasi-c2/src/sched/sync.rs rename to crates/wasi-c2/cap-std-sync/src/sched.rs index db2c65e5e2..1fd930f937 100644 --- a/crates/wasi-c2/src/sched/sync.rs +++ b/crates/wasi-c2/cap-std-sync/src/sched.rs @@ -3,18 +3,17 @@ pub use unix::*; #[cfg(unix)] mod unix { - use crate::file::WasiFile; - use crate::sched::subscription::{RwEventFlags, Subscription}; - use crate::sched::{Poll, WasiSched}; - use crate::Error; use cap_std::time::Duration; use std::convert::TryInto; use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; + use wasi_c2::file::WasiFile; + use wasi_c2::sched::subscription::{RwEventFlags, Subscription}; + use wasi_c2::sched::{Poll, WasiSched}; + use wasi_c2::Error; use yanix::poll::{PollFd, PollFlags}; - #[derive(Default)] - pub struct SyncSched {} + pub struct SyncSched; impl WasiSched for SyncSched { fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> { @@ -106,22 +105,25 @@ mod unix { fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option { let a = f.as_any(); - if a.is::() { - Some(a.downcast_ref::().unwrap().as_raw_fd()) - } else if a.is::() { - Some(a.downcast_ref::().unwrap().as_raw_fd()) - } else if a.is::() { - Some( - a.downcast_ref::() - .unwrap() - .as_raw_fd(), - ) - } else if a.is::() { - Some( - a.downcast_ref::() - .unwrap() - .as_raw_fd(), - ) + if a.is::() { + /* DISABLED UNTIL AsRawFd can be implemented properly + Some(a.downcast_ref::().unwrap().as_raw_fd()) + } else if a.is::() { + Some(a.downcast_ref::().unwrap().as_raw_fd()) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_fd(), + ) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_fd(), + ) + */ + None } else { None } diff --git a/crates/wasi-c2/cap-std-sync/src/stdio.rs b/crates/wasi-c2/cap-std-sync/src/stdio.rs new file mode 100644 index 0000000000..46fe2b19d6 --- /dev/null +++ b/crates/wasi-c2/cap-std-sync/src/stdio.rs @@ -0,0 +1,63 @@ +use wasi_c2::pipe::{ReadPipe, WritePipe}; + +pub type Stdin = ReadPipe; + +pub fn stdin() -> Stdin { + ReadPipe::new(std::io::stdin()) +} + +pub type Stdout = WritePipe; + +pub fn stdout() -> Stdout { + WritePipe::new(std::io::stdout()) +} + +pub type Stderr = WritePipe; + +pub fn stderr() -> Stderr { + WritePipe::new(std::io::stderr()) +} + +/* +#[cfg(windows)] +mod windows { + use super::*; + use std::os::windows::io::{AsRawHandle, RawHandle}; + impl AsRawHandle for Stdin { + fn as_raw_handle(&self) -> RawHandle { + self.borrow().as_raw_handle() + } + } + impl AsRawHandle for Stdout { + fn as_raw_handle(&self) -> RawHandle { + self.borrow().as_raw_handle() + } + } + impl AsRawHandle for Stderr { + fn as_raw_handle(&self) -> RawHandle { + self.borrow().as_raw_handle() + } + } +} + +#[cfg(unix)] +mod unix { + use super::*; + use std::os::unix::io::{AsRawFd, RawFd}; + impl AsRawFd for Stdin { + fn as_raw_fd(&self) -> RawFd { + self.borrow().as_raw_fd() + } + } + impl AsRawFd for Stdout { + fn as_raw_fd(&self) -> RawFd { + self.borrow().as_raw_fd() + } + } + impl AsRawFd for Stderr { + fn as_raw_fd(&self) -> RawFd { + self.borrow().as_raw_fd() + } + } +} +*/ diff --git a/crates/wasi-c2/src/clocks.rs b/crates/wasi-c2/src/clocks.rs index 7561e91b48..729ced1dd5 100644 --- a/crates/wasi-c2/src/clocks.rs +++ b/crates/wasi-c2/src/clocks.rs @@ -28,3 +28,9 @@ impl WasiMonotonicClock for MonotonicClock { self.now_with(precision) } } + +pub struct WasiClocks { + pub system: Box, + pub monotonic: Box, + pub creation_time: cap_std::time::Instant, +} diff --git a/crates/wasi-c2/src/ctx.rs b/crates/wasi-c2/src/ctx.rs index 5c3e4ba1e4..d1ee783b1c 100644 --- a/crates/wasi-c2/src/ctx.rs +++ b/crates/wasi-c2/src/ctx.rs @@ -1,7 +1,7 @@ -use crate::clocks::{WasiMonotonicClock, WasiSystemClock}; +use crate::clocks::WasiClocks; use crate::dir::{DirCaps, DirEntry, WasiDir}; use crate::file::{FileCaps, FileEntry, WasiFile}; -use crate::sched::{SyncSched, WasiSched}; +use crate::sched::WasiSched; use crate::string_array::{StringArray, StringArrayError}; use crate::table::Table; use crate::Error; @@ -11,28 +11,29 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; pub struct WasiCtx { - pub(crate) args: StringArray, - pub(crate) env: StringArray, - pub(crate) random: RefCell>, - pub(crate) clocks: WasiCtxClocks, - pub(crate) sched: Box, - table: Rc>, + pub args: StringArray, + pub env: StringArray, + pub random: RefCell>, + pub clocks: WasiClocks, + pub sched: Box, + pub table: Rc>, } impl WasiCtx { - pub fn builder() -> WasiCtxBuilder { - WasiCtxBuilder(WasiCtx::new()) - } - - pub fn new() -> Self { - WasiCtx { + pub fn builder( + random: RefCell>, + clocks: WasiClocks, + sched: Box, + table: Rc>, + ) -> WasiCtxBuilder { + WasiCtxBuilder(WasiCtx { args: StringArray::new(), env: StringArray::new(), - random: RefCell::new(Box::new(unsafe { cap_rand::rngs::OsRng::default() })), - clocks: WasiCtxClocks::default(), - sched: Box::new(SyncSched::default()), - table: Rc::new(RefCell::new(Table::new())), - } + random, + clocks, + sched, + table, + }) } pub fn insert_file(&self, fd: u32, file: Box, caps: FileCaps) { @@ -98,12 +99,6 @@ impl WasiCtxBuilder { self } - pub fn inherit_stdio(self) -> Self { - self.stdin(Box::new(crate::stdio::stdin())) - .stdout(Box::new(crate::stdio::stdout())) - .stderr(Box::new(crate::stdio::stderr())) - } - pub fn preopened_dir( self, dir: Box, @@ -119,29 +114,4 @@ impl WasiCtxBuilder { )))?; Ok(self) } - - pub fn random(self, random: Box) -> Self { - self.0.random.replace(random); - self - } -} - -pub struct WasiCtxClocks { - pub(crate) system: Box, - pub(crate) monotonic: Box, - pub(crate) creation_time: cap_std::time::Instant, -} - -impl Default for WasiCtxClocks { - fn default() -> WasiCtxClocks { - let system = Box::new(unsafe { cap_std::time::SystemClock::new() }); - let monotonic = unsafe { cap_std::time::MonotonicClock::new() }; - let creation_time = monotonic.now(); - let monotonic = Box::new(monotonic); - WasiCtxClocks { - system, - monotonic, - creation_time, - } - } } diff --git a/crates/wasi-c2/src/dir.rs b/crates/wasi-c2/src/dir.rs index 0325b69fdd..cb46c916e5 100644 --- a/crates/wasi-c2/src/dir.rs +++ b/crates/wasi-c2/src/dir.rs @@ -4,9 +4,8 @@ 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}; +use std::path::PathBuf; pub trait WasiDir { fn as_any(&self) -> &dyn Any; @@ -187,211 +186,3 @@ impl From for 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(()) - } -} diff --git a/crates/wasi-c2/src/file.rs b/crates/wasi-c2/src/file.rs index d273b722a5..c94dd6a6dd 100644 --- a/crates/wasi-c2/src/file.rs +++ b/crates/wasi-c2/src/file.rs @@ -166,46 +166,3 @@ pub struct FdStat { pub caps: FileCaps, pub flags: FdFlags, } - -impl WasiFile for cap_std::fs::File { - fn as_any(&self) -> &dyn Any { - self - } - fn datasync(&self) -> Result<(), Error> { - self.sync_data()?; - Ok(()) - } - fn sync(&self) -> Result<(), Error> { - self.sync_all()?; - Ok(()) - } - fn get_filetype(&self) -> Result { - let meta = self.metadata()?; - Ok(FileType::from(&meta.file_type())) - } - fn get_fdflags(&self) -> Result { - // XXX get_fdflags is not implemented but lets lie rather than panic: - Ok(FdFlags::empty()) - } - fn set_fdflags(&self, _fdflags: FdFlags) -> Result<(), Error> { - todo!("set_fdflags is not implemented") - } - fn get_filestat(&self) -> Result { - let meta = self.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 set_filestat_size(&self, size: u64) -> Result<(), Error> { - self.set_len(size)?; - Ok(()) - } -} diff --git a/crates/wasi-c2/src/lib.rs b/crates/wasi-c2/src/lib.rs index 7aa79d3d9e..6fbb269df1 100644 --- a/crates/wasi-c2/src/lib.rs +++ b/crates/wasi-c2/src/lib.rs @@ -2,16 +2,15 @@ pub mod clocks; mod ctx; -mod dir; +pub mod dir; mod error; -mod file; +pub mod file; +pub mod pipe; pub mod random; pub mod sched; pub mod snapshots; -pub mod stdio; mod string_array; pub mod table; -pub mod virt; pub use cap_fs_ext::SystemTimeSpec; pub use ctx::{WasiCtx, WasiCtxBuilder}; diff --git a/crates/wasi-c2/src/virt/pipe.rs b/crates/wasi-c2/src/pipe.rs similarity index 100% rename from crates/wasi-c2/src/virt/pipe.rs rename to crates/wasi-c2/src/pipe.rs diff --git a/crates/wasi-c2/src/sched.rs b/crates/wasi-c2/src/sched.rs index bb678c5d8b..db2a81f9db 100644 --- a/crates/wasi-c2/src/sched.rs +++ b/crates/wasi-c2/src/sched.rs @@ -5,9 +5,6 @@ use cap_std::time::{Duration, Instant}; use std::cell::Ref; pub mod subscription; -mod sync; -pub use sync::SyncSched; - use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult}; pub trait WasiSched { diff --git a/crates/wasi-c2/src/stdio.rs b/crates/wasi-c2/src/stdio.rs deleted file mode 100644 index 194c9e3f1f..0000000000 --- a/crates/wasi-c2/src/stdio.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::file::{FdFlags, FileType, Filestat, WasiFile}; -use crate::Error; -use std::any::Any; -#[cfg(unix)] -use std::os::unix::io::{AsRawFd, RawFd}; -use system_interface::io::ReadReady; - -pub struct Stdin(std::io::Stdin); - -pub fn stdin() -> Stdin { - Stdin(std::io::stdin()) -} - -#[cfg(unix)] -impl AsRawFd for Stdin { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl ReadReady for Stdin { - fn num_ready_bytes(&self) -> Result { - self.0.num_ready_bytes() - } -} - -impl WasiFile for Stdin { - fn as_any(&self) -> &dyn Any { - self - } - fn datasync(&self) -> Result<(), Error> { - Ok(()) - } - fn sync(&self) -> Result<(), Error> { - Ok(()) - } - fn get_filetype(&self) -> Result { - Ok(FileType::CharacterDevice) - } - fn get_fdflags(&self) -> Result { - todo!() - } - fn set_fdflags(&self, _fdflags: FdFlags) -> Result<(), Error> { - todo!() - } - fn get_filestat(&self) -> Result { - todo!() - } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { - todo!() - } -} - -pub struct Stdout(std::io::Stdout); - -pub fn stdout() -> Stdout { - Stdout(std::io::stdout()) -} - -#[cfg(unix)] -impl AsRawFd for Stdout { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl ReadReady for Stdout { - fn num_ready_bytes(&self) -> Result { - Ok(0) - } -} - -impl WasiFile for Stdout { - fn as_any(&self) -> &dyn Any { - self - } - fn datasync(&self) -> Result<(), Error> { - Ok(()) - } - fn sync(&self) -> Result<(), Error> { - Ok(()) - } - fn get_filetype(&self) -> Result { - Ok(FileType::CharacterDevice) - } - fn get_fdflags(&self) -> Result { - todo!() - } - fn set_fdflags(&self, _fdflags: FdFlags) -> Result<(), Error> { - todo!() - } - fn get_filestat(&self) -> Result { - todo!() - } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { - todo!() - } -} - -pub struct Stderr(std::io::Stderr); - -pub fn stderr() -> Stderr { - Stderr(std::io::stderr()) -} - -#[cfg(unix)] -impl AsRawFd for Stderr { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl ReadReady for Stderr { - fn num_ready_bytes(&self) -> Result { - Ok(0) - } -} - -impl WasiFile for Stderr { - fn as_any(&self) -> &dyn Any { - self - } - fn datasync(&self) -> Result<(), Error> { - Ok(()) - } - fn sync(&self) -> Result<(), Error> { - Ok(()) - } - fn get_filetype(&self) -> Result { - Ok(FileType::CharacterDevice) - } - fn get_fdflags(&self) -> Result { - todo!() - } - fn set_fdflags(&self, _fdflags: FdFlags) -> Result<(), Error> { - todo!() - } - fn get_filestat(&self) -> Result { - todo!() - } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { - todo!() - } -} diff --git a/crates/wasi-c2/src/virt/mod.rs b/crates/wasi-c2/src/virt/mod.rs deleted file mode 100644 index 97a59c2c4e..0000000000 --- a/crates/wasi-c2/src/virt/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod pipe;