diff --git a/Cargo.lock b/Cargo.lock index b2a87ecbfe..b8bf5efaa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3124,6 +3124,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "wasi-virtfs" +version = "0.22.0" +dependencies = [ + "anyhow", + "cap-std", + "wasi-common", +] + [[package]] name = "wasm-encoder" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 4321fda023..376a40bea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ members = [ "crates/wiggle/wasmtime", "crates/wasi-common", "crates/wasi-common/cap-std-sync", + "crates/wasi-common/virtfs", "examples/fib-debug/wasm", "examples/wasi/wasm", "fuzz", diff --git a/crates/wasi-common/virtfs/Cargo.toml b/crates/wasi-common/virtfs/Cargo.toml new file mode 100644 index 0000000000..4a760434a6 --- /dev/null +++ b/crates/wasi-common/virtfs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "wasi-virtfs" +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-common = { path = "../", version = "0.22.0" } +anyhow = "1.0" +cap-std = "0.12" diff --git a/crates/wasi-common/virtfs/src/dir.rs b/crates/wasi-common/virtfs/src/dir.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/wasi-common/virtfs/src/file.rs b/crates/wasi-common/virtfs/src/file.rs new file mode 100644 index 0000000000..f76de7c85a --- /dev/null +++ b/crates/wasi-common/virtfs/src/file.rs @@ -0,0 +1,84 @@ +use crate::FileInode; +use std::any::Any; +use std::cell::RefCell; +use std::io; +use std::rc::Rc; +use wasi_common::{ + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, +}; + +enum FileMode { + ReadOnly, + WriteOnly { append: bool }, + ReadWrite, +} + +pub struct File { + inode: Rc>, + cursor: RefCell, + mode: FileMode, +} + +impl File {} + +impl WasiFile for File { + fn as_any(&self) -> &dyn Any { + self + } + fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + fn sync(&self) -> Result<(), Error> { + Ok(()) + } + fn get_filetype(&self) -> Result { + todo!() + } + fn get_fdflags(&self) -> Result { + todo!() + } + fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + todo!() + } + fn get_filestat(&self) -> Result { + todo!() + } + fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + todo!() + } + fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Ok(()) + } + fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + todo!() + } + fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + todo!() + } + fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result { + todo!() + } + fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result { + todo!() + } + fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result { + todo!() + } + fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result { + todo!() + } + fn seek(&self, pos: std::io::SeekFrom) -> Result { + todo!() + } + fn peek(&self, buf: &mut [u8]) -> Result { + todo!() + } + fn num_ready_bytes(&self) -> Result { + todo!() + } +} diff --git a/crates/wasi-common/virtfs/src/lib.rs b/crates/wasi-common/virtfs/src/lib.rs new file mode 100644 index 0000000000..9f000ca57a --- /dev/null +++ b/crates/wasi-common/virtfs/src/lib.rs @@ -0,0 +1,316 @@ +#![allow(dead_code, unused_variables, unused_imports)] +use cap_std::time::{Duration, SystemTime}; +use std::any::Any; +use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::collections::HashMap; +use std::convert::TryInto; +use std::io::{Cursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; +use std::ops::Deref; +use std::path::PathBuf; +use std::rc::{Rc, Weak}; +use wasi_common::{ + clocks::WasiSystemClock, + dir::{ReaddirCursor, ReaddirEntity, WasiDir}, + file::{Advice, FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile}, + Error, ErrorExt, SystemTimeSpec, +}; + +pub struct Filesystem { + root: Rc>, + clock: Box, + device_id: u64, + next_serial: Cell, +} + +pub enum Inode { + Dir(Rc>), + File(Rc>), +} + +pub struct DirInode { + fs: Weak, + serial: u64, + parent: Option>>, + contents: HashMap, + atim: SystemTime, + mtim: SystemTime, + ctim: SystemTime, +} + +impl DirInode { + pub fn fs(&self) -> Rc { + Weak::upgrade(&self.fs).unwrap() + } +} + +pub struct FileInode { + fs: Weak, + serial: u64, + contents: Vec, + atim: SystemTime, + mtim: SystemTime, + ctim: SystemTime, +} + +impl FileInode { + pub fn fs(&self) -> Rc { + Weak::upgrade(&self.fs).unwrap() + } +} + +enum FileMode { + ReadOnly, + WriteOnly, + ReadWrite, +} + +pub struct File { + inode: Rc>, + position: Cell, + fdflags: FdFlags, + mode: FileMode, +} + +impl File { + fn is_read(&self) -> bool { + match self.mode { + FileMode::ReadOnly | FileMode::ReadWrite => true, + _ => false, + } + } + fn is_write(&self) -> bool { + match self.mode { + FileMode::WriteOnly | FileMode::ReadWrite => true, + _ => false, + } + } + fn is_append(&self) -> bool { + self.fdflags.contains(FdFlags::APPEND) + } + fn inode(&self) -> Ref { + self.inode.borrow() + } + fn inode_mut(&self) -> RefMut { + self.inode.borrow_mut() + } +} + +impl WasiFile for File { + 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::RegularFile) + } + fn get_fdflags(&self) -> Result { + Ok(self.fdflags) + } + fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + self.fdflags = fdflags; + Ok(()) + } + fn get_filestat(&self) -> Result { + let inode = self.inode(); + let fs = inode.fs(); + Ok(Filestat { + device_id: fs.device_id, + inode: inode.serial, + filetype: self.get_filetype().unwrap(), + nlink: 0, + size: inode.contents.len() as u64, + atim: Some(inode.atim.into_std()), + ctim: Some(inode.ctim.into_std()), + mtim: Some(inode.mtim.into_std()), + }) + } + fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + let mut inode = self.inode.borrow_mut(); + inode.contents.resize(size.try_into()?, 0); + Ok(()) + } + fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Ok(()) + } + fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + let mut inode = self.inode.borrow_mut(); + let size = offset.checked_add(len).ok_or_else(|| Error::overflow())?; + if size > inode.contents.len() as u64 { + inode.contents.resize(size.try_into()?, 0); + } + Ok(()) + } + fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + let newtime = |s| match s { + SystemTimeSpec::SymbolicNow => self.inode().fs().clock.now(Duration::from_secs(0)), + SystemTimeSpec::Absolute(t) => t, + }; + let mut inode = self.inode.borrow_mut(); + if let Some(atim) = atime { + inode.atim = newtime(atim); + } + if let Some(mtim) = mtime { + inode.mtim = newtime(mtim); + } + Ok(()) + } + fn read_vectored(&self, bufs: &mut [IoSliceMut]) -> Result { + if !self.is_read() { + return Err(Error::badf()); + } + let inode = self.inode(); + let mut cursor = Cursor::new(inode.contents.as_slice()); + cursor.set_position(self.position.get()); + let nbytes = cursor.read_vectored(bufs)?; + self.position.set(cursor.position()); + Ok(nbytes.try_into()?) + } + fn read_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> Result { + if !self.is_read() { + return Err(Error::badf()); + } + let inode = self.inode(); + let mut cursor = Cursor::new(inode.contents.as_slice()); + cursor.set_position(offset); + let nbytes = cursor.read_vectored(bufs)?; + Ok(nbytes.try_into()?) + } + fn write_vectored(&self, bufs: &[IoSlice]) -> Result { + if !self.is_write() { + return Err(Error::badf()); + } + let mut inode = self.inode_mut(); + let mut cursor = Cursor::new(&mut inode.contents); + cursor.set_position(self.position.get()); + let nbytes = cursor.write_vectored(bufs)?; + self.position.set(cursor.position()); + Ok(nbytes.try_into()?) + } + fn write_vectored_at(&self, bufs: &[IoSlice], offset: u64) -> Result { + if !self.is_write() || self.is_append() { + return Err(Error::badf()); + } + let mut inode = self.inode_mut(); + let mut cursor = Cursor::new(&mut inode.contents); + cursor.set_position(offset); + let nbytes = cursor.write_vectored(bufs)?; + self.position.set(cursor.position()); + Ok(nbytes.try_into()?) + } + fn seek(&self, pos: SeekFrom) -> Result { + if self.is_append() { + match pos { + SeekFrom::Current(0) => return Ok(self.position.get()), + _ => return Err(Error::badf()), + } + } + let inode = self.inode(); + let mut cursor = Cursor::new(inode.contents.as_slice()); + cursor.set_position(self.position.get()); + cursor.seek(pos)?; + let new_position = cursor.position(); + self.position.set(new_position); + Ok(new_position) + } + fn peek(&self, buf: &mut [u8]) -> Result { + if !self.is_read() { + return Err(Error::badf()); + } + let inode = self.inode(); + let mut cursor = Cursor::new(inode.contents.as_slice()); + cursor.set_position(self.position.get()); + let nbytes = cursor.read(buf)?; + Ok(nbytes.try_into()?) + } + fn num_ready_bytes(&self) -> Result { + if !self.is_read() { + return Err(Error::badf()); + } + let len = self.inode().contents.len() as u64; + Ok(len - self.position.get()) + } +} + +pub struct Dir; + +impl Dir {} + +impl WasiDir for Dir { + fn as_any(&self) -> &dyn Any { + self + } + fn open_file( + &self, + symlink_follow: bool, + path: &str, + oflags: OFlags, + read: bool, + write: bool, + fdflags: FdFlags, + ) -> Result, Error> { + todo!() + } + + fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { + todo!() + } + + fn create_dir(&self, path: &str) -> Result<(), Error> { + todo!() + } + fn readdir( + &self, + cursor: ReaddirCursor, + ) -> Result>>, Error> { + todo!() + } + + fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { + todo!() + } + fn remove_dir(&self, path: &str) -> Result<(), Error> { + todo!() + } + + fn unlink_file(&self, path: &str) -> Result<(), Error> { + todo!() + } + fn read_link(&self, path: &str) -> Result { + todo!() + } + fn get_filestat(&self) -> Result { + todo!() + } + fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result { + todo!() + } + fn rename(&self, src_path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error> { + todo!() + } + fn hard_link( + &self, + src_path: &str, + target_dir: &dyn WasiDir, + target_path: &str, + ) -> Result<(), Error> { + todo!() + } + fn set_times( + &self, + path: &str, + atime: Option, + mtime: Option, + follow_symlinks: bool, + ) -> Result<(), Error> { + todo!() + } +}