diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f784a9e2d..fc92dfc71e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All PRs to the Wasmer repository must add to this file. Blocks of changes will separated by version increments. ## **[Unreleased]** +- [#555](https://github.com/wasmerio/wasmer/pull/555) WASI filesystem rewrite. Major improvements + - adds virtual root showing all preopened directories + - improved sandboxing and code-reuse + - symlinks work in a lot more situations + - many various improvements to most syscalls touching the filesystem ## 0.5.6 - [#565](https://github.com/wasmerio/wasmer/pull/565) Update wapm and bump version to 0.5.6 diff --git a/Makefile b/Makefile index 0b518fe2d9f..2d9af1cf2ad 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,13 @@ generate-emtests: WASM_EMSCRIPTEN_GENERATE_EMTESTS=1 cargo build -p wasmer-emscripten-tests --release generate-wasitests: - WASM_WASI_GENERATE_WASITESTS=1 cargo build -p wasmer-wasi-tests --release -vv + WASM_WASI_GENERATE_WASITESTS=1 cargo build -p wasmer-wasi-tests --release -vv \ + && echo "formatting" \ + && cargo fmt + +spectests-generate: generate-spectests +emtests-generate: generate-emtests +wasitests-generate: generate-wasitests generate: generate-spectests generate-emtests generate-wasitests diff --git a/lib/runtime-core/src/memory/ptr.rs b/lib/runtime-core/src/memory/ptr.rs index 465474c1316..0a9454acc27 100644 --- a/lib/runtime-core/src/memory/ptr.rs +++ b/lib/runtime-core/src/memory/ptr.rs @@ -126,6 +126,15 @@ impl WasmPtr { [index as usize..slice_full_len]; Some(cell_ptrs) } + + pub fn get_utf8_string<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> { + if self.offset as usize + str_len as usize > memory.size().bytes().0 { + return None; + } + let ptr = unsafe { memory.view::().as_ptr().add(self.offset as usize) as *const u8 }; + let slice: &[u8] = unsafe { std::slice::from_raw_parts(ptr, str_len as usize) }; + std::str::from_utf8(slice).ok() + } } unsafe impl WasmExternType for WasmPtr { diff --git a/lib/wasi-tests/build/wasitests.rs b/lib/wasi-tests/build/wasitests.rs index 189ab9c1799..3ad62865493 100644 --- a/lib/wasi-tests/build/wasitests.rs +++ b/lib/wasi-tests/build/wasitests.rs @@ -89,7 +89,7 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { }; let src_code = fs::read_to_string(file).expect("read src file"); - let args = extract_args_from_source_file(&src_code).unwrap_or_default(); + let args: Args = extract_args_from_source_file(&src_code).unwrap_or_default(); let mapdir_args = { let mut out_str = String::new(); @@ -116,12 +116,25 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { out_str }; + let dir_args = { + let mut out_str = String::new(); + out_str.push_str("vec!["); + + for entry in args.po_dirs { + out_str.push_str(&format!("\"{}\".to_string(),", entry)); + } + + out_str.push_str("]"); + out_str + }; + let contents = format!( "#[test]{ignore} fn test_{rs_module_name}() {{ assert_wasi_output!( \"../../{module_path}\", \"{rs_module_name}\", + {dir_args}, {mapdir_args}, {envvar_args}, \"../../{test_output_path}\" @@ -132,6 +145,7 @@ fn test_{rs_module_name}() {{ module_path = wasm_out_name, rs_module_name = rs_module_name, test_output_path = format!("{}.out", normalized_name), + dir_args = dir_args, mapdir_args = mapdir_args, envvar_args = envvar_args ); @@ -192,6 +206,8 @@ fn read_ignore_list() -> HashSet { struct Args { pub mapdir: Vec<(String, String)>, pub envvars: Vec<(String, String)>, + /// pre-opened directories + pub po_dirs: Vec, } /// Pulls args to the program out of a comment at the top of the file starting with "// Args:" @@ -237,6 +253,9 @@ fn extract_args_from_source_file(source_code: &str) -> Option { eprintln!("Parse error in env {} not parsed correctly", &tokenized[1]); } } + "dir" => { + args.po_dirs.push(tokenized[1].to_string()); + } e => { eprintln!("WARN: comment arg: {} is not supported", e); } diff --git a/lib/wasi-tests/tests/wasitests/_common.rs b/lib/wasi-tests/tests/wasitests/_common.rs index b22af8989ec..958fdf6ad26 100644 --- a/lib/wasi-tests/tests/wasitests/_common.rs +++ b/lib/wasi-tests/tests/wasitests/_common.rs @@ -1,5 +1,5 @@ macro_rules! assert_wasi_output { - ($file:expr, $name:expr, $mapdir_args:expr, $envvar_args:expr, $expected:expr) => {{ + ($file:expr, $name:expr, $po_dir_args: expr, $mapdir_args:expr, $envvar_args:expr, $expected:expr) => {{ use wasmer_dev_utils::stdio::StdioCapturer; use wasmer_runtime_core::{backend::Compiler, Func}; use wasmer_wasi::generate_import_object; @@ -33,8 +33,7 @@ macro_rules! assert_wasi_output { let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler()) .expect("WASM can't be compiled"); - let import_object = - generate_import_object(vec![], vec![], vec![".".to_string()], $mapdir_args); + let import_object = generate_import_object(vec![], vec![], $po_dir_args, $mapdir_args); let instance = module .instantiate(&import_object) diff --git a/lib/wasi-tests/tests/wasitests/create_dir.rs b/lib/wasi-tests/tests/wasitests/create_dir.rs index 87a73dc2b4c..3c8866721ad 100644 --- a/lib/wasi-tests/tests/wasitests/create_dir.rs +++ b/lib/wasi-tests/tests/wasitests/create_dir.rs @@ -1,9 +1,9 @@ #[test] -#[ignore] fn test_create_dir() { assert_wasi_output!( "../../wasitests/create_dir.wasm", "create_dir", + vec![".".to_string(),], vec![], vec![], "../../wasitests/create_dir.out" diff --git a/lib/wasi-tests/tests/wasitests/envvar.rs b/lib/wasi-tests/tests/wasitests/envvar.rs index f32c6c36e35..7c66b3d2ceb 100644 --- a/lib/wasi-tests/tests/wasitests/envvar.rs +++ b/lib/wasi-tests/tests/wasitests/envvar.rs @@ -4,6 +4,7 @@ fn test_envvar() { "../../wasitests/envvar.wasm", "envvar", vec![], + vec![], vec!["DOG=1".to_string(), "CAT=2".to_string(),], "../../wasitests/envvar.out" ); diff --git a/lib/wasi-tests/tests/wasitests/file_metadata.rs b/lib/wasi-tests/tests/wasitests/file_metadata.rs index f0b0b33aabf..67d3a8611b1 100644 --- a/lib/wasi-tests/tests/wasitests/file_metadata.rs +++ b/lib/wasi-tests/tests/wasitests/file_metadata.rs @@ -3,6 +3,7 @@ fn test_file_metadata() { assert_wasi_output!( "../../wasitests/file_metadata.wasm", "file_metadata", + vec![".".to_string(),], vec![], vec![], "../../wasitests/file_metadata.out" diff --git a/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs b/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs index 6a9c82efa0b..25fcfa1fa70 100644 --- a/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs +++ b/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs @@ -5,6 +5,7 @@ fn test_fs_sandbox_test() { "fs_sandbox_test", vec![], vec![], + vec![], "../../wasitests/fs_sandbox_test.out" ); } diff --git a/lib/wasi-tests/tests/wasitests/fseek.rs b/lib/wasi-tests/tests/wasitests/fseek.rs index e48dd5814d9..a1eac93ed75 100644 --- a/lib/wasi-tests/tests/wasitests/fseek.rs +++ b/lib/wasi-tests/tests/wasitests/fseek.rs @@ -3,6 +3,7 @@ fn test_fseek() { assert_wasi_output!( "../../wasitests/fseek.wasm", "fseek", + vec![], vec![( ".".to_string(), ::std::path::PathBuf::from("wasitests/test_fs/hamlet") diff --git a/lib/wasi-tests/tests/wasitests/hello.rs b/lib/wasi-tests/tests/wasitests/hello.rs index ec0c0a3519e..8cf9ebe4903 100644 --- a/lib/wasi-tests/tests/wasitests/hello.rs +++ b/lib/wasi-tests/tests/wasitests/hello.rs @@ -5,6 +5,7 @@ fn test_hello() { "hello", vec![], vec![], + vec![], "../../wasitests/hello.out" ); } diff --git a/lib/wasi-tests/tests/wasitests/mapdir.rs b/lib/wasi-tests/tests/wasitests/mapdir.rs index efb99c7a4d6..301f4887ab8 100644 --- a/lib/wasi-tests/tests/wasitests/mapdir.rs +++ b/lib/wasi-tests/tests/wasitests/mapdir.rs @@ -3,6 +3,7 @@ fn test_mapdir() { assert_wasi_output!( "../../wasitests/mapdir.wasm", "mapdir", + vec![], vec![( ".".to_string(), ::std::path::PathBuf::from("wasitests/test_fs/hamlet") diff --git a/lib/wasi-tests/tests/wasitests/mod.rs b/lib/wasi-tests/tests/wasitests/mod.rs index 7331b309895..a7d06db0616 100644 --- a/lib/wasi-tests/tests/wasitests/mod.rs +++ b/lib/wasi-tests/tests/wasitests/mod.rs @@ -13,3 +13,6 @@ mod fseek; mod hello; mod mapdir; mod quine; +mod readlink; +mod wasi_sees_virtual_root; +mod writing; diff --git a/lib/wasi-tests/tests/wasitests/quine.rs b/lib/wasi-tests/tests/wasitests/quine.rs index 193b2c19e4b..b8ab50ce7bc 100644 --- a/lib/wasi-tests/tests/wasitests/quine.rs +++ b/lib/wasi-tests/tests/wasitests/quine.rs @@ -3,6 +3,7 @@ fn test_quine() { assert_wasi_output!( "../../wasitests/quine.wasm", "quine", + vec![".".to_string(),], vec![], vec![], "../../wasitests/quine.out" diff --git a/lib/wasi-tests/tests/wasitests/readlink.rs b/lib/wasi-tests/tests/wasitests/readlink.rs new file mode 100644 index 00000000000..72e0c7fa760 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/readlink.rs @@ -0,0 +1,14 @@ +#[test] +fn test_readlink() { + assert_wasi_output!( + "../../wasitests/readlink.wasm", + "readlink", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/readlink.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs b/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs new file mode 100644 index 00000000000..f604f967864 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs @@ -0,0 +1,24 @@ +#[test] +fn test_wasi_sees_virtual_root() { + assert_wasi_output!( + "../../wasitests/wasi_sees_virtual_root.wasm", + "wasi_sees_virtual_root", + vec![], + vec![ + ( + "act1".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1") + ), + ( + "act2".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act2") + ), + ( + "act1-again".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1") + ), + ], + vec![], + "../../wasitests/wasi_sees_virtual_root.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/writing.rs b/lib/wasi-tests/tests/wasitests/writing.rs new file mode 100644 index 00000000000..2709f2cfae3 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/writing.rs @@ -0,0 +1,24 @@ +#[test] +fn test_writing() { + assert_wasi_output!( + "../../wasitests/writing.wasm", + "writing", + vec![], + vec![ + ( + "act1".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1") + ), + ( + "act2".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act2") + ), + ( + "act1-again".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet/act1") + ), + ], + vec![], + "../../wasitests/writing.out" + ); +} diff --git a/lib/wasi-tests/wasitests/create_dir.rs b/lib/wasi-tests/wasitests/create_dir.rs index 514387b0205..21e07d619ef 100644 --- a/lib/wasi-tests/wasitests/create_dir.rs +++ b/lib/wasi-tests/wasitests/create_dir.rs @@ -1,3 +1,6 @@ +// Args: +// dir: . + use std::fs; use std::io::{Read, Seek, SeekFrom, Write}; use std::path::*; diff --git a/lib/wasi-tests/wasitests/create_dir.wasm b/lib/wasi-tests/wasitests/create_dir.wasm index 6908bfbe373..9847f3bec7a 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/file_metadata.out b/lib/wasi-tests/wasitests/file_metadata.out index 7250bc00f3a..faa15dc2901 100644 --- a/lib/wasi-tests/wasitests/file_metadata.out +++ b/lib/wasi-tests/wasitests/file_metadata.out @@ -1,3 +1,3 @@ is dir: false filetype: false true false -file info: 456 +file info: 476 diff --git a/lib/wasi-tests/wasitests/file_metadata.rs b/lib/wasi-tests/wasitests/file_metadata.rs index 630382a0113..67c3999ccba 100644 --- a/lib/wasi-tests/wasitests/file_metadata.rs +++ b/lib/wasi-tests/wasitests/file_metadata.rs @@ -1,3 +1,6 @@ +// Args: +// dir: . + use std::fs; use std::io::Read; diff --git a/lib/wasi-tests/wasitests/ignores.txt b/lib/wasi-tests/wasitests/ignores.txt index 061ac5e1e6d..8b137891791 100644 --- a/lib/wasi-tests/wasitests/ignores.txt +++ b/lib/wasi-tests/wasitests/ignores.txt @@ -1 +1 @@ -create_dir + diff --git a/lib/wasi-tests/wasitests/mapdir.out b/lib/wasi-tests/wasitests/mapdir.out index a40625b2412..8f5d6ad4449 100644 --- a/lib/wasi-tests/wasitests/mapdir.out +++ b/lib/wasi-tests/wasitests/mapdir.out @@ -4,3 +4,4 @@ "./act3" "./act4" "./act5" +"./bookmarks" diff --git a/lib/wasi-tests/wasitests/mapdir.rs b/lib/wasi-tests/wasitests/mapdir.rs index 8eb90050bc6..bdf7a774bb0 100644 --- a/lib/wasi-tests/wasitests/mapdir.rs +++ b/lib/wasi-tests/wasitests/mapdir.rs @@ -4,8 +4,6 @@ use std::fs; fn main() { - #[cfg(not(target_os = "wasi"))] - let cur_dir = std::env::current_dir().unwrap(); #[cfg(not(target_os = "wasi"))] std::env::set_current_dir("wasitests/test_fs/hamlet").unwrap(); @@ -19,7 +17,4 @@ fn main() { for p in out { println!("{}", p); } - // return to the current directory - #[cfg(not(target_os = "wasi"))] - std::env::set_current_dir(cur_dir).unwrap(); } diff --git a/lib/wasi-tests/wasitests/quine.out b/lib/wasi-tests/wasitests/quine.out index 13de73d0540..8edcd3d8e0e 100644 --- a/lib/wasi-tests/wasitests/quine.out +++ b/lib/wasi-tests/wasitests/quine.out @@ -1,3 +1,6 @@ +// Args: +// dir: . + use std::fs; use std::io::Read; diff --git a/lib/wasi-tests/wasitests/quine.rs b/lib/wasi-tests/wasitests/quine.rs index 6ca65ee8c4e..0d338d39c61 100644 --- a/lib/wasi-tests/wasitests/quine.rs +++ b/lib/wasi-tests/wasitests/quine.rs @@ -1,3 +1,6 @@ +// Args: +// dir: . + use std::fs; use std::io::Read; diff --git a/lib/wasi-tests/wasitests/readlink.out b/lib/wasi-tests/wasitests/readlink.out new file mode 100644 index 00000000000..547b33b18d2 --- /dev/null +++ b/lib/wasi-tests/wasitests/readlink.out @@ -0,0 +1,4 @@ +../act1/scene2.txt +SCENE II. A room of state in the castle. + + Enter KING CLAUDIUS, QUEEN GERTRUDE, HAMLET, POLONIUS, LAERTES, VOLTIMAND, CORNELI diff --git a/lib/wasi-tests/wasitests/readlink.rs b/lib/wasi-tests/wasitests/readlink.rs new file mode 100644 index 00000000000..bd04af8514e --- /dev/null +++ b/lib/wasi-tests/wasitests/readlink.rs @@ -0,0 +1,21 @@ +// Args: +// mapdir: .:wasitests/test_fs/hamlet +use std::io::Read; + +fn main() { + #[cfg(not(target_os = "wasi"))] + std::env::set_current_dir("wasitests/test_fs/hamlet").unwrap(); + + let sym_link_path = "bookmarks/2019-07-16"; + + let link_path = std::fs::read_link(sym_link_path).unwrap(); + println!("{}", link_path.to_string_lossy()); + + let mut some_contents = std::fs::File::open(sym_link_path).unwrap(); + + let mut buffer = [0; 128]; + + assert_eq!(some_contents.read(&mut buffer).unwrap(), 128); + let str_val = std::str::from_utf8(&buffer[..]).unwrap(); + println!("{}", str_val); +} diff --git a/lib/wasi-tests/wasitests/readlink.wasm b/lib/wasi-tests/wasitests/readlink.wasm new file mode 100755 index 00000000000..c04abb75db7 Binary files /dev/null and b/lib/wasi-tests/wasitests/readlink.wasm differ diff --git a/lib/wasi-tests/wasitests/test_fs/hamlet/bookmarks/2019-07-16 b/lib/wasi-tests/wasitests/test_fs/hamlet/bookmarks/2019-07-16 new file mode 120000 index 00000000000..b7541e40ff7 --- /dev/null +++ b/lib/wasi-tests/wasitests/test_fs/hamlet/bookmarks/2019-07-16 @@ -0,0 +1 @@ +../act1/scene2.txt \ No newline at end of file diff --git a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.out b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.out new file mode 100644 index 00000000000..f9e5ca114f6 --- /dev/null +++ b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.out @@ -0,0 +1,13 @@ +"/act1" +"/act1-again" +"/act2" +"/act1" +"/act1-again" +"/act2" +"/act1" +"/act1-again" +"/act2" +"/act1" +"/act1-again" +"/act2" +ROOT IS SAFE diff --git a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.rs b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.rs new file mode 100644 index 00000000000..f31df50b748 --- /dev/null +++ b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.rs @@ -0,0 +1,59 @@ +// Args: +// mapdir: act1:wasitests/test_fs/hamlet/act1 +// mapdir: act2:wasitests/test_fs/hamlet/act2 +// mapdir: act1-again:wasitests/test_fs/hamlet/act1 + +use std::fs; + +fn main() { + // just cheat in this test because there is no comparison for native + #[cfg(not(target_os = "wasi"))] + let results = { + let start = vec!["\"/act1\"", "\"/act1-again\"", "\"/act2\""]; + + let mut out = vec![]; + for _ in 0..4 { + for path_str in &start { + out.push(path_str.to_string()); + } + } + + out.push("ROOT IS SAFE".to_string()); + out + }; + + #[cfg(target_os = "wasi")] + let results = { + let mut out = vec![]; + + let read_dir = fs::read_dir("/").unwrap(); + for entry in read_dir { + out.push(format!("{:?}", entry.unwrap().path())) + } + let read_dir = fs::read_dir("act1/..").unwrap(); + for entry in read_dir { + out.push(format!("{:?}", entry.unwrap().path())) + } + let read_dir = fs::read_dir("act1/../../..").unwrap(); + for entry in read_dir { + out.push(format!("{:?}", entry.unwrap().path())) + } + let read_dir = fs::read_dir("act1/../../act2/../act1/../../../").unwrap(); + for entry in read_dir { + out.push(format!("{:?}", entry.unwrap().path())) + } + let f = fs::OpenOptions::new().write(true).open("/abc"); + + if f.is_ok() { + out.push("ROOT IS NOT SAFE".to_string()); + } else { + out.push("ROOT IS SAFE".to_string()); + } + + out + }; + + for result in results { + println!("{}", result); + } +} diff --git a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm new file mode 100755 index 00000000000..6d0b3cd3a97 Binary files /dev/null and b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm differ diff --git a/lib/wasi-tests/wasitests/writing.out b/lib/wasi-tests/wasitests/writing.out new file mode 100644 index 00000000000..a471ade37c8 --- /dev/null +++ b/lib/wasi-tests/wasitests/writing.out @@ -0,0 +1,2 @@ +abcdefghijklmnopqrstuvwxyz +file is gone diff --git a/lib/wasi-tests/wasitests/writing.rs b/lib/wasi-tests/wasitests/writing.rs new file mode 100644 index 00000000000..dd91c3aeed9 --- /dev/null +++ b/lib/wasi-tests/wasitests/writing.rs @@ -0,0 +1,41 @@ +// Args: +// mapdir: act1:wasitests/test_fs/hamlet/act1 +// mapdir: act2:wasitests/test_fs/hamlet/act2 +// mapdir: act1-again:wasitests/test_fs/hamlet/act1 + +use std::fs; +use std::io::Write; + +pub const BYTE_STR: &'static [u8] = b"abcdefghijklmnopqrstuvwxyz"; + +fn main() { + #[cfg(not(target_os = "wasi"))] + do_logic_on_path( + "wasitests/test_fs/hamlet/act1/abc", + "wasitests/test_fs/hamlet/act1/abc", + ); + + #[cfg(target_os = "wasi")] + do_logic_on_path("/act1/abc", "act1-again/abc"); +} + +fn do_logic_on_path(path: &'static str, alt_path: &'static str) { + { + let mut f = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(path) + .unwrap(); + f.write_all(BYTE_STR).unwrap(); + } + + println!("{}", fs::read_to_string(alt_path).unwrap()); + fs::remove_file(path).unwrap(); + + let file_path = std::path::Path::new(path); + if file_path.exists() { + println!("file is here"); + } else { + println!("file is gone") + } +} diff --git a/lib/wasi-tests/wasitests/writing.wasm b/lib/wasi-tests/wasitests/writing.wasm new file mode 100755 index 00000000000..eae2eb97bcc Binary files /dev/null and b/lib/wasi-tests/wasitests/writing.wasm differ diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 2ebf294b31e..51371480fd9 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -1,7 +1,5 @@ #![deny(unused_imports, unused_variables, unused_unsafe, unreachable_patterns)] -#[macro_use] -extern crate log; #[cfg(target = "windows")] extern crate winapi; diff --git a/lib/wasi/src/macros.rs b/lib/wasi/src/macros.rs index 86b6b07dcbd..819a4c91b89 100644 --- a/lib/wasi/src/macros.rs +++ b/lib/wasi/src/macros.rs @@ -12,7 +12,7 @@ macro_rules! wasi_try { } } }}; - ($expr:expr; $e:expr) => {{ + ($expr:expr, $e:expr) => {{ let opt: Option<_> = $expr; wasi_try!(opt.ok_or($e)) }}; diff --git a/lib/wasi/src/ptr.rs b/lib/wasi/src/ptr.rs index 0af012eacdd..87bdc104c3c 100644 --- a/lib/wasi/src/ptr.rs +++ b/lib/wasi/src/ptr.rs @@ -75,4 +75,9 @@ impl WasmPtr { ) -> Result<&'a [Cell], __wasi_errno_t> { self.0.deref(memory, index, length).ok_or(__WASI_EFAULT) } + + #[inline(always)] + pub fn get_utf8_string<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> { + self.0.get_utf8_string(memory, str_len) + } } diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state.rs index bcb34d333ec..1c428a2af69 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -3,24 +3,32 @@ // file_like::{FileLike, Metadata}; // }; use crate::syscalls::types::*; -use generational_arena::{Arena, Index as Inode}; +use generational_arena::Arena; +pub use generational_arena::Index as Inode; use hashbrown::hash_map::{Entry, HashMap}; use std::{ + borrow::Borrow, cell::Cell, fs, io::{self, Read, Seek, Write}, - path::PathBuf, + path::{Path, PathBuf}, time::SystemTime, }; use wasmer_runtime_core::debug; -pub const MAX_SYMLINKS: usize = 100; +/// A completely aribtrary "big enough" number used as the upper limit for +/// the number of symlinks that can be traversed when resolving a path +pub const MAX_SYMLINKS: u32 = 128; #[derive(Debug)] pub enum WasiFile { HostFile(fs::File), } +impl WasiFile { + pub fn close(self) {} +} + impl Write for WasiFile { fn write(&mut self, buf: &[u8]) -> io::Result { match self { @@ -81,6 +89,7 @@ impl Seek for WasiFile { } } +/// A file that Wasi knows about that may or may not be open #[derive(Debug)] pub struct InodeVal { pub stat: __wasi_filestat_t, @@ -89,54 +98,14 @@ pub struct InodeVal { pub kind: Kind, } -impl InodeVal { - // TODO: clean this up - pub fn from_file_metadata( - metadata: &std::fs::Metadata, - name: String, - is_preopened: bool, - kind: Kind, - ) -> Self { - InodeVal { - stat: __wasi_filestat_t { - st_filetype: if metadata.is_dir() { - __WASI_FILETYPE_DIRECTORY - } else { - __WASI_FILETYPE_REGULAR_FILE - }, - st_size: metadata.len(), - st_atim: metadata - .accessed() - .ok() - .and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|duration| duration.as_nanos() as u64) - .unwrap_or(0), - st_ctim: metadata - .created() - .ok() - .and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|duration| duration.as_nanos() as u64) - .unwrap_or(0), - st_mtim: metadata - .modified() - .ok() - .and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|duration| duration.as_nanos() as u64) - .unwrap_or(0), - ..__wasi_filestat_t::default() - }, - is_preopened, - name, - kind, - } - } -} - #[allow(dead_code)] #[derive(Debug)] pub enum Kind { File { - handle: WasiFile, + /// the open file, if it's open + handle: Option, + /// the path to the file + path: PathBuf, }, Dir { /// Parent directory @@ -147,8 +116,26 @@ pub enum Kind { /// The entries of a directory are lazily filled. entries: HashMap, }, + /// The same as Dir but without the irrelevant bits + /// The root is immutable after creation; generally the Kind::Root + /// branch of whatever code you're writing will be a simpler version of + /// your Kind::Dir logic + Root { + entries: HashMap, + }, + /// The first two fields are data _about_ the symlink + /// the last field is the data _inside_ the symlink + /// + /// `base_po_dir` should never be the root because: + /// - Right now symlinks are not allowed in the immutable root + /// - There is always a closer pre-opened dir to the symlink file (by definition of the root being a collection of preopened dirs) Symlink { - forwarded: Inode, + /// The preopened dir that this symlink file is relative to (via `path_to_symlink`) + base_po_dir: __wasi_fd_t, + /// The path to the symlink from the `base_po_dir` + path_to_symlink: PathBuf, + /// the value of the symlink as a relative path + relative_path: PathBuf, }, Buffer { buffer: Vec, @@ -167,11 +154,12 @@ pub struct Fd { #[derive(Debug)] pub struct WasiFs { //pub repo: Repo, + pub preopen_fds: Vec, pub name_map: HashMap, pub inodes: Arena, pub fd_map: HashMap, pub next_fd: Cell, - pub inode_counter: Cell, + inode_counter: Cell, } impl WasiFs { @@ -182,12 +170,41 @@ impl WasiFs { debug!("wasi::fs::inodes"); let inodes = Arena::new(); let mut wasi_fs = Self { + preopen_fds: vec![], name_map: HashMap::new(), - inodes: inodes, + inodes, fd_map: HashMap::new(), next_fd: Cell::new(3), - inode_counter: Cell::new(1000), + inode_counter: Cell::new(1024), + }; + // create virtual root + let root_inode = { + let all_rights = 0x1FFFFFFF; + // TODO: make this a list of positive rigths instead of negative ones + // root gets all right for now + let root_rights = all_rights + /*& (!__WASI_RIGHT_FD_WRITE) + & (!__WASI_RIGHT_FD_ALLOCATE) + & (!__WASI_RIGHT_PATH_CREATE_DIRECTORY) + & (!__WASI_RIGHT_PATH_CREATE_FILE) + & (!__WASI_RIGHT_PATH_LINK_SOURCE) + & (!__WASI_RIGHT_PATH_RENAME_SOURCE) + & (!__WASI_RIGHT_PATH_RENAME_TARGET) + & (!__WASI_RIGHT_PATH_FILESTAT_SET_SIZE) + & (!__WASI_RIGHT_PATH_FILESTAT_SET_TIMES) + & (!__WASI_RIGHT_FD_FILESTAT_SET_SIZE) + & (!__WASI_RIGHT_FD_FILESTAT_SET_TIMES) + & (!__WASI_RIGHT_PATH_SYMLINK) + & (!__WASI_RIGHT_PATH_UNLINK_FILE) + & (!__WASI_RIGHT_PATH_REMOVE_DIRECTORY)*/; + let inode = wasi_fs.create_virtual_root(); + let fd = wasi_fs + .create_fd(root_rights, root_rights, 0, inode) + .expect("Could not create root fd"); + wasi_fs.preopen_fds.push(fd); + inode }; + debug!("wasi::fs::preopen_dirs"); for dir in preopened_dirs { debug!("Attempting to preopen {}", &dir); @@ -197,7 +214,7 @@ impl WasiFs { let cur_dir_metadata = cur_dir.metadata().expect("Could not find directory"); let kind = if cur_dir_metadata.is_dir() { Kind::Dir { - parent: None, + parent: Some(root_inode), path: cur_dir.clone(), entries: Default::default(), } @@ -208,14 +225,22 @@ impl WasiFs { )); }; // TODO: handle nested pats in `file` - let inode_val = - InodeVal::from_file_metadata(&cur_dir_metadata, dir.clone(), true, kind); - - let inode = wasi_fs.inodes.insert(inode_val); - wasi_fs.inodes[inode].stat.st_ino = wasi_fs.inode_counter.get(); - wasi_fs + let inode = wasi_fs + .create_inode(kind, true, dir.to_string()) + .map_err(|e| { + format!( + "Failed to create inode for preopened dir: WASI error code: {}", + e + ) + })?; + let fd = wasi_fs .create_fd(default_rights, default_rights, 0, inode) .expect("Could not open fd"); + if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { + // todo handle collisions + assert!(entries.insert(dir.to_string(), inode).is_none()) + } + wasi_fs.preopen_fds.push(fd); } debug!("wasi::fs::mapped_dirs"); for (alias, real_dir) in mapped_dirs { @@ -227,7 +252,7 @@ impl WasiFs { .expect("mapped dir not at previously verified location"); let kind = if cur_dir_metadata.is_dir() { Kind::Dir { - parent: None, + parent: Some(root_inode), path: real_dir.clone(), entries: Default::default(), } @@ -238,19 +263,34 @@ impl WasiFs { )); }; // TODO: handle nested pats in `file` - let inode_val = - InodeVal::from_file_metadata(&cur_dir_metadata, alias.clone(), true, kind); - - let inode = wasi_fs.inodes.insert(inode_val); - wasi_fs.inodes[inode].stat.st_ino = wasi_fs.inode_counter.get(); - wasi_fs + let inode = wasi_fs + .create_inode(kind, true, alias.clone()) + .map_err(|e| { + format!( + "Failed to create inode for preopened dir: WASI error code: {}", + e + ) + })?; + let fd = wasi_fs .create_fd(default_rights, default_rights, 0, inode) .expect("Could not open fd"); + if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { + // todo handle collisions + assert!(entries.insert(alias.clone(), inode).is_none()); + } + wasi_fs.preopen_fds.push(fd); } + debug!("wasi::fs::end"); Ok(wasi_fs) } + fn get_next_inode_index(&mut self) -> u64 { + let next = self.inode_counter.get(); + self.inode_counter.set(next + 1); + next + } + #[allow(dead_code)] fn get_inode(&mut self, path: &str) -> Option { Some(match self.name_map.entry(path.to_string()) { @@ -311,6 +351,7 @@ impl WasiFs { }) } + /* #[allow(dead_code)] fn filestat_inode( &self, @@ -318,8 +359,13 @@ impl WasiFs { flags: __wasi_lookupflags_t, ) -> Result<__wasi_filestat_t, __wasi_errno_t> { let inode_val = &self.inodes[inode]; - if let (true, Kind::Symlink { mut forwarded }) = - (flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind) + if let ( + true, + Kind::Symlink { + mut forwarded, + path, + }, + ) = (flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind) { // Time to follow the symlink. let mut counter = 0; @@ -355,6 +401,224 @@ impl WasiFs { self.filestat_inode(inode, flags) } + */ + + fn get_inode_at_path_inner( + &mut self, + base: __wasi_fd_t, + path: &str, + mut symlink_count: u32, + follow_symlinks: bool, + ) -> Result { + if symlink_count > MAX_SYMLINKS { + return Err(__WASI_EMLINK); + } + + let base_dir = self.get_fd(base)?; + let path: &Path = Path::new(path); + + let mut cur_inode = base_dir.inode; + // TODO: rights checks + 'path_iter: for component in path.components() { + // for each component traverse file structure + // loading inodes as necessary + 'symlink_resolution: while symlink_count < MAX_SYMLINKS { + match &mut self.inodes[cur_inode].kind { + Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"), + Kind::Dir { + ref mut entries, + ref path, + ref parent, + .. + } => { + match component.as_os_str().to_string_lossy().borrow() { + ".." => { + if let Some(p) = parent { + cur_inode = *p; + continue 'path_iter; + } else { + return Err(__WASI_EACCES); + } + } + "." => continue 'path_iter, + _ => (), + } + // used for full resolution of symlinks + let mut loop_for_symlink = false; + if let Some(entry) = + entries.get(component.as_os_str().to_string_lossy().as_ref()) + { + cur_inode = *entry; + } else { + let file = { + let mut cd = path.clone(); + cd.push(component); + cd + }; + // 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(); + + let kind = if file_type.is_dir() { + // load DIR + Kind::Dir { + parent: Some(cur_inode), + path: file.clone(), + entries: Default::default(), + } + } else if file_type.is_file() { + // load file + Kind::File { + handle: None, + path: file.clone(), + } + } else if file_type.is_symlink() { + let link_value = file.read_link().ok().ok_or(__WASI_EIO)?; + debug!("attempting to decompose path {:?}", link_value); + + let (pre_open_dir_fd, relative_path) = if link_value.is_relative() { + self.path_into_pre_open_and_relative_path(&file)? + } else { + unimplemented!("Absolute symlinks are not yet supported"); + }; + loop_for_symlink = true; + symlink_count += 1; + Kind::Symlink { + base_po_dir: pre_open_dir_fd, + path_to_symlink: relative_path, + relative_path: link_value, + } + } else { + unimplemented!("state::get_inode_at_path unknown file type: not file, directory, or symlink"); + }; + + cur_inode = + self.create_inode(kind, false, file.to_string_lossy().to_string())?; + if loop_for_symlink && follow_symlinks { + continue 'symlink_resolution; + } + } + } + Kind::Root { entries } => { + match component.as_os_str().to_string_lossy().borrow() { + // the root's parent is the root + ".." => continue 'path_iter, + // the root's current directory is the root + "." => continue 'path_iter, + _ => (), + } + + if let Some(entry) = + entries.get(component.as_os_str().to_string_lossy().as_ref()) + { + cur_inode = *entry; + } else { + return Err(__WASI_EINVAL); + } + } + Kind::File { .. } => { + return Err(__WASI_ENOTDIR); + } + Kind::Symlink { + base_po_dir, + path_to_symlink, + relative_path, + } => { + let new_base_dir = *base_po_dir; + // allocate to reborrow mutabily to recur + let new_path = { + /*if let Kind::Root { .. } = self.inodes[base_po_dir].kind { + assert!(false, "symlinks should never be relative to the root"); + }*/ + let mut base = path_to_symlink.clone(); + // remove the symlink file itself from the path, leaving just the path from the base + // to the dir containing the symlink + base.pop(); + base.push(relative_path); + base.to_string_lossy().to_string() + }; + let symlink_inode = self.get_inode_at_path_inner( + new_base_dir, + &new_path, + symlink_count + 1, + follow_symlinks, + )?; + cur_inode = symlink_inode; + //continue 'symlink_resolution; + } + } + break 'symlink_resolution; + } + } + + Ok(cur_inode) + } + + fn path_into_pre_open_and_relative_path( + &self, + path: &Path, + ) -> Result<(__wasi_fd_t, PathBuf), __wasi_errno_t> { + // for each preopened directory + for po_fd in &self.preopen_fds { + let po_inode = self.fd_map[po_fd].inode; + let po_path = match &self.inodes[po_inode].kind { + Kind::Dir { path, .. } => &**path, + Kind::Root { .. } => Path::new("/"), + _ => unreachable!("Preopened FD that's not a directory or the root"), + }; + // stem path based on it + if let Ok(rest) = path.strip_prefix(po_path) { + // if any path meets this criteria + // (verify that all remaining components are not symlinks except for maybe last? (or do the more complex logic of resolving intermediary symlinks)) + // return preopened dir and the rest of the path + + return Ok((*po_fd, rest.to_owned())); + } + } + Err(__WASI_EINVAL) // this may not make sense + } + + /// gets a host file from a base directory and a path + /// this function ensures the fs remains sandboxed + // NOTE: follow symlinks is super weird right now + // even if it's false, it still follows symlinks, just not the last + // symlink so + // This will be resolved when we have tests asserting the correct behavior + pub fn get_inode_at_path( + &mut self, + base: __wasi_fd_t, + path: &str, + follow_symlinks: bool, + ) -> Result { + self.get_inode_at_path_inner(base, path, 0, follow_symlinks) + } + + /// Returns the parent Dir or Root that the file at a given path is in and the file name + /// stripped off + pub fn get_parent_inode_at_path( + &mut self, + base: __wasi_fd_t, + path: &Path, + follow_symlinks: bool, + ) -> Result<(Inode, String), __wasi_errno_t> { + let mut parent_dir = std::path::PathBuf::new(); + let mut components = path.components().rev(); + let new_entity_name = components + .next() + .ok_or(__WASI_EINVAL)? + .as_os_str() + .to_string_lossy() + .to_string(); + for comp in components.rev() { + parent_dir.push(comp); + } + self.get_inode_at_path(base, &parent_dir.to_string_lossy(), follow_symlinks) + .map(|v| (v, new_entity_name)) + } + + pub fn get_fd(&self, fd: __wasi_fd_t) -> Result<&Fd, __wasi_errno_t> { + self.fd_map.get(&fd).ok_or(__WASI_EBADF) + } 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)?; @@ -414,17 +678,39 @@ impl WasiFs { let inode = &mut self.inodes[fd.inode]; match &mut inode.kind { - Kind::File { handle } => handle.flush().map_err(|_| __WASI_EIO)?, + Kind::File { + handle: Some(handle), + .. + } => handle.flush().map_err(|_| __WASI_EIO)?, // TODO: verify this behavior Kind::Dir { .. } => return Err(__WASI_EISDIR), Kind::Symlink { .. } => unimplemented!(), Kind::Buffer { .. } => (), + _ => return Err(__WASI_EIO), } } } Ok(()) } + /// Creates an inode and inserts it given a Kind and some extra data + pub fn create_inode( + &mut self, + kind: Kind, + is_preopened: bool, + name: String, + ) -> Result { + let mut stat = self.get_stat_for_kind(&kind).ok_or(__WASI_EIO)?; + stat.st_ino = self.get_next_inode_index(); + + Ok(self.inodes.insert(InodeVal { + stat: stat, + is_preopened, + name, + kind, + })) + } + pub fn create_fd( &mut self, rights: __wasi_rights_t, @@ -447,11 +733,90 @@ impl WasiFs { Ok(idx) } - pub fn get_base_path_for_directory(&self, directory: Inode) -> Option { - if let Kind::Dir { path, .. } = &self.inodes[directory].kind { - return Some(path.to_string_lossy().to_string()); - } - None + /// This function is unsafe because it's the caller's responsibility to ensure that + /// all refences to the given inode have been removed from the filesystem + /// + /// returns true if the inode existed and was removed + pub unsafe fn remove_inode(&mut self, inode: Inode) -> bool { + self.inodes.remove(inode).is_some() + } + + fn create_virtual_root(&mut self) -> Inode { + let stat = __wasi_filestat_t { + st_filetype: __WASI_FILETYPE_DIRECTORY, + st_ino: self.get_next_inode_index(), + ..__wasi_filestat_t::default() + }; + let root_kind = Kind::Root { + entries: HashMap::new(), + }; + + self.inodes.insert(InodeVal { + stat: stat, + is_preopened: true, + name: "/".to_string(), + kind: root_kind, + }) + } + + pub fn get_stat_for_kind(&self, kind: &Kind) -> Option<__wasi_filestat_t> { + let md = match kind { + Kind::File { handle, path } => match handle { + Some(WasiFile::HostFile(hf)) => hf.metadata().ok()?, + None => path.metadata().ok()?, + }, + Kind::Dir { path, .. } => path.metadata().ok()?, + Kind::Symlink { + base_po_dir, + path_to_symlink, + .. + } => { + let base_po_inode = &self.fd_map[base_po_dir].inode; + let base_po_inode_v = &self.inodes[*base_po_inode]; + match &base_po_inode_v.kind { + Kind::Root { .. } => { + path_to_symlink.clone().symlink_metadata().ok()? + } + Kind::Dir { path, .. } => { + let mut real_path = path.clone(); + // PHASE 1: ignore all possible symlinks in `relative_path` + // TODO: walk the segments of `relative_path` via the entries of the Dir + // use helper function to avoid duplicating this logic (walking this will require + // &self to be &mut sel + // TODO: adjust size of symlink, too + // for all paths adjusted think about this + real_path.push(path_to_symlink); + real_path.symlink_metadata().ok()? + } + // if this triggers, there's a bug in the symlink code + _ => unreachable!("Symlink pointing to something that's not a directory as its base preopened directory"), + } + } + __ => return None, + }; + Some(__wasi_filestat_t { + st_filetype: host_file_type_to_wasi_file_type(md.file_type()), + st_size: md.len(), + st_atim: md + .accessed() + .ok()? + .duration_since(SystemTime::UNIX_EPOCH) + .ok()? + .as_nanos() as u64, + st_mtim: md + .modified() + .ok()? + .duration_since(SystemTime::UNIX_EPOCH) + .ok()? + .as_nanos() as u64, + st_ctim: md + .created() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0), + ..__wasi_filestat_t::default() + }) } } @@ -474,64 +839,3 @@ pub fn host_file_type_to_wasi_file_type(file_type: fs::FileType) -> __wasi_filet __WASI_FILETYPE_UNKNOWN } } - -pub fn get_stat_for_kind(kind: &Kind) -> Option<__wasi_filestat_t> { - match kind { - Kind::File { handle } => match handle { - WasiFile::HostFile(hf) => { - let md = hf.metadata().ok()?; - - Some(__wasi_filestat_t { - st_filetype: host_file_type_to_wasi_file_type(md.file_type()), - st_size: md.len(), - st_atim: md - .accessed() - .ok()? - .duration_since(SystemTime::UNIX_EPOCH) - .ok()? - .as_nanos() as u64, - st_mtim: md - .modified() - .ok()? - .duration_since(SystemTime::UNIX_EPOCH) - .ok()? - .as_nanos() as u64, - st_ctim: md - .created() - .ok() - .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|ct| ct.as_nanos() as u64) - .unwrap_or(0), - ..__wasi_filestat_t::default() - }) - } - }, - Kind::Dir { path, .. } => { - let md = path.metadata().ok()?; - Some(__wasi_filestat_t { - st_filetype: host_file_type_to_wasi_file_type(md.file_type()), - st_size: md.len(), - st_atim: md - .accessed() - .ok()? - .duration_since(SystemTime::UNIX_EPOCH) - .ok()? - .as_nanos() as u64, - st_mtim: md - .modified() - .ok()? - .duration_since(SystemTime::UNIX_EPOCH) - .ok()? - .as_nanos() as u64, - st_ctim: md - .created() - .ok() - .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) - .map(|ct| ct.as_nanos() as u64) - .unwrap_or(0), - ..__wasi_filestat_t::default() - }) - } - _ => None, - } -} diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 440103a99f8..2df8b747777 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -9,12 +9,13 @@ use self::types::*; use crate::{ ptr::{Array, WasmPtr}, state::{ - get_stat_for_kind, host_file_type_to_wasi_file_type, Fd, InodeVal, Kind, WasiFile, - WasiState, MAX_SYMLINKS, + host_file_type_to_wasi_file_type, Fd, Inode, InodeVal, Kind, WasiFile, WasiState, + MAX_SYMLINKS, }, ExitCode, }; use rand::{thread_rng, Rng}; +use std::borrow::Borrow; use std::cell::Cell; use std::convert::Infallible; use std::io::{self, Read, Seek, Write}; @@ -313,12 +314,30 @@ pub fn fd_allocate( /// If `fd` is invalid or not open (TODO: consider __WASI_EINVAL) pub fn fd_close(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_close"); - // FD is too large - return __WASI_EMFILE; - // FD is a directory (due to user input) - return __WASI_EISDIR; - // FD is invalid - return __WASI_EBADF; + + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + + let inode_val = &mut state.fs.inodes[fd_entry.inode]; + if inode_val.is_preopened { + return __WASI_EACCES; + } + match &mut inode_val.kind { + Kind::File { ref mut handle, .. } => { + let mut empty_handle = None; + std::mem::swap(handle, &mut empty_handle); + if let Some(handle_inner) = empty_handle { + handle_inner.close() + } else { + return __WASI_EINVAL; + } + } + Kind::Dir { .. } => return __WASI_EISDIR, + Kind::Root { .. } => return __WASI_EACCES, + Kind::Symlink { .. } | Kind::Buffer { .. } => return __WASI_EINVAL, + } + __WASI_ESUCCESS } @@ -568,28 +587,31 @@ pub fn fd_prestat_dir_name( // check inode-val.is_preopened? - if let Kind::Dir { .. } = inode_val.kind { - // TODO: verify this: null termination, etc - if inode_val.name.len() <= path_len as usize { - let mut i = 0; - for c in inode_val.name.bytes() { - path_chars[i].set(c); - i += 1 - } - path_chars[i].set(0); + match inode_val.kind { + Kind::Dir { .. } | Kind::Root { .. } => { + // TODO: verify this: null termination, etc + if inode_val.name.len() <= path_len as usize { + let mut i = 0; + for c in inode_val.name.bytes() { + path_chars[i].set(c); + i += 1 + } + path_chars[i].set(0); - debug!( - "=> result: \"{}\"", - ::std::str::from_utf8(unsafe { &*(&path_chars[..] as *const [_] as *const [u8]) }) + debug!( + "=> result: \"{}\"", + ::std::str::from_utf8(unsafe { + &*(&path_chars[..] as *const [_] as *const [u8]) + }) .unwrap() - ); + ); - __WASI_ESUCCESS - } else { - __WASI_EOVERFLOW + __WASI_ESUCCESS + } else { + __WASI_EOVERFLOW + } } - } else { - __WASI_ENOTDIR + Kind::Symlink { .. } | Kind::Buffer { .. } | Kind::File { .. } => __WASI_ENOTDIR, } } @@ -648,11 +670,15 @@ pub fn fd_pwrite( let inode = &mut state.fs.inodes[fd_entry.inode]; let bytes_written = match &mut inode.kind { - Kind::File { handle } => { - handle.seek(::std::io::SeekFrom::Start(offset as u64)); - wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.seek(::std::io::SeekFrom::Start(offset as u64)); + wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) + } else { + return __WASI_EINVAL; + } } - Kind::Dir { .. } => { + Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify return __WASI_EISDIR; } @@ -736,11 +762,15 @@ pub fn fd_read( let inode = &mut state.fs.inodes[fd_entry.inode]; let bytes_read = match &mut inode.kind { - Kind::File { handle } => { - handle.seek(::std::io::SeekFrom::Start(offset as u64)); - wasi_try!(read_bytes(handle, memory, iovs_arr_cell)) + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.seek(::std::io::SeekFrom::Start(offset as u64)); + wasi_try!(read_bytes(handle, memory, iovs_arr_cell)) + } else { + return __WASI_EINVAL; + } } - Kind::Dir { .. } => { + Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify return __WASI_EISDIR; } @@ -796,54 +826,79 @@ pub fn fd_readdir( let mut cur_cookie = cookie; let mut buf_idx = 0; - if let Kind::Dir { path, .. } = &state.fs.inodes[working_dir.inode].kind { - // we need to support multiple calls, - // simple and obviously correct implementation for now: - // maintain consistent order via lexacographic sorting - let mut entries = wasi_try!(wasi_try!(std::fs::read_dir(path).map_err(|_| __WASI_EIO)) - .collect::, _>>() - .map_err(|_| __WASI_EIO)); - entries.sort_by(|a, b| a.file_name().cmp(&b.file_name())); - - for entry in entries.iter().skip(cookie as usize) { - cur_cookie += 1; - let entry_path = entry.path(); - let entry_path = wasi_try!(entry_path.file_name().ok_or(__WASI_EIO)); - let entry_path_str = entry_path.to_string_lossy(); - let namlen = entry_path_str.len(); - debug!("Returning dirent for {}", entry_path_str); - let dirent = __wasi_dirent_t { - d_next: cur_cookie, - d_ino: 0, // TODO: inode - d_namlen: namlen as u32, - d_type: host_file_type_to_wasi_file_type(wasi_try!(entry - .file_type() - .map_err(|_| __WASI_EIO))), + let entries = match &state.fs.inodes[working_dir.inode].kind { + Kind::Dir { path, .. } => { + // TODO: refactor this code + // we need to support multiple calls, + // simple and obviously correct implementation for now: + // maintain consistent order via lexacographic sorting + let mut entries = wasi_try!(wasi_try!(std::fs::read_dir(path).map_err(|_| __WASI_EIO)) + .collect::, _>>() + .map_err(|_| __WASI_EIO)); + entries.sort_by(|a, b| a.file_name().cmp(&b.file_name())); + wasi_try!(entries + .into_iter() + .map(|entry| Ok(( + entry.file_name().to_string_lossy().to_string(), + host_file_type_to_wasi_file_type(entry.file_type().map_err(|_| __WASI_EIO)?), + 0, // TODO: inode + ))) + .collect::, __wasi_errno_t>>()) + } + Kind::Root { entries } => { + let sorted_entries = { + let mut entry_vec: Vec<(String, Inode)> = + entries.iter().map(|(a, b)| (a.clone(), *b)).collect(); + entry_vec.sort_by(|a, b| a.0.cmp(&b.0)); + entry_vec }; - let dirent_bytes = dirent_to_le_bytes(&dirent); - let upper_limit = std::cmp::min( - buf_len as usize - buf_idx, - std::mem::size_of::<__wasi_dirent_t>(), - ); - for i in 0..upper_limit { - buf_arr_cell[i + buf_idx].set(dirent_bytes[i]); - } - buf_idx += upper_limit; - if upper_limit != std::mem::size_of::<__wasi_dirent_t>() { - break; - } - let upper_limit = std::cmp::min(buf_len as usize - buf_idx, namlen); - for (i, b) in entry_path_str.bytes().take(upper_limit).enumerate() { - buf_arr_cell[i + buf_idx].set(b); - } - buf_idx += upper_limit; - if upper_limit != namlen { - break; - } + sorted_entries + .into_iter() + .map(|(name, inode)| { + let entry = &state.fs.inodes[inode]; + ( + format!("/{}", entry.name), + entry.stat.st_filetype, + entry.stat.st_ino, + ) + }) + .collect() + } + Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => return __WASI_ENOTDIR, + }; + + for (entry_path_str, wasi_file_type, ino) in entries.iter().skip(cookie as usize) { + cur_cookie += 1; + let namlen = entry_path_str.len(); + debug!("Returning dirent for {}", entry_path_str); + let dirent = __wasi_dirent_t { + d_next: cur_cookie, + d_ino: *ino, + d_namlen: namlen as u32, + d_type: *wasi_file_type, + }; + let dirent_bytes = dirent_to_le_bytes(&dirent); + let upper_limit = std::cmp::min( + buf_len as usize - buf_idx, + std::mem::size_of::<__wasi_dirent_t>(), + ); + for i in 0..upper_limit { + buf_arr_cell[i + buf_idx].set(dirent_bytes[i]); + } + buf_idx += upper_limit; + if upper_limit != std::mem::size_of::<__wasi_dirent_t>() { + break; + } + let upper_limit = std::cmp::min(buf_len as usize - buf_idx, namlen); + for (i, b) in entry_path_str.bytes().take(upper_limit).enumerate() { + buf_arr_cell[i + buf_idx].set(b); + } + buf_idx += upper_limit; + if upper_limit != namlen { + break; } - } else { - return __WASI_ENOTDIR; } + bufused_cell.set(buf_idx as u32); __WASI_ESUCCESS } @@ -906,15 +961,19 @@ pub fn fd_seek( __WASI_WHENCE_END => { use std::io::SeekFrom; match state.fs.inodes[fd_entry.inode].kind { - Kind::File { ref mut handle } => { - let end = wasi_try!(handle.seek(SeekFrom::End(0)).ok().ok_or(__WASI_EIO)); - // TODO: handle case if fd_entry.offset uses 64 bits of a u64 - fd_entry.offset = (end as i64 + offset) as u64; + Kind::File { ref mut handle, .. } => { + if let Some(handle) = handle { + let end = wasi_try!(handle.seek(SeekFrom::End(0)).ok().ok_or(__WASI_EIO)); + // TODO: handle case if fd_entry.offset uses 64 bits of a u64 + fd_entry.offset = (end as i64 + offset) as u64; + } else { + return __WASI_EINVAL; + } } Kind::Symlink { .. } => { unimplemented!("wasi::fd_seek not implemented for symlinks") } - Kind::Dir { .. } => { + Kind::Dir { .. } | Kind::Root { .. } => { // TODO: check this return __WASI_EINVAL; } @@ -1032,12 +1091,15 @@ pub fn fd_write( let inode = &mut state.fs.inodes[fd_entry.inode]; let bytes_written = match &mut inode.kind { - Kind::File { handle } => { - handle.seek(::std::io::SeekFrom::Start(offset as u64)); - - wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) + Kind::File { handle, .. } => { + if let Some(handle) = handle { + handle.seek(::std::io::SeekFrom::Start(offset as u64)); + wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) + } else { + return __WASI_EINVAL; + } } - Kind::Dir { .. } => { + Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify return __WASI_EISDIR; } @@ -1081,17 +1143,15 @@ pub fn path_create_directory( let memory = ctx.memory(0); let state = get_wasi_state(ctx); - let working_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let working_dir = wasi_try!(state.fs.get_fd(fd)).clone(); + if let Kind::Root { .. } = &state.fs.inodes[working_dir.inode].kind { + return __WASI_EACCES; + } if !has_rights(working_dir.rights, __WASI_RIGHT_PATH_CREATE_DIRECTORY) { return __WASI_EACCES; } - let path_cells = wasi_try!(path.deref(memory, 0, path_len)); - let path_string = - wasi_try!( - std::str::from_utf8(unsafe { &*(path_cells as *const [_] as *const [u8]) }) - .map_err(|_| __WASI_EINVAL) - ); - debug!("=> path: {}", &path_string); + let path_string = wasi_try!(path.get_utf8_string(memory, path_len), __WASI_EINVAL); + debug!("=> fd: {}, path: {}", fd, &path_string); let path = std::path::PathBuf::from(path_string); let path_vec = wasi_try!(path @@ -1107,30 +1167,57 @@ pub fn path_create_directory( return __WASI_EINVAL; } - assert!( - path_vec.len() == 1, - "path_create_directory for paths greater than depth 1 has not been implemented because our WASI FS abstractions are a work in progress. We apologize for the inconvenience" - ); - debug!("Path vec: {:#?}", path_vec); - - wasi_try!(std::fs::create_dir(&path).map_err(|_| __WASI_EIO)); + debug!("Looking at components {:?}", &path_vec); - let kind = Kind::Dir { - parent: Some(working_dir.inode), - path: path.clone(), - entries: Default::default(), - }; - let new_inode = state.fs.inodes.insert(InodeVal { - stat: wasi_try!(get_stat_for_kind(&kind).ok_or(__WASI_EIO)), - is_preopened: false, - name: path_vec[0].clone(), - kind, - }); - - if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind { - entries.insert(path_vec[0].clone(), new_inode); - } else { - return __WASI_ENOTDIR; + let mut cur_dir_inode = working_dir.inode; + for comp in &path_vec { + debug!("Creating dir {}", comp); + match &mut state.fs.inodes[cur_dir_inode].kind { + Kind::Dir { + ref mut entries, + path, + parent, + } => { + match comp.borrow() { + ".." => { + if let Some(p) = parent { + cur_dir_inode = *p; + continue; + } + } + "." => continue, + _ => (), + } + if let Some(child) = entries.get(comp) { + cur_dir_inode = *child; + } else { + let mut adjusted_path = path.clone(); + // TODO: double check this doesn't risk breaking the sandbox + adjusted_path.push(comp); + if adjusted_path.exists() && !adjusted_path.is_dir() { + return __WASI_ENOTDIR; + } else if !adjusted_path.exists() { + wasi_try!(std::fs::create_dir(&adjusted_path).ok(), __WASI_EIO); + } + let kind = Kind::Dir { + parent: Some(cur_dir_inode), + path: adjusted_path, + entries: Default::default(), + }; + let new_inode = wasi_try!(state.fs.create_inode(kind, false, comp.to_string())); + // reborrow to insert + if let Kind::Dir { + ref mut entries, .. + } = &mut state.fs.inodes[cur_dir_inode].kind + { + entries.insert(comp.to_string(), new_inode); + } + cur_dir_inode = new_inode; + } + } + Kind::Root { .. } => return __WASI_EACCES, + _ => return __WASI_ENOTDIR, + } } __WASI_ESUCCESS @@ -1162,135 +1249,27 @@ pub fn path_filestat_get( let state = get_wasi_state(ctx); let memory = ctx.memory(0); - let root_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let root_dir = wasi_try!(state.fs.get_fd(fd)); if !has_rights(root_dir.rights, __WASI_RIGHT_PATH_FILESTAT_GET) { return __WASI_EACCES; } - let path_string = wasi_try!(::std::str::from_utf8(unsafe { - &*(wasi_try!(path.deref(memory, 0, path_len)) as *const [_] as *const [u8]) - }) - .map_err(|_| __WASI_EINVAL)); - debug!("=> path: {}", &path_string); - let path = std::path::PathBuf::from(path_string); - let path_vec = path - .components() - .map(|comp| comp.as_os_str().to_string_lossy().to_string()) - .collect::>(); - let buf_cell = wasi_try!(buf.deref(memory)); + let path_string = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); - if path_vec.is_empty() { - return __WASI_EINVAL; - } - let mut cumulative_path = std::path::PathBuf::from(wasi_try!(state - .fs - .get_base_path_for_directory(root_dir.inode) - .ok_or(__WASI_EIO))); - - debug!("=> Path vec: {:?}:", &path_vec); - // find the inode by traversing the path - let mut inode = root_dir.inode; - 'outer: for segment in &path_vec[..(path_vec.len() - 1)] { - // loop to traverse symlinks - // TODO: proper cycle detection - let mut sym_count = 0; - loop { - match &state.fs.inodes[inode].kind { - Kind::Dir { entries, .. } => { - cumulative_path.push(&segment); - if let Some(entry) = entries.get(segment) { - debug!("Entry {:?} found", &segment); - inode = entry.clone(); - } else { - // lazily load - debug!("Lazily loading entry {:?}", &segment); - let path_metadata = - wasi_try!(cumulative_path.metadata().map_err(|_| __WASI_ENOENT)); - if !path_metadata.is_dir() { - // TODO: should this just return invalid arg? - return __WASI_ENOTDIR; - } - let kind = Kind::Dir { - parent: Some(inode), - path: std::path::PathBuf::from(&segment), - entries: Default::default(), - }; - let inode_val = InodeVal::from_file_metadata( - &path_metadata, - segment.clone(), - false, - kind, - ); - let new_inode = state.fs.inodes.insert(inode_val); - let inode_idx = state.fs.inode_counter.get(); - state.fs.inode_counter.replace(inode_idx + 1); - if let Kind::Dir { entries, .. } = &mut state.fs.inodes[inode].kind { - // check that we're not displacing any entries - assert!(entries.insert(segment.clone(), new_inode).is_none()); - state.fs.inodes[new_inode].stat.st_ino = state.fs.inode_counter.get(); - inode = new_inode; - } - debug!("Directory {:#?} lazily loaded", &cumulative_path); - } - continue 'outer; - } - Kind::Symlink { forwarded } => { - // TODO: updated cumulative path - sym_count += 1; - inode = forwarded.clone(); - if sym_count > MAX_SYMLINKS { - return __WASI_ELOOP; - } - } - _ => { - return __WASI_ENOTDIR; - } - } - } - } + debug!("=> base_fd: {}, path: {}", fd, &path_string); - let stat = match &state.fs.inodes[inode].kind { - Kind::Dir { path, entries, .. } => { - // read it from internal data structures if we can - let last_segment = path_vec.last().unwrap(); - cumulative_path.push(last_segment); - - // read it from wasi FS cache first, otherwise check host system - if entries.contains_key(last_segment) { - state.fs.inodes[entries[last_segment]].stat - } else { - // otherwise read it from the host FS - if !cumulative_path.exists() { - return __WASI_ENOENT; - } - let final_path_metadata = - wasi_try!(cumulative_path.metadata().map_err(|_| __WASI_EIO)); - let kind = if final_path_metadata.is_file() { - let file = - wasi_try!(std::fs::File::open(&cumulative_path).ok().ok_or(__WASI_EIO)); - Kind::File { - handle: WasiFile::HostFile(file), - } - } else if final_path_metadata.is_dir() { - Kind::Dir { - parent: Some(inode), - // TODO: verify that this doesn't cause issues with relative paths - path: cumulative_path.clone(), - entries: Default::default(), - } - } else { - // TODO: check this - return __WASI_EINVAL; - }; + 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)); - wasi_try!(get_stat_for_kind(&kind).ok_or(__WASI_EIO)) - } - } - _ => { - return __WASI_ENOTDIR; - } - }; + let buf_cell = wasi_try!(buf.deref(memory)); buf_cell.set(stat); __WASI_ESUCCESS @@ -1395,6 +1374,10 @@ pub fn path_open( fd: WasmPtr<__wasi_fd_t>, ) -> __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); /* TODO: find actual upper bound on name size (also this is a path, not a name :think-fish:) */ if path_len > 1024 * 1024 { @@ -1402,237 +1385,157 @@ pub fn path_open( } let fd_cell = wasi_try!(fd.deref(memory)); - let path_cells = wasi_try!(path.deref(memory, 0, path_len)); let state = get_wasi_state(ctx); // o_flags: - // - __WASI_O_FLAG_CREAT (create if it does not exist) + // - __WASI_O_CREAT (create if it does not exist) // - __WASI_O_DIRECTORY (fail if not dir) // - __WASI_O_EXCL (fail if file exists) // - __WASI_O_TRUNC (truncate size to 0) - let working_dir = wasi_try!(state.fs.fd_map.get(&dirfd).ok_or(__WASI_EBADF)); + let working_dir = wasi_try!(state.fs.get_fd(dirfd)).clone(); // ASSUMPTION: open rights apply recursively if !has_rights(working_dir.rights, __WASI_RIGHT_PATH_OPEN) { return __WASI_EACCES; } - let path_string = - wasi_try!( - std::str::from_utf8(unsafe { &*(path_cells as *const [_] as *const [u8]) }) - .map_err(|_| __WASI_EINVAL) - ); - let path = std::path::PathBuf::from(path_string); - let path_vec = wasi_try!(path - .components() - .map(|comp| { - comp.as_os_str() - .to_str() - .map(|inner_str| inner_str.to_string()) - .ok_or(__WASI_EINVAL) - }) - .collect::, __wasi_errno_t>>()); - debug!("Path vec: {:#?}", path_vec); - - if path_vec.is_empty() { - return __WASI_EINVAL; - } - - let mut cur_dir_inode = working_dir.inode; - let mut cumulative_path = std::path::PathBuf::from(wasi_try!(state - .fs - .get_base_path_for_directory(working_dir.inode) - .ok_or(__WASI_EIO))); - - // traverse path - if path_vec.len() > 1 { - for path_segment in &path_vec[..(path_vec.len() - 1)] { - match &state.fs.inodes[cur_dir_inode].kind { - Kind::Dir { entries, .. } => { - if let Some(child) = entries.get(path_segment) { - cumulative_path.push(path_segment); - let inode_val = *child; - cur_dir_inode = inode_val; - } else { - // attempt to lazily load or create - if path_segment == ".." { - unimplemented!( - "\"..\" in paths in `path_open` has not been implemented yet" - ); - } - // lazily load - cumulative_path.push(path_segment); - let mut open_options = std::fs::OpenOptions::new(); - let open_options = open_options.read(true); - // ASSUMPTION: __WASI_O_CREAT applies recursively - let open_options = if o_flags & __WASI_O_CREAT != 0 { - open_options.create(true) - } else { - open_options - }; - // TODO: handle __WASI_O_TRUNC on directories - - // TODO: refactor and reuse - let cur_file_metadata = - wasi_try!(cumulative_path.metadata().map_err(|_| __WASI_EINVAL)); - let kind = if cur_file_metadata.is_dir() { - Kind::Dir { - parent: Some(cur_dir_inode), - path: cumulative_path.clone(), - entries: Default::default(), - } - } else { - return __WASI_ENOTDIR; - }; - let inode_val = InodeVal::from_file_metadata( - &cur_file_metadata, - path_segment.clone(), - false, - kind, - ); - - let new_inode = state.fs.inodes.insert(inode_val); - let inode_idx = state.fs.inode_counter.get(); - state.fs.inode_counter.replace(inode_idx + 1); - // reborrow to insert entry - if let Kind::Dir { entries, .. } = &mut state.fs.inodes[cur_dir_inode].kind - { - assert!(entries.insert(path_segment.clone(), new_inode).is_none()); - state.fs.inodes[new_inode].stat.st_ino = state.fs.inode_counter.get(); - cur_dir_inode = new_inode; - } - } - } - Kind::Symlink { .. } => unimplemented!("Symlinks not yet supported in `path_open`"), - _ => return __WASI_ENOTDIR, - } - } - } + let path_string = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); - let file_name = path_vec.last().unwrap(); + debug!("=> fd: {}, path: {}", dirfd, &path_string); - debug!( - "Looking for file {} in directory {:#?}", - file_name, cumulative_path + let path_arg = std::path::PathBuf::from(path_string); + let maybe_inode = state.fs.get_inode_at_path( + dirfd, + path_string, + dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, ); - cumulative_path.push(file_name); - let file_path = cumulative_path; - - let out_fd = if let Kind::Dir { - entries, parent, .. - } = &mut state.fs.inodes[cur_dir_inode].kind - { - // short circuit logic if attempting to get parent - if file_name == ".." { - if let Some(p) = parent { - let parent_inode = *p; - wasi_try!(state.fs.create_fd( - fs_rights_base, - fs_rights_inheriting, - fs_flags, - parent_inode - )) - } else { - return __WASI_EACCES; - } - } else { - if let Some(child) = entries.get(file_name).cloned() { - let child_inode_val = &state.fs.inodes[child]; - // early return based on flags - if o_flags & __WASI_O_EXCL != 0 { - return __WASI_EEXIST; - } + // TODO: traverse rights of dirs properly + 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 { + Kind::File { + ref mut handle, + path, + } => { if o_flags & __WASI_O_DIRECTORY != 0 { - match &child_inode_val.kind { - Kind::Dir { .. } => (), - Kind::Symlink { .. } => { - unimplemented!("Symlinks not yet supported in path_open") - } - _ => return __WASI_ENOTDIR, - } + return __WASI_ENOTDIR; } - // do logic on child - wasi_try!(state - .fs - .create_fd(fs_rights_base, fs_rights_inheriting, fs_flags, child)) - } else { - debug!("Attempting to load file from host system"); - - let file_metadata = file_path.metadata(); - // if entry does not exist in parent directory, try to lazily - // load it; possibly creating or truncating it if flags set - let kind = if file_metadata.is_ok() && file_metadata.unwrap().is_dir() { - // special dir logic - Kind::Dir { - parent: Some(cur_dir_inode), - path: file_path.clone(), - entries: Default::default(), + if o_flags & __WASI_O_EXCL != 0 { + if path.exists() { + return __WASI_EEXIST; } - } else { - // file is not a dir - let real_opened_file = { - let mut open_options = std::fs::OpenOptions::new(); - let open_options = open_options.read(true); - let open_options = if fs_rights_base & __WASI_RIGHT_FD_WRITE != 0 { - open_options.write(true) - } else { - open_options - }; - let open_options = if o_flags & __WASI_O_CREAT != 0 { - debug!( - "File {:?} may be created when opened if it does not exist", - &file_path - ); - open_options.create(true) - } else { - open_options - }; - let open_options = if o_flags & __WASI_O_TRUNC != 0 { - debug!("File {:?} will be truncated when opened", &file_path); - open_options.truncate(true) - } else { - open_options - }; - debug!("Opening host file {:?}", &file_path); - let real_open_file = - wasi_try!(open_options.open(&file_path).map_err(|_| __WASI_EIO)); - - real_open_file - }; - Kind::File { - handle: WasiFile::HostFile(real_opened_file), + } + let mut open_options = std::fs::OpenOptions::new(); + let open_options = open_options + .read(true) + // TODO: ensure these rights are actually valid given parent, etc. + .write(adjusted_rights & __WASI_RIGHT_FD_WRITE != 0) + .create(o_flags & __WASI_O_CREAT != 0) + .truncate(o_flags & __WASI_O_TRUNC != 0); + + *handle = Some(WasiFile::HostFile(wasi_try!(open_options + .open(&path) + .map_err(|_| __WASI_EIO)))); + } + Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), + Kind::Dir { .. } | Kind::Root { .. } => { + // TODO: adjust these to be correct + if o_flags & __WASI_O_EXCL != 0 { + if path_arg.exists() { + return __WASI_EEXIST; } - }; - - // record lazily loaded or newly created fd - let new_inode = state.fs.inodes.insert(InodeVal { - stat: wasi_try!(get_stat_for_kind(&kind).ok_or(__WASI_EIO)), - is_preopened: false, - name: file_name.clone(), - kind, - }); - - // reborrow to insert entry - if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind { - entries.insert(file_name.clone(), new_inode); } - let new_fd = wasi_try!(state.fs.create_fd( - fs_rights_base, - fs_rights_inheriting, - fs_flags, - new_inode, - )); - - new_fd + } + Kind::Symlink { + base_po_dir, + path_to_symlink, + relative_path, + } => { + // I think this should return an error + // TODO: investigate this + unimplemented!("SYMLINKS IN PATH_OPEN"); } } + inode } else { - // working_dir did not match on Kind::Dir - return __WASI_ENOTDIR; + // less-happy path, we have to try to create the file + debug!("Maybe creating file"); + if o_flags & __WASI_O_CREAT != 0 { + if o_flags & __WASI_O_DIRECTORY != 0 { + return __WASI_ENOTDIR; + } + debug!("Creating file"); + // strip end file name + + let (parent_inode, new_entity_name) = wasi_try!(state.fs.get_parent_inode_at_path( + dirfd, + &path_arg, + dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 + )); + let new_file_host_path = match &state.fs.inodes[parent_inode].kind { + Kind::Dir { path, .. } => { + let mut new_path = path.clone(); + new_path.push(&new_entity_name); + new_path + } + Kind::Root { .. } => return __WASI_EACCES, + _ => return __WASI_EINVAL, + }; + // once we got the data we need from the parent, we lookup the host file + // todo: extra check that opening with write access is okay + let handle = { + let mut open_options = std::fs::OpenOptions::new(); + let open_options = open_options + .read(true) + // TODO: ensure these rights are actually valid given parent, etc. + // write access is required for creating a file + .write(true) + .create_new(true); + + Some(WasiFile::HostFile(wasi_try!(open_options + .open(&new_file_host_path) + .map_err(|e| { + debug!("Error opening file {}", e); + __WASI_EIO + })))) + }; + + let new_inode = { + let kind = Kind::File { + handle, + path: new_file_host_path, + }; + wasi_try!(state.fs.create_inode(kind, false, new_entity_name.clone())) + }; + + if let Kind::Dir { + ref mut entries, .. + } = &mut state.fs.inodes[parent_inode].kind + { + entries.insert(new_entity_name, new_inode); + } + + new_inode + } else { + return maybe_inode.unwrap_err(); + } }; + debug!( + "inode {:?} value {:#?} found!", + inode, state.fs.inodes[inode] + ); + + // TODO: check and reduce these + // TODO: ensure a mutable fd to root can never be opened + let out_fd = + wasi_try!(state + .fs + .create_fd(adjusted_rights, fs_rights_inheriting, fs_flags, inode)); + fd_cell.set(out_fd); __WASI_ESUCCESS @@ -1648,18 +1551,105 @@ pub fn path_readlink( buf_used: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::path_readlink"); - unimplemented!("wasi::path_readlink") + let state = get_wasi_state(ctx); + let memory = ctx.memory(0); + + let base_dir = wasi_try!(state.fs.fd_map.get(&dir_fd).ok_or(__WASI_EBADF)); + if !has_rights(base_dir.rights, __WASI_RIGHT_PATH_READLINK) { + return __WASI_EACCES; + } + let path_str = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + let inode = wasi_try!(state.fs.get_inode_at_path(dir_fd, path_str, false)); + + if let Kind::Symlink { relative_path, .. } = &state.fs.inodes[inode].kind { + let rel_path_str = relative_path.to_string_lossy(); + let bytes = rel_path_str.bytes(); + if bytes.len() >= buf_len as usize { + return __WASI_EOVERFLOW; + } + + let out = wasi_try!(buf.deref(memory, 0, buf_len)); + let mut bytes_written = 0; + for b in bytes { + out[bytes_written].set(b); + bytes_written += 1; + } + // should we null terminate this? + + let bytes_out = wasi_try!(buf_used.deref(memory)); + bytes_out.set(bytes_written as u32); + } else { + return __WASI_EINVAL; + } + + __WASI_ESUCCESS } +/// Returns __WASI_ENOTEMTPY if directory is not empty pub fn path_remove_directory( ctx: &mut Ctx, fd: __wasi_fd_t, path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { + // TODO check if fd is a dir, ensure it's within sandbox, etc. debug!("wasi::path_remove_directory"); - unimplemented!("wasi::path_remove_directory") + let state = get_wasi_state(ctx); + let memory = ctx.memory(0); + + let base_dir = wasi_try!(state.fs.fd_map.get(&fd), __WASI_EBADF); + let path_str = wasi_try!(path.get_utf8_string(memory, path_len), __WASI_EINVAL); + + let inode = wasi_try!(state.fs.get_inode_at_path(fd, path_str, false)); + let (parent_inode, childs_name) = + wasi_try!(state + .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::Dir { entries, path, .. } => { + if !entries.is_empty() { + return __WASI_ENOTEMPTY; + } else { + if wasi_try!(std::fs::read_dir(path).ok(), __WASI_EIO).count() != 0 { + return __WASI_ENOTEMPTY; + } + } + path.clone() + } + Kind::Root { .. } => return __WASI_EACCES, + _ => return __WASI_ENOTDIR, + }; + + 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); + } + Kind::Root { .. } => return __WASI_EACCES, + _ => unreachable!( + "Internal logic error in wasi::path_remove_directory, parent is not a directory" + ), + } + + if let Err(_) = std::fs::remove_dir(path_str) { + // reinsert to prevent FS from being in bad state + if let Kind::Dir { + ref mut entries, .. + } = &mut state.fs.inodes[parent_inode].kind + { + entries.insert(childs_name, inode); + } + // TODO: more intelligently return error value by inspecting returned error value + return __WASI_EIO; + } + + __WASI_ESUCCESS } + pub fn path_rename( ctx: &mut Ctx, old_fd: __wasi_fd_t, @@ -1683,15 +1673,57 @@ pub fn path_symlink( debug!("wasi::path_symlink"); unimplemented!("wasi::path_symlink") } + pub fn path_unlink_file( ctx: &mut Ctx, fd: __wasi_fd_t, path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { + // TODO check if fd is a dir, ensure it's within sandbox, etc. debug!("wasi::path_unlink_file"); - unimplemented!("wasi::path_unlink_file") + let state = get_wasi_state(ctx); + let memory = ctx.memory(0); + + let base_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let path_str = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + + let inode = wasi_try!(state.fs.get_inode_at_path(fd, path_str, false)); + let (parent_inode, childs_name) = + wasi_try!(state + .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 { + 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); + } + Kind::Root { .. } => return __WASI_EACCES, + _ => unreachable!( + "Internal logic error in wasi::path_unlink_file, parent is not a directory" + ), + } + 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 } + pub fn poll_oneoff( ctx: &mut Ctx, in_: WasmPtr<__wasi_subscription_t, Array>, diff --git a/lib/wasi/src/syscalls/windows.rs b/lib/wasi/src/syscalls/windows.rs index f6559290bbc..6c0cff71bcb 100644 --- a/lib/wasi/src/syscalls/windows.rs +++ b/lib/wasi/src/syscalls/windows.rs @@ -1,5 +1,6 @@ use crate::syscalls::types::*; use std::cell::Cell; +use wasmer_runtime_core::debug; pub fn platform_clock_res_get( clock_id: __wasi_clockid_t,