diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a175bdc9c..7b490f36e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ All PRs to the Wasmer repository must add to this file. Blocks of changes will separated by version increments. ## **[Unreleased]** + Special thanks to @YaronWittenstein @penberg for their contributions. +- [#608](https://github.com/wasmerio/wasmer/issues/608) Implement wasi syscalls `fd_allocate`, `fd_sync`, `fd_pread`, `path_link`, `path_filestat_set_times`; update WASI fs API in a WIP way; reduce coupling of WASI code to host filesystem; make debug messages from WASI more readable; improve rights-checking when calling syscalls; implement reference counting on inodes; misc bug fixes and improvements - [#616](https://github.com/wasmerio/wasmer/issues/616) Create the import object separately from instance instantiation in `runtime-c-api` - [#620](https://github.com/wasmerio/wasmer/issues/620) Replace one `throw()` with `noexcept` in llvm backend - [#618](https://github.com/wasmerio/wasmer/issues/618) Implement `InternalEvent::Breakpoint` in the llvm backend to allow metering in llvm diff --git a/Cargo.lock b/Cargo.lock index dd7ed2d4be9..1bd05a8d339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.6.0", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Makefile b/Makefile index 4804ab3c40c..c8db3108183 100644 --- a/Makefile +++ b/Makefile @@ -62,13 +62,17 @@ middleware: middleware-singlepass middleware-cranelift middleware-llvm # Wasitests -wasitests-singlepass: +wasitests-setup: + rm -rf lib/wasi-tests/wasitests/test_fs/temp + mkdir -p lib/wasi-tests/wasitests/test_fs/temp + +wasitests-singlepass: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features singlepass -- --test-threads=1 -wasitests-cranelift: +wasitests-cranelift: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 -wasitests-llvm: +wasitests-llvm: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1 wasitests-unit: diff --git a/lib/wasi-tests/build/wasitests.rs b/lib/wasi-tests/build/wasitests.rs index 3ad62865493..aefab31eda2 100644 --- a/lib/wasi-tests/build/wasitests.rs +++ b/lib/wasi-tests/build/wasitests.rs @@ -63,6 +63,22 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { std::fs::remove_file(&normalized_name).expect("could not delete executable"); let wasm_out_name = format!("{}.wasm", &normalized_name); + let mut file_contents = String::new(); + { + let mut file = std::fs::File::open(file).unwrap(); + file.read_to_string(&mut file_contents).unwrap(); + } + { + let mut actual_file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(file) + .unwrap(); + actual_file + .write_all(format!("#![feature(wasi_ext)]\n{}", &file_contents).as_bytes()) + .unwrap(); + } + let wasm_compilation_out = Command::new("rustc") .arg("+nightly") .arg("--target=wasm32-wasi") @@ -74,6 +90,14 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { .output() .expect("Failed to compile program to native code"); print_info_on_error(&wasm_compilation_out, "WASM COMPILATION"); + { + let mut actual_file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(file) + .unwrap(); + actual_file.write_all(file_contents.as_bytes()).unwrap(); + } // to prevent commiting huge binary blobs forever let wasm_strip_out = Command::new("wasm-strip") diff --git a/lib/wasi-tests/tests/wasitests/fd_allocate.rs b/lib/wasi-tests/tests/wasitests/fd_allocate.rs new file mode 100644 index 00000000000..b6a43a8e540 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_allocate.rs @@ -0,0 +1,14 @@ +#[test] +fn test_fd_allocate() { + assert_wasi_output!( + "../../wasitests/fd_allocate.wasm", + "fd_allocate", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/temp") + ),], + vec![], + "../../wasitests/fd_allocate.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/fd_pread.rs b/lib/wasi-tests/tests/wasitests/fd_pread.rs new file mode 100644 index 00000000000..cfd8f2ac5aa --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_pread.rs @@ -0,0 +1,14 @@ +#[test] +fn test_fd_pread() { + assert_wasi_output!( + "../../wasitests/fd_pread.wasm", + "fd_pread", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/fd_pread.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/fd_sync.rs b/lib/wasi-tests/tests/wasitests/fd_sync.rs new file mode 100644 index 00000000000..ab1ed71bf3a --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_sync.rs @@ -0,0 +1,14 @@ +#[test] +fn test_fd_sync() { + assert_wasi_output!( + "../../wasitests/fd_sync.wasm", + "fd_sync", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/temp") + ),], + vec![], + "../../wasitests/fd_sync.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/mod.rs b/lib/wasi-tests/tests/wasitests/mod.rs index a7d06db0616..a4830aa51f1 100644 --- a/lib/wasi-tests/tests/wasitests/mod.rs +++ b/lib/wasi-tests/tests/wasitests/mod.rs @@ -7,11 +7,15 @@ mod _common; mod create_dir; mod envvar; +mod fd_allocate; +mod fd_pread; +mod fd_sync; mod file_metadata; mod fs_sandbox_test; mod fseek; mod hello; mod mapdir; +mod path_link; mod quine; mod readlink; mod wasi_sees_virtual_root; diff --git a/lib/wasi-tests/tests/wasitests/path_link.rs b/lib/wasi-tests/tests/wasitests/path_link.rs new file mode 100644 index 00000000000..a099d0420d3 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/path_link.rs @@ -0,0 +1,20 @@ +#[test] +fn test_path_link() { + assert_wasi_output!( + "../../wasitests/path_link.wasm", + "path_link", + vec![], + vec![ + ( + "act5".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act5") + ), + ( + "temp".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/temp") + ), + ], + vec![], + "../../wasitests/path_link.out" + ); +} diff --git a/lib/wasi-tests/wasitests/create_dir.wasm b/lib/wasi-tests/wasitests/create_dir.wasm index 9847f3bec7a..2fa02379276 100755 Binary files a/lib/wasi-tests/wasitests/create_dir.wasm and b/lib/wasi-tests/wasitests/create_dir.wasm differ diff --git a/lib/wasi-tests/wasitests/envvar.wasm b/lib/wasi-tests/wasitests/envvar.wasm index a952a6c0447..ef539e6a5ae 100755 Binary files a/lib/wasi-tests/wasitests/envvar.wasm and b/lib/wasi-tests/wasitests/envvar.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_allocate.out b/lib/wasi-tests/wasitests/fd_allocate.out new file mode 100644 index 00000000000..d8a0f88c377 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_allocate.out @@ -0,0 +1,2 @@ +171 +1405 diff --git a/lib/wasi-tests/wasitests/fd_allocate.rs b/lib/wasi-tests/wasitests/fd_allocate.rs new file mode 100644 index 00000000000..e7bc1c6310e --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_allocate.rs @@ -0,0 +1,58 @@ +// Args: +// mapdir: .:wasitests/test_fs/temp + +use std::fs; +#[cfg(target_os = "wasi")] +use std::os::wasi::prelude::AsRawFd; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_allocate(fd: u32, offset: u64, length: u64) -> u16; +} + +#[cfg(target_os = "wasi")] +fn allocate(fd: u32, offset: u64, length: u64) -> u16 { + unsafe { fd_allocate(fd, offset, length) } +} + +fn main() { + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + #[cfg(target_os = "wasi")] + { + base.push("fd_allocate_file.txt"); + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&base) + .expect("Could not create file"); + let mut buffer = [0u8; 64]; + + { + use std::io::Write; + // example text from https://www.un.org/en/universal-declaration-human-rights/ + file.write_all(b"All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.\n").unwrap(); + let raw_fd = file.as_raw_fd(); + file.flush(); + let len = file.metadata().unwrap().len(); + println!("{}", len); + assert_eq!(len, 171); + allocate(raw_fd, len, 1234); + let len = file.metadata().unwrap().len(); + println!("{}", len); + assert_eq!(len, 1234 + 171); + } + } + #[cfg(target_os = "wasi")] + std::fs::remove_file(&base).unwrap(); + + #[cfg(not(target_os = "wasi"))] + { + // eh, just print the output directly + println!("171"); + println!("1405"); + } +} diff --git a/lib/wasi-tests/wasitests/fd_allocate.wasm b/lib/wasi-tests/wasitests/fd_allocate.wasm new file mode 100755 index 00000000000..b3b8a46bda1 Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_allocate.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_pread.out b/lib/wasi-tests/wasitests/fd_pread.out new file mode 100644 index 00000000000..a83632aeaf3 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_pread.out @@ -0,0 +1,9 @@ + POLONIUS + + He will come straight. Look you lay home to him: + + POLONIUS + + He will come straight. Look you lay home to him: + +Read the same data? true \ No newline at end of file diff --git a/lib/wasi-tests/wasitests/fd_pread.rs b/lib/wasi-tests/wasitests/fd_pread.rs new file mode 100644 index 00000000000..72addfdfec0 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_pread.rs @@ -0,0 +1,87 @@ +// Args: +// mapdir: .:wasitests/test_fs/hamlet + +use std::fs; +#[cfg(target_os = "wasi")] +use std::os::wasi::prelude::AsRawFd; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[repr(C)] +struct WasiIovec { + pub buf: u32, + pub buf_len: u32, +} + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_pread(fd: u32, iovs: u32, iovs_len: u32, offset: u64, nread: u32) -> u16; +} + +#[cfg(target_os = "wasi")] +fn pread(fd: u32, iovs: &[&mut [u8]], offset: u64) -> u32 { + let mut nread = 0; + let mut processed_iovs = vec![]; + + for iov in iovs { + processed_iovs.push(WasiIovec { + buf: iov.as_ptr() as usize as u32, + buf_len: iov.len() as u32, + }) + } + + unsafe { + fd_pread( + fd, + processed_iovs.as_ptr() as usize as u32, + processed_iovs.len() as u32, + offset, + &mut nread as *mut u32 as usize as u32, + ); + } + nread +} + +fn main() { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs/hamlet"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + + base.push("act3/scene4.txt"); + let mut file = fs::File::open(&base).expect("Could not open file"); + let mut buffer = [0u8; 64]; + + #[cfg(target_os = "wasi")] + { + let raw_fd = file.as_raw_fd(); + assert_eq!(pread(raw_fd, &[&mut buffer], 75), 64); + let str_val = std::str::from_utf8(&buffer[..]).unwrap().to_string(); + println!("{}", &str_val); + for i in 0..buffer.len() { + buffer[i] = 0; + } + assert_eq!(pread(raw_fd, &[&mut buffer], 75), 64); + let str_val2 = std::str::from_utf8(&buffer[..]).unwrap().to_string(); + println!("{}", &str_val2); + + println!("Read the same data? {}", str_val == str_val2); + } + + #[cfg(not(target_os = "wasi"))] + { + // eh, just print the output directly + print!( + " POLONIUS + + He will come straight. Look you lay home to him: + + POLONIUS + + He will come straight. Look you lay home to him: + +Read the same data? true" + ); + } +} diff --git a/lib/wasi-tests/wasitests/fd_pread.wasm b/lib/wasi-tests/wasitests/fd_pread.wasm new file mode 100755 index 00000000000..a6fdb0c33a4 Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_pread.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_sync.out b/lib/wasi-tests/wasitests/fd_sync.out new file mode 100644 index 00000000000..ccda92fc03d --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_sync.out @@ -0,0 +1,2 @@ +170 +1404 diff --git a/lib/wasi-tests/wasitests/fd_sync.rs b/lib/wasi-tests/wasitests/fd_sync.rs new file mode 100644 index 00000000000..58be96ef5db --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_sync.rs @@ -0,0 +1,56 @@ +// Args: +// mapdir: .:wasitests/test_fs/temp + +use std::fs; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_sync(fd: u32) -> u16; +} + +#[cfg(target_os = "wasi")] +fn sync(fd: u32) -> u16 { + unsafe { fd_sync(fd) } +} + +fn main() { + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + #[cfg(target_os = "wasi")] + { + base.push("fd_sync_file.txt"); + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&base) + .expect("Could not create file"); + let mut buffer = [0u8; 64]; + + { + use std::io::Write; + // example text from https://www.un.org/en/universal-declaration-human-rights/ + file.write_all(b"All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.").unwrap(); + file.sync_all(); + let len = file.metadata().unwrap().len(); + println!("{}", len); + assert_eq!(len, 170); + file.set_len(170 + 1234); + file.sync_all(); + let len = file.metadata().unwrap().len(); + println!("{}", len); + assert_eq!(len, 1234 + 170); + } + } + #[cfg(target_os = "wasi")] + std::fs::remove_file(&base).unwrap(); + + #[cfg(not(target_os = "wasi"))] + { + // eh, just print the output directly + println!("170"); + println!("1404"); + } +} diff --git a/lib/wasi-tests/wasitests/fd_sync.wasm b/lib/wasi-tests/wasitests/fd_sync.wasm new file mode 100755 index 00000000000..b5a3243ffc2 Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_sync.wasm differ diff --git a/lib/wasi-tests/wasitests/file_metadata.wasm b/lib/wasi-tests/wasitests/file_metadata.wasm index fd9b2ba97d0..986362c588c 100755 Binary files a/lib/wasi-tests/wasitests/file_metadata.wasm and b/lib/wasi-tests/wasitests/file_metadata.wasm differ diff --git a/lib/wasi-tests/wasitests/fs_sandbox_test.wasm b/lib/wasi-tests/wasitests/fs_sandbox_test.wasm index 901bdaf16b9..255287806fe 100755 Binary files a/lib/wasi-tests/wasitests/fs_sandbox_test.wasm and b/lib/wasi-tests/wasitests/fs_sandbox_test.wasm differ diff --git a/lib/wasi-tests/wasitests/fseek.wasm b/lib/wasi-tests/wasitests/fseek.wasm index 20b51e1f6bb..f50045437df 100755 Binary files a/lib/wasi-tests/wasitests/fseek.wasm and b/lib/wasi-tests/wasitests/fseek.wasm differ diff --git a/lib/wasi-tests/wasitests/hello.wasm b/lib/wasi-tests/wasitests/hello.wasm index 3e2cfc43665..758a14bd1a8 100755 Binary files a/lib/wasi-tests/wasitests/hello.wasm and b/lib/wasi-tests/wasitests/hello.wasm differ diff --git a/lib/wasi-tests/wasitests/mapdir.wasm b/lib/wasi-tests/wasitests/mapdir.wasm index ae0eba16bb9..c6e5949912f 100755 Binary files a/lib/wasi-tests/wasitests/mapdir.wasm and b/lib/wasi-tests/wasitests/mapdir.wasm differ diff --git a/lib/wasi-tests/wasitests/path_link.out b/lib/wasi-tests/wasitests/path_link.out new file mode 100644 index 00000000000..53df012adc9 --- /dev/null +++ b/lib/wasi-tests/wasitests/path_link.out @@ -0,0 +1,9 @@ +ACT V +SCENE I. A churchyard. + + Enter two Clowns, with spades, +ACT V +SCENE I. A churchyard. + + Enter two Clowns, with spades, +Path still exists diff --git a/lib/wasi-tests/wasitests/path_link.rs b/lib/wasi-tests/wasitests/path_link.rs new file mode 100644 index 00000000000..2b9aa534e89 --- /dev/null +++ b/lib/wasi-tests/wasitests/path_link.rs @@ -0,0 +1,53 @@ +// Args: +// mapdir: act5:wasitests/test_fs/hamlet/act5 +// mapdir: temp:wasitests/test_fs/temp + +use std::fs; +use std::io::Read; + +fn main() { + #[cfg(not(target_os = "wasi"))] + { + let out_str = "ACT V +SCENE I. A churchyard. + + Enter two Clowns, with spades,"; + println!("{}", out_str); + println!("{}", out_str); + println!("Path still exists"); + } + + #[cfg(target_os = "wasi")] + { + { + std::fs::hard_link("act5/scene1.txt", "temp/scene_of_the_day.txt").unwrap(); + let mut f = fs::OpenOptions::new() + .read(true) + .open("temp/scene_of_the_day.txt") + .unwrap(); + let mut buffer = [0u8; 64]; + f.read_exact(&mut buffer).unwrap(); + + println!("{}", std::str::from_utf8(&buffer[..]).unwrap()); + for b in buffer.iter_mut() { + *b = 0; + } + + let mut f = fs::OpenOptions::new() + .read(true) + .open("act5/scene1.txt") + .unwrap(); + f.read_exact(&mut buffer).unwrap(); + println!("{}", std::str::from_utf8(&buffer[..]).unwrap()); + } + + std::fs::remove_file("temp/scene_of_the_day.txt").unwrap(); + let path = std::path::PathBuf::from("act5/scene1.txt"); + + if path.exists() { + println!("Path still exists"); + } else { + println!("Path was deleted!"); + } + } +} diff --git a/lib/wasi-tests/wasitests/path_link.wasm b/lib/wasi-tests/wasitests/path_link.wasm new file mode 100755 index 00000000000..822c78ab3bb Binary files /dev/null and b/lib/wasi-tests/wasitests/path_link.wasm differ diff --git a/lib/wasi-tests/wasitests/quine.wasm b/lib/wasi-tests/wasitests/quine.wasm index 086f19844ae..fc6361b5c23 100755 Binary files a/lib/wasi-tests/wasitests/quine.wasm and b/lib/wasi-tests/wasitests/quine.wasm differ diff --git a/lib/wasi-tests/wasitests/readlink.wasm b/lib/wasi-tests/wasitests/readlink.wasm index c04abb75db7..67ada4404d9 100755 Binary files a/lib/wasi-tests/wasitests/readlink.wasm and b/lib/wasi-tests/wasitests/readlink.wasm differ diff --git a/lib/wasi-tests/wasitests/test_fs/README.md b/lib/wasi-tests/wasitests/test_fs/README.md index e211041d2bb..3384bc2d335 100644 --- a/lib/wasi-tests/wasitests/test_fs/README.md +++ b/lib/wasi-tests/wasitests/test_fs/README.md @@ -4,4 +4,6 @@ This is a test "file system" used in some of the WASI integration tests. It's just a bunch of files in a tree. -If you make changes here, please regenerate the tests with `make wasitests`! \ No newline at end of file +If you make changes here, please regenerate the tests with `make wasitests`! + +The contents of `temp` are deleted before each run. If you want to test making or mutating files, do it in that sub directory. diff --git a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm index 6d0b3cd3a97..4c819b4cc9f 100755 Binary files a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm and b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm differ diff --git a/lib/wasi-tests/wasitests/writing.wasm b/lib/wasi-tests/wasitests/writing.wasm index eae2eb97bcc..148add04d7d 100755 Binary files a/lib/wasi-tests/wasitests/writing.wasm and b/lib/wasi-tests/wasitests/writing.wasm differ diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index 88ee9a56d39..26a09c5ba83 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -15,6 +15,7 @@ rand = "0.7.0" generational-arena = "0.2.2" log = "0.4.8" byteorder = "1.3.2" +time = "0.1.42" [target.'cfg(windows)'.dependencies] winapi = "0.3.7" diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state/mod.rs similarity index 78% rename from lib/wasi/src/state.rs rename to lib/wasi/src/state/mod.rs index 348e0eaa266..b74dc927c9a 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state/mod.rs @@ -1,10 +1,10 @@ -//! WARNING: the API exposed here is unstable and very experimental. Certain thins will not +//! WARNING: the API exposed here is unstable and very experimental. Certain things are not ready //! yet and may be broken in patch releases. If you're using this and have any specific needs, //! please let us know here https://github.com/wasmerio/wasmer/issues/583 or by filing an issue. -// use wasmer_runtime_abi::vfs::{ -// vfs::Vfs, -// file_like::{FileLike, Metadata}; -// }; + +mod types; + +pub use self::types::*; use crate::syscalls::types::*; use generational_arena::Arena; pub use generational_arena::Index as Inode; @@ -13,14 +13,14 @@ use std::{ borrow::Borrow, cell::Cell, fs, - io::{self, Read, Seek, Write}, + io::{self, Write}, path::{Path, PathBuf}, time::SystemTime, }; use wasmer_runtime_core::{debug, vm::Ctx}; /// the fd value of the virtual root -pub const VIRTUAL_ROOT_FD: __wasi_fd_t = 4; +pub const VIRTUAL_ROOT_FD: __wasi_fd_t = 3; /// all the rights enabled pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFFFFFF; @@ -34,295 +34,6 @@ pub unsafe fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState { /// the number of symlinks that can be traversed when resolving a path pub const MAX_SYMLINKS: u32 = 128; -/// Error type for external users -#[derive(Debug, PartialEq, Eq)] -#[allow(dead_code)] -// dead code beacuse this is for external use -pub enum WasiFsError { - /// The fd given as a base was not a directory so the operation was not possible - BaseNotDirectory, - /// Expected a file but found not a file - NotAFile, - /// The fd given was not usable - InvalidFd, - /// File exists - AlreadyExists, - /// Something failed when doing IO. These errors can generally not be handled. - /// It may work if tried again. - IOError, - /// A WASI error without an external name. If you encounter this it means - /// that there's probably a bug on our side (maybe as simple as forgetting to wrap - /// this error, but perhaps something broke) - UnknownError(__wasi_errno_t), -} - -impl WasiFsError { - pub fn from_wasi_err(err: __wasi_errno_t) -> WasiFsError { - match err { - __WASI_EBADF => WasiFsError::InvalidFd, - __WASI_EEXIST => WasiFsError::AlreadyExists, - __WASI_EIO => WasiFsError::IOError, - _ => WasiFsError::UnknownError(err), - } - } -} - -/// This trait relies on your file closing when it goes out of scope via `Drop` -pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { - /// the last time the file was accessed in nanoseconds as a UNIX timestamp - fn last_accessed(&self) -> u64; - /// the last time the file was modified in nanoseconds as a UNIX timestamp - fn last_modified(&self) -> u64; - /// the time at which the file was created in nanoseconds as a UNIX timestamp - fn created_time(&self) -> u64; - /// the size of the file in bytes - fn size(&self) -> u64; -} - -impl WasiFile for fs::File { - fn last_accessed(&self) -> u64 { - self.metadata() - .unwrap() - .accessed() - .ok() - .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|ct| ct.as_nanos() as u64) - .unwrap_or(0) - } - - fn last_modified(&self) -> u64 { - self.metadata() - .unwrap() - .modified() - .ok() - .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|ct| ct.as_nanos() as u64) - .unwrap_or(0) - } - - fn created_time(&self) -> u64 { - self.metadata() - .unwrap() - .created() - .ok() - .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|ct| ct.as_nanos() as u64) - .unwrap_or(0) - } - - fn size(&self) -> u64 { - self.metadata().unwrap().len() - } -} - -#[derive(Debug)] -pub struct Stdout(std::io::Stdout); -impl Read for Stdout { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stdout", - )) - } - fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stdout", - )) - } - fn read_to_string(&mut self, _buf: &mut String) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stdout", - )) - } - fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stdout", - )) - } -} -impl Seek for Stdout { - fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not seek stdout", - )) - } -} -impl Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.0.write_all(buf) - } - fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - self.0.write_fmt(fmt) - } -} - -impl WasiFile for Stdout { - fn last_accessed(&self) -> u64 { - 0 - } - fn last_modified(&self) -> u64 { - 0 - } - fn created_time(&self) -> u64 { - 0 - } - fn size(&self) -> u64 { - 0 - } -} - -#[derive(Debug)] -pub struct Stderr(std::io::Stderr); -impl Read for Stderr { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stderr", - )) - } - fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stderr", - )) - } - fn read_to_string(&mut self, _buf: &mut String) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stderr", - )) - } - fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not read from stderr", - )) - } -} -impl Seek for Stderr { - fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not seek stderr", - )) - } -} -impl Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.0.write_all(buf) - } - fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - self.0.write_fmt(fmt) - } -} - -impl WasiFile for Stderr { - fn last_accessed(&self) -> u64 { - 0 - } - fn last_modified(&self) -> u64 { - 0 - } - fn created_time(&self) -> u64 { - 0 - } - fn size(&self) -> u64 { - 0 - } -} - -#[derive(Debug)] -pub struct Stdin(std::io::Stdin); -impl Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) - } - fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - self.0.read_to_end(buf) - } - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - self.0.read_to_string(buf) - } - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - self.0.read_exact(buf) - } -} -impl Seek for Stdin { - fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not seek stdin", - )) - } -} -impl Write for Stdin { - fn write(&mut self, _buf: &[u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not write to stdin", - )) - } - fn flush(&mut self) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not write to stdin", - )) - } - fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not write to stdin", - )) - } - fn write_fmt(&mut self, _fmt: ::std::fmt::Arguments) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not write to stdin", - )) - } -} - -impl WasiFile for Stdin { - fn last_accessed(&self) -> u64 { - 0 - } - fn last_modified(&self) -> u64 { - 0 - } - fn created_time(&self) -> u64 { - 0 - } - fn size(&self) -> u64 { - 0 - } -} - -/* -TODO: Think about using this -trait WasiFdBacking: std::fmt::Debug { - fn get_stat(&self) -> &__wasi_filestat_t; - fn get_stat_mut(&mut self) -> &mut __wasi_filestat_t; - fn is_preopened(&self) -> bool; - fn get_name(&self) -> &str; -} -*/ - /// A file that Wasi knows about that may or may not be open #[derive(Debug)] pub struct InodeVal { @@ -356,7 +67,8 @@ pub enum Kind { File { /// the open file, if it's open handle: Option>, - /// the path to the file + /// The path on the host system where the file is located + /// This is deprecated and will be removed in 0.7.0 or a shortly thereafter path: PathBuf, }, Dir { @@ -645,6 +357,27 @@ impl WasiFs { } } + /// refresh size from filesystem + pub(crate) fn filestat_resync_size( + &mut self, + fd: __wasi_fd_t, + ) -> Result<__wasi_filesize_t, __wasi_errno_t> { + let fd = self.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)?; + match &mut self.inodes[fd.inode].kind { + Kind::File { handle, .. } => { + if let Some(h) = handle { + let new_size = h.size(); + self.inodes[fd.inode].stat.st_size = new_size; + Ok(new_size as __wasi_filesize_t) + } else { + Err(__WASI_EBADF) + } + } + Kind::Dir { .. } | Kind::Root { .. } => Err(__WASI_EISDIR), + _ => Err(__WASI_EINVAL), + } + } + fn get_inode_at_path_inner( &mut self, base: __wasi_fd_t, @@ -700,8 +433,12 @@ impl WasiFs { // TODO: verify this returns successfully when given a non-symlink let metadata = file.symlink_metadata().ok().ok_or(__WASI_EINVAL)?; let file_type = metadata.file_type(); + // we want to insert newly opened dirs and files, but not transient symlinks + // TODO: explain why (think about this deeply when well rested) + let mut should_insert = false; let kind = if file_type.is_dir() { + should_insert = true; // load DIR Kind::Dir { parent: Some(cur_inode), @@ -709,6 +446,7 @@ impl WasiFs { entries: Default::default(), } } else if file_type.is_file() { + should_insert = true; // load file Kind::File { handle: None, @@ -734,8 +472,21 @@ impl WasiFs { unimplemented!("state::get_inode_at_path unknown file type: not file, directory, or symlink"); }; - cur_inode = + let new_inode = self.create_inode(kind, false, file.to_string_lossy().to_string())?; + if should_insert { + if let Kind::Dir { + ref mut entries, .. + } = &mut self.inodes[cur_inode].kind + { + entries.insert( + component.as_os_str().to_string_lossy().to_string(), + new_inode, + ); + } + } + cur_inode = new_inode; + if loop_for_symlink && follow_symlinks { continue 'symlink_resolution; } @@ -863,13 +614,13 @@ impl WasiFs { } pub fn filestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_filestat_t, __wasi_errno_t> { - let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + let fd = self.get_fd(fd)?; Ok(self.inodes[fd.inode].stat) } pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> { - let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + let fd = self.get_fd(fd)?; debug!("fdstat: {:?}", fd); diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs new file mode 100644 index 00000000000..5390d6a8046 --- /dev/null +++ b/lib/wasi/src/state/types.rs @@ -0,0 +1,408 @@ +/// types for use in the WASI filesystem +use crate::syscalls::types::*; +use std::{ + fs, + io::{self, Read, Seek, Write}, + path::PathBuf, + time::SystemTime, +}; + +/// Error type for external users +#[derive(Debug, PartialEq, Eq)] +#[allow(dead_code)] +// dead code beacuse this is for external use +pub enum WasiFsError { + /// The fd given as a base was not a directory so the operation was not possible + BaseNotDirectory, + /// Expected a file but found not a file + NotAFile, + /// The fd given was not usable + InvalidFd, + /// File exists + AlreadyExists, + /// Something failed when doing IO. These errors can generally not be handled. + /// It may work if tried again. + IOError, + /// A WASI error without an external name. If you encounter this it means + /// that there's probably a bug on our side (maybe as simple as forgetting to wrap + /// this error, but perhaps something broke) + UnknownError(__wasi_errno_t), +} + +impl WasiFsError { + pub fn from_wasi_err(err: __wasi_errno_t) -> WasiFsError { + match err { + __WASI_EBADF => WasiFsError::InvalidFd, + __WASI_EEXIST => WasiFsError::AlreadyExists, + __WASI_EIO => WasiFsError::IOError, + _ => WasiFsError::UnknownError(err), + } + } +} + +/// This trait relies on your file closing when it goes out of scope via `Drop` +pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { + /// the last time the file was accessed in nanoseconds as a UNIX timestamp + fn last_accessed(&self) -> __wasi_timestamp_t; + /// the last time the file was modified in nanoseconds as a UNIX timestamp + fn last_modified(&self) -> __wasi_timestamp_t; + /// the time at which the file was created in nanoseconds as a UNIX timestamp + fn created_time(&self) -> __wasi_timestamp_t; + /// set the last time the file was accessed in nanoseconds as a UNIX timestamp + // TODO: stablize this in 0.7.0 by removing default impl + fn set_last_accessed(&self, _last_accessed: __wasi_timestamp_t) { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::set_last_accessed for your type before then"); + } + /// set the last time the file was modified in nanoseconds as a UNIX timestamp + // TODO: stablize this in 0.7.0 by removing default impl + fn set_last_modified(&self, _last_modified: __wasi_timestamp_t) { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::set_last_modified for your type before then"); + } + /// set the time at which the file was created in nanoseconds as a UNIX timestamp + // TODO: stablize this in 0.7.0 by removing default impl + fn set_created_time(&self, _created_time: __wasi_timestamp_t) { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::set_created_time for your type before then"); + } + /// the size of the file in bytes + fn size(&self) -> u64; + /// Change the size of the file, if the `new_size` is greater than the current size + /// the extra bytes will be allocated and zeroed + // TODO: stablize this in 0.7.0 by removing default impl + fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Option<()> { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::allocate for your type before then"); + } + + /// Request deletion of the file + // TODO: break this out into a WasiPath trait which is dynamically in Kind::File + // this change can't be done until before release + fn unlink(&mut self) -> Option<()> { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::unlink for your type before then"); + } + + /// Store file contents and metadata to disk + // TODO: stablize this in 0.7.0 by removing default impl + fn sync_to_disk(&self) -> Option<()> { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::sync_to_disk for your type before then"); + } +} + +pub trait WasiPath {} + +/// A thin wrapper around `std::fs::File` +#[derive(Debug)] +pub struct HostFile { + pub inner: fs::File, + pub host_path: PathBuf, +} + +impl HostFile { + /// creates a new host file from a `std::fs::File` and a path + pub fn new(file: fs::File, host_path: PathBuf) -> Self { + Self { + inner: file, + host_path, + } + } + + pub fn metadata(&self) -> fs::Metadata { + self.inner.metadata().unwrap() + } +} + +impl Read for HostFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.inner.read_to_end(buf) + } + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + self.inner.read_to_string(buf) + } + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.inner.read_exact(buf) + } +} +impl Seek for HostFile { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + self.inner.seek(pos) + } +} +impl Write for HostFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.inner.write_all(buf) + } + fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { + self.inner.write_fmt(fmt) + } +} + +impl WasiFile for HostFile { + fn last_accessed(&self) -> u64 { + self.metadata() + .accessed() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0) + } + + fn set_last_accessed(&self, _last_accessed: __wasi_timestamp_t) { + // TODO: figure out how to do this + } + + fn last_modified(&self) -> u64 { + self.metadata() + .modified() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0) + } + + fn set_last_modified(&self, _last_modified: __wasi_timestamp_t) { + // TODO: figure out how to do this + } + + fn created_time(&self) -> u64 { + self.metadata() + .created() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0) + } + + fn set_created_time(&self, _created_time: __wasi_timestamp_t) { + // TODO: figure out how to do this + } + + fn size(&self) -> u64 { + self.metadata().len() + } + + fn set_len(&mut self, new_size: __wasi_filesize_t) -> Option<()> { + fs::File::set_len(&self.inner, new_size).ok() + } + + fn unlink(&mut self) -> Option<()> { + std::fs::remove_file(&self.host_path).ok() + } + fn sync_to_disk(&self) -> Option<()> { + self.inner.sync_all().ok() + } +} + +#[derive(Debug)] +pub struct Stdout(pub std::io::Stdout); +impl Read for Stdout { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } + fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } + fn read_to_string(&mut self, _buf: &mut String) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } + fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } +} +impl Seek for Stdout { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek stdout", + )) + } +} +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } + fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { + self.0.write_fmt(fmt) + } +} + +impl WasiFile for Stdout { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } +} + +#[derive(Debug)] +pub struct Stderr(pub std::io::Stderr); +impl Read for Stderr { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } + fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } + fn read_to_string(&mut self, _buf: &mut String) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } + fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } +} +impl Seek for Stderr { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek stderr", + )) + } +} +impl Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } + fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { + self.0.write_fmt(fmt) + } +} + +impl WasiFile for Stderr { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } +} + +#[derive(Debug)] +pub struct Stdin(pub std::io::Stdin); +impl Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.0.read_to_end(buf) + } + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + self.0.read_to_string(buf) + } + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.0.read_exact(buf) + } +} +impl Seek for Stdin { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek stdin", + )) + } +} +impl Write for Stdin { + fn write(&mut self, _buf: &[u8]) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } + fn flush(&mut self) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } + fn write_fmt(&mut self, _fmt: ::std::fmt::Arguments) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } +} + +impl WasiFile for Stdin { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } +} + +/* +TODO: Think about using this +trait WasiFdBacking: std::fmt::Debug { + fn get_stat(&self) -> &__wasi_filestat_t; + fn get_stat_mut(&mut self) -> &mut __wasi_filestat_t; + fn is_preopened(&self) -> bool; + fn get_name(&self) -> &str; +} +*/ diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 2dee1ac143b..422b67dcee8 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -9,8 +9,8 @@ use self::types::*; use crate::{ ptr::{Array, WasmPtr}, state::{ - self, host_file_type_to_wasi_file_type, Fd, Inode, InodeVal, Kind, WasiFile, WasiState, - MAX_SYMLINKS, + self, host_file_type_to_wasi_file_type, Fd, HostFile, Inode, InodeVal, Kind, WasiFile, + WasiState, MAX_SYMLINKS, }, ExitCode, }; @@ -56,6 +56,23 @@ fn write_bytes( Ok(bytes_written) } +fn read_bytes( + mut reader: T, + memory: &Memory, + iovs_arr_cell: &[Cell<__wasi_iovec_t>], +) -> Result { + let mut bytes_read = 0; + + for iov in iovs_arr_cell { + let iov_inner = iov.get(); + let bytes = iov_inner.buf.deref(memory, 0, iov_inner.buf_len)?; + let mut raw_bytes: &mut [u8] = + unsafe { &mut *(bytes as *const [_] as *mut [_] as *mut [u8]) }; + bytes_read += reader.read(raw_bytes).map_err(|_| __WASI_EIO)? as u32; + } + Ok(bytes_read) +} + /// checks that `rights_check_set` is a subset of `rights_set` fn has_rights(rights_set: __wasi_rights_t, rights_check_set: __wasi_rights_t) -> bool { rights_set | rights_check_set == rights_set @@ -86,6 +103,14 @@ fn write_buffer_array( __WASI_ESUCCESS } +fn get_current_time_in_nanos() -> Result<__wasi_timestamp_t, __wasi_errno_t> { + let now = std::time::SystemTime::now(); + let duration = now + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .map_err(|_| __WASI_EIO)?; + Ok(duration.as_nanos() as __wasi_timestamp_t) +} + /// ### `args_get()` /// Read command-line argument data. /// The sizes of the buffers should match that returned by [`args_sizes_get()`](#args_sizes_get). @@ -300,7 +325,34 @@ pub fn fd_allocate( len: __wasi_filesize_t, ) -> __wasi_errno_t { debug!("wasi::fd_allocate"); - unimplemented!("wasi::fd_allocate") + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + let inode = fd_entry.inode; + + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_ALLOCATE) { + return __WASI_EACCES; + } + let new_size = wasi_try!(offset.checked_add(len), __WASI_EINVAL); + + match &mut state.fs.inodes[inode].kind { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + wasi_try!(handle.set_len(new_size), __WASI_EIO); + } else { + return __WASI_EBADF; + } + } + Kind::Buffer { buffer } => { + buffer.resize(new_size as usize, 0); + } + Kind::Symlink { .. } => return __WASI_EBADF, + Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, + } + state.fs.inodes[inode].stat.st_size = new_size; + debug!("New file size: {}", new_size); + + __WASI_ESUCCESS } /// ### `fd_close()` @@ -345,6 +397,10 @@ pub fn fd_close(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { pub fn fd_datasync(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_datasync"); let state = get_wasi_state(ctx); + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_DATASYNC) { + return __WASI_EACCES; + } if let Err(e) = state.fs.flush(fd) { e @@ -373,6 +429,8 @@ pub fn fd_fdstat_get( ); let mut state = get_wasi_state(ctx); let memory = ctx.memory(0); + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + let stat = wasi_try!(state.fs.fdstat(fd)); let buf = wasi_try!(buf_ptr.deref(memory)); @@ -453,6 +511,10 @@ pub fn fd_filestat_get( debug!("wasi::fd_filestat_get"); let mut state = get_wasi_state(ctx); let memory = ctx.memory(0); + let fd_entry = wasi_try!(state.fs.get_fd(fd)); + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_FILESTAT_GET) { + return __WASI_EACCES; + } let stat = wasi_try!(state.fs.filestat_fd(fd)); @@ -462,13 +524,45 @@ pub fn fd_filestat_get( __WASI_ESUCCESS } +/// ### `fd_filestat_set_size()` +/// Change the size of an open file, zeroing out any new bytes +/// Inputs: +/// - `__wasi_fd_t fd` +/// File descriptor to adjust +/// - `__wasi_filesize_t st_size` +/// New size that `fd` will be set to pub fn fd_filestat_set_size( ctx: &mut Ctx, fd: __wasi_fd_t, st_size: __wasi_filesize_t, ) -> __wasi_errno_t { debug!("wasi::fd_filestat_set_size"); - unimplemented!("wasi::fd_filestat_set_size") + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + let inode = fd_entry.inode; + + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_FILESTAT_SET_SIZE) { + return __WASI_EACCES; + } + + match &mut state.fs.inodes[inode].kind { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + wasi_try!(handle.set_len(st_size), __WASI_EIO); + } else { + return __WASI_EBADF; + } + } + Kind::Buffer { buffer } => { + buffer.resize(st_size as usize, 0); + } + Kind::Symlink { .. } => return __WASI_EBADF, + Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, + } + state.fs.inodes[inode].stat.st_size = st_size; + + __WASI_ESUCCESS } /// ### `fd_filestat_set_times()` @@ -504,23 +598,60 @@ pub fn fd_filestat_set_times( let inode = &mut state.fs.inodes[fd_entry.inode]; - if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 { - inode.stat.st_atim = st_atim; - } else if fst_flags & __WASI_FILESTAT_SET_ATIM_NOW != 0 { - // set to current real time - unimplemented!("Set filestat time to the current real time"); + if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 || fst_flags & __WASI_FILESTAT_SET_ATIM_NOW != 0 { + let time_to_set = if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 { + st_atim + } else { + wasi_try!(get_current_time_in_nanos()) + }; + inode.stat.st_atim = time_to_set; + // TODO: set it for more than just files + match &mut inode.kind { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.set_last_accessed(time_to_set); + } + } + _ => {} + } } - if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 { - inode.stat.st_mtim = st_mtim; - } else if fst_flags & __WASI_FILESTAT_SET_MTIM_NOW != 0 { - // set to current real time - unimplemented!("Set filestat time to the current real time"); + if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 || fst_flags & __WASI_FILESTAT_SET_MTIM_NOW != 0 { + let time_to_set = if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 { + st_mtim + } else { + wasi_try!(get_current_time_in_nanos()) + }; + inode.stat.st_mtim = time_to_set; + // TODO: set it for more than just files + match &mut inode.kind { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.set_last_modified(time_to_set); + } + } + _ => {} + } } __WASI_ESUCCESS } +/// ### `fd_pread()` +/// Read from the file at the given offset without updating the file cursor. +/// This acts like a stateless version of Seek + Read +/// Inputs: +/// - `__wasi_fd_t fd` +/// The file descriptor to read the data with +/// - `const __wasi_iovec_t* iovs' +/// Vectors where the data will be stored +/// - `size_t iovs_len` +/// The number of vectors to store the data into +/// - `__wasi_filesize_t offset` +/// The file cursor to use: the starting position from which data will be read +/// Output: +/// - `size_t nread` +/// The number of bytes read pub fn fd_pread( ctx: &mut Ctx, fd: __wasi_fd_t, @@ -534,9 +665,56 @@ pub fn fd_pread( let iov_cells = wasi_try!(iovs.deref(memory, 0, iovs_len)); let nread_cell = wasi_try!(nread.deref(memory)); + let state = get_wasi_state(ctx); - unimplemented!("wasi::fd_pread"); + let bytes_read = match fd { + __WASI_STDIN_FILENO => wasi_try!(read_bytes(&mut state.fs.stdin, memory, iov_cells)), + __WASI_STDOUT_FILENO => return __WASI_EINVAL, + __WASI_STDERR_FILENO => return __WASI_EINVAL, + _ => { + let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let inode = fd_entry.inode; + + if !(has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) + && has_rights(fd_entry.rights, __WASI_RIGHT_FD_SEEK)) + { + return __WASI_EACCES; + } + match &mut state.fs.inodes[inode].kind { + Kind::File { handle, .. } => { + if let Some(h) = handle { + let current_pos = + wasi_try!(h.seek(std::io::SeekFrom::Current(0)).ok(), __WASI_EIO); + wasi_try!( + h.seek(std::io::SeekFrom::Start(offset as u64)).ok(), + __WASI_EIO + ); + let bytes_read = wasi_try!(read_bytes(h, memory, iov_cells)); + // reborrow so we can seek it back (the &mut gets moved into `read_bytes` + // and we can't use it after) + // If you're in the future and there's a nicer way to do this, please + // clean up this code + if let Some(h) = handle { + wasi_try!( + h.seek(std::io::SeekFrom::Start(current_pos)).ok(), + __WASI_EIO + ); + } + bytes_read + } else { + return __WASI_EINVAL; + } + } + Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, + Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_pread"), + Kind::Buffer { buffer } => { + wasi_try!(read_bytes(&buffer[(offset as usize)..], memory, iov_cells)) + } + } + } + }; + nread_cell.set(bytes_read); __WASI_ESUCCESS } @@ -647,8 +825,9 @@ pub fn fd_pwrite( _ => { let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { - // TODO: figure out the error to return when lacking rights + if !(has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) + && has_rights(fd_entry.rights, __WASI_RIGHT_FD_SEEK)) + { return __WASI_EACCES; } @@ -657,7 +836,7 @@ pub fn fd_pwrite( let bytes_written = match &mut inode.kind { Kind::File { handle, .. } => { if let Some(handle) = handle { - handle.seek(::std::io::SeekFrom::Start(offset as u64)); + handle.seek(std::io::SeekFrom::Start(offset as u64)); wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) } else { return __WASI_EINVAL; @@ -708,23 +887,6 @@ pub fn fd_read( let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len)); let nread_cell = wasi_try!(nread.deref(memory)); - - fn read_bytes( - mut reader: T, - memory: &Memory, - iovs_arr_cell: &[Cell<__wasi_iovec_t>], - ) -> Result { - let mut bytes_read = 0; - - for iov in iovs_arr_cell { - let iov_inner = iov.get(); - let bytes = iov_inner.buf.deref(memory, 0, iov_inner.buf_len)?; - let mut raw_bytes: &mut [u8] = - unsafe { &mut *(bytes as *const [_] as *mut [_] as *mut [u8]) }; - bytes_read += reader.read(raw_bytes).map_err(|_| __WASI_EIO)? as u32; - } - Ok(bytes_read) - } let state = get_wasi_state(ctx); let bytes_read = match fd { @@ -744,7 +906,7 @@ pub fn fd_read( let bytes_read = match &mut inode.kind { Kind::File { handle, .. } => { if let Some(handle) = handle { - handle.seek(::std::io::SeekFrom::Start(offset as u64)); + handle.seek(std::io::SeekFrom::Start(offset as u64)); wasi_try!(read_bytes(handle, memory, iovs_arr_cell)) } else { return __WASI_EINVAL; @@ -984,8 +1146,29 @@ pub fn fd_seek( /// - `__WASI_ENOTCAPABLE` pub fn fd_sync(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_sync"); - // TODO: check __WASI_RIGHT_FD_SYNC - unimplemented!("wasi::fd_sync") + debug!("=> fd={}", fd); + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let fd_entry = wasi_try!(state.fs.get_fd(fd)); + if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_SYNC) { + return __WASI_EACCES; + } + let inode = fd_entry.inode; + + // TODO: implement this for more than files + match &mut state.fs.inodes[inode].kind { + Kind::File { handle, .. } => { + if let Some(h) = handle { + wasi_try!(h.sync_to_disk(), __WASI_EIO); + } else { + return __WASI_EINVAL; + } + } + Kind::Root { .. } | Kind::Dir { .. } => return __WASI_EISDIR, + Kind::Buffer { .. } | Kind::Symlink { .. } => return __WASI_EINVAL, + } + + __WASI_ESUCCESS } /// ### `fd_tell()` @@ -1053,7 +1236,6 @@ pub fn fd_write( let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { - // TODO: figure out the error to return when lacking rights return __WASI_EACCES; } @@ -1063,7 +1245,7 @@ pub fn fd_write( let bytes_written = match &mut inode.kind { Kind::File { handle, .. } => { if let Some(handle) = handle { - handle.seek(::std::io::SeekFrom::Start(offset as u64)); + handle.seek(std::io::SeekFrom::Start(offset as u64)); wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) } else { return __WASI_EINVAL; @@ -1080,6 +1262,7 @@ pub fn fd_write( }; fd_entry.offset += bytes_written as u64; + wasi_try!(state.fs.filestat_resync_size(fd)); bytes_written } @@ -1273,7 +1456,70 @@ pub fn path_filestat_set_times( fst_flags: __wasi_fstflags_t, ) -> __wasi_errno_t { debug!("wasi::path_filestat_set_times"); - unimplemented!("wasi::path_filestat_set_times") + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + if !has_rights(fd_entry.rights, __WASI_RIGHT_PATH_FILESTAT_SET_TIMES) { + return __WASI_EACCES; + } + if (fst_flags & __WASI_FILESTAT_SET_ATIM != 0 && fst_flags & __WASI_FILESTAT_SET_ATIM_NOW != 0) + || (fst_flags & __WASI_FILESTAT_SET_MTIM != 0 + && fst_flags & __WASI_FILESTAT_SET_MTIM_NOW != 0) + { + return __WASI_EINVAL; + } + + let path_string = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + debug!("=> base_fd: {}, path: {}", fd, &path_string); + + let file_inode = wasi_try!(state.fs.get_inode_at_path( + fd, + path_string, + flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, + )); + let stat = wasi_try!(state + .fs + .get_stat_for_kind(&state.fs.inodes[file_inode].kind) + .ok_or(__WASI_EIO)); + + let inode = &mut state.fs.inodes[fd_entry.inode]; + + if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 || fst_flags & __WASI_FILESTAT_SET_ATIM_NOW != 0 { + let time_to_set = if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 { + st_atim + } else { + wasi_try!(get_current_time_in_nanos()) + }; + inode.stat.st_atim = time_to_set; + // TODO: set it for more than just files + match &mut inode.kind { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.set_last_accessed(time_to_set); + } + } + _ => {} + } + } + if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 || fst_flags & __WASI_FILESTAT_SET_MTIM_NOW != 0 { + let time_to_set = if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 { + st_mtim + } else { + wasi_try!(get_current_time_in_nanos()) + }; + inode.stat.st_mtim = time_to_set; + // TODO: set it for more than just files + match &mut inode.kind { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.set_last_modified(time_to_set); + } + } + _ => {} + } + } + + __WASI_ESUCCESS } /// ### `path_link()` @@ -1304,7 +1550,60 @@ pub fn path_link( new_path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_link"); - unimplemented!("wasi::path_link") + if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { + debug!(" - will follow symlinks when opening path"); + } + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let old_path_str = wasi_try!( + old_path.get_utf8_string(memory, old_path_len), + __WASI_EINVAL + ); + let new_path_str = wasi_try!( + new_path.get_utf8_string(memory, new_path_len), + __WASI_EINVAL + ); + + let source_fd = wasi_try!(state.fs.get_fd(old_fd)); + let target_fd = wasi_try!(state.fs.get_fd(new_fd)); + debug!( + "=> source_fd: {}, source_path: {}, target_fd: {}, target_path: {}", + old_fd, old_path_str, new_fd, new_path_str + ); + + if !(has_rights(source_fd.rights, __WASI_RIGHT_PATH_LINK_SOURCE) + && has_rights(target_fd.rights, __WASI_RIGHT_PATH_LINK_TARGET)) + { + return __WASI_EACCES; + } + + let source_inode = wasi_try!(state.fs.get_inode_at_path( + old_fd, + old_path_str, + old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, + )); + let target_path_arg = std::path::PathBuf::from(new_path_str); + let (target_parent_inode, new_entry_name) = + wasi_try!(state + .fs + .get_parent_inode_at_path(new_fd, &target_path_arg, false)); + + if state.fs.inodes[source_inode].stat.st_nlink == __wasi_linkcount_t::max_value() { + return __WASI_EMLINK; + } + match &mut state.fs.inodes[target_parent_inode].kind { + Kind::Dir { entries, .. } => { + if entries.contains_key(&new_entry_name) { + return __WASI_EEXIST; + } + entries.insert(new_entry_name, source_inode); + } + Kind::Root { .. } => return __WASI_EINVAL, + Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => return __WASI_ENOTDIR, + } + state.fs.inodes[source_inode].stat.st_nlink += 1; + + __WASI_ESUCCESS } /// ### `path_open()` @@ -1345,7 +1644,6 @@ pub fn path_open( ) -> __wasi_errno_t { debug!("wasi::path_open"); if dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { - // TODO: resolution fn needs to get this bit debug!(" - will follow symlinks when opening path"); } let memory = ctx.memory(0); @@ -1382,7 +1680,9 @@ pub fn path_open( ); // TODO: traverse rights of dirs properly - let adjusted_rights = fs_rights_base & working_dir.rights_inheriting; + // COMMENTED OUT: WASI isn't giving appropriate rights here when opening + // TODO: look into this; file a bug report if this is a bug + let adjusted_rights = /*fs_rights_base &*/ working_dir.rights_inheriting; let inode = if let Ok(inode) = maybe_inode { // Happy path, we found the file we're trying to open match &mut state.fs.inodes[inode].kind { @@ -1406,9 +1706,10 @@ pub fn path_open( .create(o_flags & __WASI_O_CREAT != 0) .truncate(o_flags & __WASI_O_TRUNC != 0); - *handle = Some(Box::new(wasi_try!(open_options - .open(&path) - .map_err(|_| __WASI_EIO)))); + *handle = Some(Box::new(HostFile::new( + wasi_try!(open_options.open(&path).map_err(|_| __WASI_EIO)), + path.to_path_buf(), + ))); } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), Kind::Dir { .. } | Kind::Root { .. } => { @@ -1465,14 +1766,13 @@ pub fn path_open( .write(true) .create_new(true); - Some( - Box::new(wasi_try!(open_options.open(&new_file_host_path).map_err( - |e| { - debug!("Error opening file {}", e); - __WASI_EIO - } - ))) as Box, - ) + Some(Box::new(HostFile::new( + wasi_try!(open_options.open(&new_file_host_path).map_err(|e| { + debug!("Error opening file {}", e); + __WASI_EIO + })), + new_file_host_path.clone(), + )) as Box) }; let new_inode = { @@ -1535,6 +1835,7 @@ pub fn path_readlink( if let Kind::Symlink { relative_path, .. } = &state.fs.inodes[inode].kind { let rel_path_str = relative_path.to_string_lossy(); + debug!("Result => {:?}", rel_path_str); let bytes = rel_path_str.bytes(); if bytes.len() >= buf_len as usize { return __WASI_EOVERFLOW; @@ -1658,7 +1959,11 @@ pub fn path_unlink_file( let memory = ctx.memory(0); let base_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + if !has_rights(base_dir.rights, __WASI_RIGHT_PATH_UNLINK_FILE) { + return __WASI_EACCES; + } let path_str = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + debug!("Requested file: {}", path_str); let inode = wasi_try!(state.fs.get_inode_at_path(fd, path_str, false)); let (parent_inode, childs_name) = @@ -1666,32 +1971,43 @@ pub fn path_unlink_file( .fs .get_parent_inode_at_path(fd, std::path::Path::new(path_str), false)); - let host_path_to_remove = match &state.fs.inodes[inode].kind { - Kind::File { path, .. } => path.clone(), - _ => unimplemented!("wasi::path_unlink_file for non-files"), - }; - - match &mut state.fs.inodes[parent_inode].kind { + let removed_inode = match &mut state.fs.inodes[parent_inode].kind { Kind::Dir { ref mut entries, .. } => { let removed_inode = wasi_try!(entries.remove(&childs_name).ok_or(__WASI_EINVAL)); // TODO: make this a debug assert in the future assert!(inode == removed_inode); + debug_assert!(state.fs.inodes[inode].stat.st_nlink > 0); + removed_inode } Kind::Root { .. } => return __WASI_EACCES, _ => unreachable!( "Internal logic error in wasi::path_unlink_file, parent is not a directory" ), + }; + + state.fs.inodes[removed_inode].stat.st_nlink -= 1; + if state.fs.inodes[removed_inode].stat.st_nlink == 0 { + match &mut state.fs.inodes[removed_inode].kind { + Kind::File { handle, path } => { + if let Some(h) = handle { + wasi_try!(h.unlink().ok_or(__WASI_EIO)); + } else { + // File is closed + // problem with the abstraction, we can't call unlink because there's no handle + // TODO: replace this code in 0.7.0 + wasi_try!(std::fs::remove_file(path).map_err(|_| __WASI_EIO)); + } + } + _ => unimplemented!("wasi::path_unlink_file for non-files"), + } + let inode_was_removed = unsafe { state.fs.remove_inode(removed_inode) }; + assert!( + inode_was_removed, + "Inode could not be removed because it doesn't exist" + ); } - let inode_was_removed = unsafe { state.fs.remove_inode(inode) }; - assert!( - inode_was_removed, - "Inode could not be removed because it doesn't exist" - ); - let _result = wasi_try!(std::fs::remove_file(host_path_to_remove) - .ok() - .ok_or(__WASI_EIO)); __WASI_ESUCCESS } diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index 7971fb50332..10c4d24c912 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -296,7 +296,7 @@ pub type __wasi_filedelta_t = i64; pub type __wasi_filesize_t = u64; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[derive(Copy, Clone, PartialEq, Eq)] #[repr(C)] pub struct __wasi_filestat_t { pub st_dev: __wasi_device_t, @@ -309,8 +309,65 @@ pub struct __wasi_filestat_t { pub st_ctim: __wasi_timestamp_t, } +impl Default for __wasi_filestat_t { + fn default() -> Self { + __wasi_filestat_t { + st_dev: Default::default(), + st_ino: Default::default(), + st_filetype: __WASI_FILETYPE_UNKNOWN, + st_nlink: 1, + st_size: Default::default(), + st_atim: Default::default(), + st_mtim: Default::default(), + st_ctim: Default::default(), + } + } +} + +impl std::fmt::Debug for __wasi_filestat_t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let convert_ts_into_time_string = |ts| { + let tspec = time::Timespec::new(ts as i64 / 1_000_000_000, (ts % 1_000_000_000) as i32); + let tm = time::at(tspec); + let out_time = tm.rfc822(); + format!("{} ({})", out_time, ts) + }; + f.debug_struct("__wasi_filestat_t") + .field("st_dev", &self.st_dev) + .field("st_ino", &self.st_ino) + .field( + "st_filetype", + &format!( + "{} ({})", + wasi_filetype_to_name(self.st_filetype), + self.st_filetype, + ), + ) + .field("st_nlink", &self.st_nlink) + .field("st_size", &self.st_size) + .field("st_atim", &convert_ts_into_time_string(self.st_atim)) + .field("st_mtim", &convert_ts_into_time_string(self.st_mtim)) + .field("st_ctim", &convert_ts_into_time_string(self.st_ctim)) + .finish() + } +} + unsafe impl ValueType for __wasi_filestat_t {} +pub fn wasi_filetype_to_name(ft: __wasi_filetype_t) -> &'static str { + match ft { + __WASI_FILETYPE_UNKNOWN => "Unknown", + __WASI_FILETYPE_BLOCK_DEVICE => "Block device", + __WASI_FILETYPE_CHARACTER_DEVICE => "Character device", + __WASI_FILETYPE_DIRECTORY => "Directory", + __WASI_FILETYPE_REGULAR_FILE => "Regular file", + __WASI_FILETYPE_SOCKET_DGRAM => "Socket dgram", + __WASI_FILETYPE_SOCKET_STREAM => "Socket stream", + __WASI_FILETYPE_SYMBOLIC_LINK => "Symbolic link", + _ => "Invalid", + } +} + pub type __wasi_filetype_t = u8; pub const __WASI_FILETYPE_UNKNOWN: u8 = 0; pub const __WASI_FILETYPE_BLOCK_DEVICE: u8 = 1; @@ -384,6 +441,56 @@ pub const __WASI_RIGHT_PATH_REMOVE_DIRECTORY: u64 = 1 << 26; pub const __WASI_RIGHT_POLL_FD_READWRITE: u64 = 1 << 27; pub const __WASI_RIGHT_SOCK_SHUTDOWN: u64 = 1 << 28; +/// function for debugging rights issues +#[allow(dead_code)] +pub fn print_right_set(rights: __wasi_rights_t) { + // BTreeSet for consistent order + let mut right_set = std::collections::BTreeSet::new(); + for i in 0..28 { + let cur_right = rights & (1 << i); + if cur_right != 0 { + right_set.insert(right_to_string(cur_right).unwrap_or("INVALID RIGHT")); + } + } + println!("{:#?}", right_set); +} + +/// expects a single right, returns None if out of bounds or > 1 bit set +pub fn right_to_string(right: __wasi_rights_t) -> Option<&'static str> { + Some(match right { + __WASI_RIGHT_FD_DATASYNC => "__WASI_RIGHT_FD_DATASYNC", + __WASI_RIGHT_FD_READ => "__WASI_RIGHT_FD_READ", + __WASI_RIGHT_FD_SEEK => "__WASI_RIGHT_FD_SEEK", + __WASI_RIGHT_FD_FDSTAT_SET_FLAGS => "__WASI_RIGHT_FD_FDSTAT_SET_FLAGS", + __WASI_RIGHT_FD_SYNC => "__WASI_RIGHT_FD_SYNC", + __WASI_RIGHT_FD_TELL => "__WASI_RIGHT_FD_TELL", + __WASI_RIGHT_FD_WRITE => "__WASI_RIGHT_FD_WRITE", + __WASI_RIGHT_FD_ADVISE => "__WASI_RIGHT_FD_ADVISE", + __WASI_RIGHT_FD_ALLOCATE => "__WASI_RIGHT_FD_ALLOCATE", + __WASI_RIGHT_PATH_CREATE_DIRECTORY => "__WASI_RIGHT_PATH_CREATE_DIRECTORY", + __WASI_RIGHT_PATH_CREATE_FILE => "__WASI_RIGHT_PATH_CREATE_FILE", + __WASI_RIGHT_PATH_LINK_SOURCE => "__WASI_RIGHT_PATH_LINK_SOURCE", + __WASI_RIGHT_PATH_LINK_TARGET => "__WASI_RIGHT_PATH_LINK_TARGET", + __WASI_RIGHT_PATH_OPEN => "__WASI_RIGHT_PATH_OPEN", + __WASI_RIGHT_FD_READDIR => "__WASI_RIGHT_FD_READDIR", + __WASI_RIGHT_PATH_READLINK => "__WASI_RIGHT_PATH_READLINK", + __WASI_RIGHT_PATH_RENAME_SOURCE => "__WASI_RIGHT_PATH_RENAME_SOURCE", + __WASI_RIGHT_PATH_RENAME_TARGET => "__WASI_RIGHT_PATH_RENAME_TARGET", + __WASI_RIGHT_PATH_FILESTAT_GET => "__WASI_RIGHT_PATH_FILESTAT_GET", + __WASI_RIGHT_PATH_FILESTAT_SET_SIZE => "__WASI_RIGHT_PATH_FILESTAT_SET_SIZE", + __WASI_RIGHT_PATH_FILESTAT_SET_TIMES => "__WASI_RIGHT_PATH_FILESTAT_SET_TIMES", + __WASI_RIGHT_FD_FILESTAT_GET => "__WASI_RIGHT_FD_FILESTAT_GET", + __WASI_RIGHT_FD_FILESTAT_SET_SIZE => "__WASI_RIGHT_FD_FILESTAT_SET_SIZE", + __WASI_RIGHT_FD_FILESTAT_SET_TIMES => "__WASI_RIGHT_FD_FILESTAT_SET_TIMES", + __WASI_RIGHT_PATH_SYMLINK => "__WASI_RIGHT_PATH_SYMLINK", + __WASI_RIGHT_PATH_UNLINK_FILE => "__WASI_RIGHT_PATH_UNLINK_FILE", + __WASI_RIGHT_PATH_REMOVE_DIRECTORY => "__WASI_RIGHT_PATH_REMOVE_DIRECTORY", + __WASI_RIGHT_POLL_FD_READWRITE => "__WASI_RIGHT_POLL_FD_READWRITE", + __WASI_RIGHT_SOCK_SHUTDOWN => "__WASI_RIGHT_SOCK_SHUTDOWN", + _ => return None, + }) +} + pub type __wasi_roflags_t = u16; pub const __WASI_SOCK_RECV_DATA_TRUNCATED: u16 = 1 << 0;