From 61f1df06d1358ad86cd7625762ff92ff9f2766d0 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 9 Aug 2019 15:40:04 +0900 Subject: [PATCH] implement wasi::path_rename --- CHANGELOG.md | 1 + Cargo.lock | 1 + Makefile | 2 +- lib/wasi-tests/tests/wasitests/mod.rs | 1 + lib/wasi-tests/tests/wasitests/path_rename.rs | 14 ++ lib/wasi-tests/wasitests/path_rename.out | 2 + lib/wasi-tests/wasitests/path_rename.rs | 56 +++++ lib/wasi-tests/wasitests/path_rename.wasm | Bin 0 -> 86190 bytes lib/wasi/src/macros.rs | 6 + lib/wasi/src/state/mod.rs | 19 +- lib/wasi/src/state/types.rs | 11 + lib/wasi/src/syscalls/mod.rs | 200 ++++++++++++++---- 12 files changed, 273 insertions(+), 40 deletions(-) create mode 100644 lib/wasi-tests/tests/wasitests/path_rename.rs create mode 100644 lib/wasi-tests/wasitests/path_rename.out create mode 100644 lib/wasi-tests/wasitests/path_rename.rs create mode 100755 lib/wasi-tests/wasitests/path_rename.wasm diff --git a/CHANGELOG.md b/CHANGELOG.md index d93da9b6836..62aa45ac7ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Blocks of changes will separated by version increments. Special thanks to @YaronWittenstein @penberg for their contributions. +- [#650](https://github.com/wasmerio/wasmer/issues/650) Implement `wasi::path_rename`, improve WASI FS public api, and allow open files to exist even when the underlying file is deleted - [#643](https://github.com/wasmerio/wasmer/issues/643) Implement `wasi::path_symlink` and improve WASI FS public api IO error reporting - [#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` diff --git a/Cargo.lock b/Cargo.lock index 049ee21d612..0c81bdc3cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1516,6 +1516,7 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.6.0", "wasmparser 0.35.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Makefile b/Makefile index f6a1a4f92c8..a12d14fe591 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ generate-spectests: generate-emtests: WASM_EMSCRIPTEN_GENERATE_EMTESTS=1 cargo build -p wasmer-emscripten-tests --release -generate-wasitests: +generate-wasitests: wasitests-setup WASM_WASI_GENERATE_WASITESTS=1 cargo build -p wasmer-wasi-tests --release -vv \ && echo "formatting" \ && cargo fmt diff --git a/lib/wasi-tests/tests/wasitests/mod.rs b/lib/wasi-tests/tests/wasitests/mod.rs index c9309aea072..954d80bb4c3 100644 --- a/lib/wasi-tests/tests/wasitests/mod.rs +++ b/lib/wasi-tests/tests/wasitests/mod.rs @@ -16,6 +16,7 @@ mod fseek; mod hello; mod mapdir; mod path_link; +mod path_rename; mod path_symlink; mod quine; mod readlink; diff --git a/lib/wasi-tests/tests/wasitests/path_rename.rs b/lib/wasi-tests/tests/wasitests/path_rename.rs new file mode 100644 index 00000000000..9c1d3d7a08d --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/path_rename.rs @@ -0,0 +1,14 @@ +#[test] +fn test_path_rename() { + assert_wasi_output!( + "../../wasitests/path_rename.wasm", + "path_rename", + vec![], + vec![( + "temp".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/temp") + ),], + vec![], + "../../wasitests/path_rename.out" + ); +} diff --git a/lib/wasi-tests/wasitests/path_rename.out b/lib/wasi-tests/wasitests/path_rename.out new file mode 100644 index 00000000000..7b44f57637a --- /dev/null +++ b/lib/wasi-tests/wasitests/path_rename.out @@ -0,0 +1,2 @@ +The original file does not still exist! +柴犬 diff --git a/lib/wasi-tests/wasitests/path_rename.rs b/lib/wasi-tests/wasitests/path_rename.rs new file mode 100644 index 00000000000..f60eff106c8 --- /dev/null +++ b/lib/wasi-tests/wasitests/path_rename.rs @@ -0,0 +1,56 @@ +// Args: +// mapdir: temp:wasitests/test_fs/temp + +use std::fs; +use std::io::{Read, Write}; +use std::path::PathBuf; + +fn main() { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("/"); + + let file_to_create = base.join("temp/path_rename_file.txt"); + let file_to_rename_to = base.join("temp/path_renamed_file.txt"); + + { + let mut f = std::fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&file_to_create) + .unwrap(); + + // text from https://ja.wikipedia.org/wiki/柴犬 + let shiba_string = "「柴犬」という名前は中央高地で使われていたもので、文献上では、昭和初期の日本犬保存会の会誌「日本犬」で用いられている。一般的には、「柴」は小ぶりな雑木を指す。 +由来には諸説があり、 + + 柴藪を巧みにくぐり抜けて猟を助けることから + 赤褐色の毛色が枯れ柴に似ている(柴赤)ことから + 小さなものを表す古語の「柴」から + +の3つの説が代表的。"; + let shiba_bytes: Vec = shiba_string.bytes().collect(); + f.write_all(&shiba_bytes[..]).unwrap(); + } + + std::fs::rename(&file_to_create, &file_to_rename_to).unwrap(); + let mut file = fs::File::open(&file_to_rename_to).expect("Could not open file"); + if file_to_create.exists() { + println!("The original file still exists!"); + return; + } else { + println!("The original file does not still exist!"); + } + + let mut out_str = String::new(); + file.read_to_string(&mut out_str).unwrap(); + let mut test_str = String::new(); + let mut out_chars = out_str.chars(); + out_chars.next().unwrap(); + test_str.push(out_chars.next().unwrap()); + test_str.push(out_chars.next().unwrap()); + + println!("{}", test_str); + std::fs::remove_file(file_to_rename_to).unwrap(); +} diff --git a/lib/wasi-tests/wasitests/path_rename.wasm b/lib/wasi-tests/wasitests/path_rename.wasm new file mode 100755 index 0000000000000000000000000000000000000000..40197b5319b92eeafa99245df5231bd07bf9da56 GIT binary patch literal 86190 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 literal 0 HcmV?d00001 diff --git a/lib/wasi/src/macros.rs b/lib/wasi/src/macros.rs index 819a4c91b89..e0e144a626f 100644 --- a/lib/wasi/src/macros.rs +++ b/lib/wasi/src/macros.rs @@ -17,3 +17,9 @@ macro_rules! wasi_try { wasi_try!(opt.ok_or($e)) }}; } + +macro_rules! get_input_str { + ($memory:expr, $data:expr, $len:expr) => {{ + wasi_try!($data.get_utf8_string($memory, $len), __WASI_EINVAL) + }}; +} diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index fb1c4f63ac8..dfcf5274109 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -106,7 +106,7 @@ pub enum Kind { }, } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Fd { pub rights: __wasi_rights_t, pub rights_inheriting: __wasi_rights_t, @@ -126,6 +126,8 @@ pub struct WasiFs { pub fd_map: HashMap, pub next_fd: Cell, inode_counter: Cell, + /// for fds still open after the file has been deleted + pub orphan_fds: HashMap, pub stdout: Box, pub stderr: Box, @@ -146,6 +148,7 @@ impl WasiFs { fd_map: HashMap::new(), next_fd: Cell::new(3), inode_counter: Cell::new(1024), + orphan_fds: HashMap::new(), stdin: Box::new(Stdin(io::stdin())), stdout: Box::new(Stdout(io::stdout())), @@ -683,6 +686,16 @@ impl WasiFs { self.fd_map.get(&fd).ok_or(__WASI_EBADF) } + /// gets either a normal inode or an orphaned inode + pub fn get_inodeval_mut(&mut self, fd: __wasi_fd_t) -> Result<&mut InodeVal, __wasi_errno_t> { + let inode = self.get_fd(fd)?.inode; + if let Some(iv) = self.inodes.get_mut(inode) { + Ok(iv) + } else { + self.orphan_fds.get_mut(&inode).ok_or(__WASI_EBADF) + } + } + pub fn filestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_filestat_t, __wasi_errno_t> { let fd = self.get_fd(fd)?; @@ -818,8 +831,8 @@ impl WasiFs { /// 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() + pub unsafe fn remove_inode(&mut self, inode: Inode) -> Option { + self.inodes.remove(inode) } fn create_virtual_root(&mut self) -> Inode { diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 57ee29fdd6a..64f7d079392 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -158,6 +158,13 @@ pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { fn sync_to_disk(&self) -> Result<(), WasiFsError> { 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"); } + + /// Moves the file to a new location + /// NOTE: the signature of this function will change before stabilization + // TODO: stablizie this in 0.7.0 or 0.8.0 by removing default impl + fn rename_file(&self, _new_name: &std::path::Path) -> Result<(), WasiFsError> { + panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0 or 0.8.0. Please implement WasiFile::rename_file for your type before then"); + } } pub trait WasiPath {} @@ -271,6 +278,10 @@ impl WasiFile for HostFile { fn sync_to_disk(&self) -> Result<(), WasiFsError> { self.inner.sync_all().map_err(Into::into) } + + fn rename_file(&self, new_name: &std::path::Path) -> Result<(), WasiFsError> { + std::fs::rename(&self.host_path, new_name).map_err(Into::into) + } } impl From for WasiFsError { diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 0000cb6d6b3..8e403537a80 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -364,15 +364,14 @@ pub fn fd_allocate( /// - `__WASI_EISDIR` /// If `fd` is a directory /// - `__WASI_EBADF` -/// If `fd` is invalid or not open (TODO: consider __WASI_EINVAL) +/// If `fd` is invalid or not open pub fn fd_close(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_close"); 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 = wasi_try!(state.fs.get_inodeval_mut(fd)); - let inode_val = &mut state.fs.inodes[fd_entry.inode]; if inode_val.is_preopened { return __WASI_EACCES; } @@ -1303,7 +1302,7 @@ pub fn path_create_directory( if !has_rights(working_dir.rights, __WASI_RIGHT_PATH_CREATE_DIRECTORY) { return __WASI_EACCES; } - let path_string = wasi_try!(path.get_utf8_string(memory, path_len), __WASI_EINVAL); + let path_string = get_input_str!(memory, path, path_len); debug!("=> fd: {}, path: {}", fd, &path_string); let path = std::path::PathBuf::from(path_string); @@ -1407,8 +1406,7 @@ pub fn path_filestat_get( if !has_rights(root_dir.rights, __WASI_RIGHT_PATH_FILESTAT_GET) { return __WASI_EACCES; } - - let path_string = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + let path_string = get_input_str!(memory, path, path_len); debug!("=> base_fd: {}, path: {}", fd, &path_string); @@ -1459,6 +1457,7 @@ pub fn 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(); + let fd_inode = fd_entry.inode; if !has_rights(fd_entry.rights, __WASI_RIGHT_PATH_FILESTAT_SET_TIMES) { return __WASI_EACCES; } @@ -1469,7 +1468,7 @@ pub fn path_filestat_set_times( return __WASI_EINVAL; } - let path_string = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + let path_string = get_input_str!(memory, path, path_len); debug!("=> base_fd: {}, path: {}", fd, &path_string); let file_inode = wasi_try!(state.fs.get_inode_at_path( @@ -1482,7 +1481,7 @@ pub fn path_filestat_set_times( .get_stat_for_kind(&state.fs.inodes[file_inode].kind) .ok_or(__WASI_EIO)); - let inode = &mut state.fs.inodes[fd_entry.inode]; + let inode = &mut state.fs.inodes[fd_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 { @@ -1555,15 +1554,8 @@ pub fn path_link( } 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 old_path_str = get_input_str!(memory, old_path, old_path_len); + let new_path_str = get_input_str!(memory, new_path, new_path_len); let source_fd = wasi_try!(state.fs.get_fd(old_fd)); let target_fd = wasi_try!(state.fs.get_fd(new_fd)); debug!( @@ -1661,14 +1653,14 @@ pub fn path_open( // - __WASI_O_EXCL (fail if file exists) // - __WASI_O_TRUNC (truncate size to 0) - let working_dir = wasi_try!(state.fs.get_fd(dirfd)).clone(); + let working_dir = wasi_try!(state.fs.get_fd(dirfd)); + let working_dir_rights_inheriting = working_dir.rights_inheriting; // ASSUMPTION: open rights apply recursively if !has_rights(working_dir.rights, __WASI_RIGHT_PATH_OPEN) { return __WASI_EACCES; } - - let path_string = wasi_try!(path.get_utf8_string(memory, path_len).ok_or(__WASI_EINVAL)); + let path_string = get_input_str!(memory, path, path_len); debug!("=> fd: {}, path: {}", dirfd, &path_string); @@ -1680,13 +1672,13 @@ pub fn path_open( ); if let Ok(m) = maybe_inode { - dbg!(&state.fs.inodes[m]); + &state.fs.inodes[m]; } // TODO: traverse rights of dirs properly // 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 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 { @@ -1817,6 +1809,22 @@ pub fn path_open( __WASI_ESUCCESS } +/// ### `path_readlink()` +/// Read the value of a symlink +/// Inputs: +/// - `__wasi_fd_t dir_fd` +/// The base directory from which `path` is understood +/// - `const char *path` +/// Pointer to UTF-8 bytes that make up the path to the symlink +/// - `u32 path_len` +/// The number of bytes to read from `path` +/// - `u32 buf_len` +/// Space available pointed to by `buf` +/// Outputs: +/// - `char *buf` +/// Pointer to characters containing the path that the symlink points to +/// - `u32 buf_used` +/// The number of bytes written to `buf` pub fn path_readlink( ctx: &mut Ctx, dir_fd: __wasi_fd_t, @@ -1834,7 +1842,7 @@ pub fn path_readlink( 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 path_str = get_input_str!(memory, path, path_len); 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 { @@ -1875,7 +1883,7 @@ pub fn path_remove_directory( 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 path_str = get_input_str!(memory, path, path_len); let inode = wasi_try!(state.fs.get_inode_at_path(fd, path_str, false)); let (parent_inode, childs_name) = @@ -1927,6 +1935,21 @@ pub fn path_remove_directory( __WASI_ESUCCESS } +/// ### `path_rename()` +/// Rename a file or directory +/// Inputs: +/// - `__wasi_fd_t old_fd` +/// The base directory for `old_path` +/// - `const char* old_path` +/// Pointer to UTF8 bytes, the file to be renamed +/// - `u32 old_path_len` +/// The number of bytes to read from `old_path` +/// - `__wasi_fd_t new_fd` +/// The base directory for `new_path` +/// - `const char* new_path` +/// Pointer to UTF8 bytes, the new file name +/// - `u32 new_path_len` +/// The number of bytes to read from `new_path` pub fn path_rename( ctx: &mut Ctx, old_fd: __wasi_fd_t, @@ -1937,7 +1960,90 @@ pub fn path_rename( new_path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_rename"); - unimplemented!("wasi::path_rename") + let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + let source_str = get_input_str!(memory, old_path, old_path_len); + let source_path = std::path::Path::new(source_str); + let target_str = get_input_str!(memory, new_path, new_path_len); + let target_path = std::path::Path::new(target_str); + + { + let source_fd = wasi_try!(state.fs.get_fd(old_fd)); + if !has_rights(source_fd.rights, __WASI_RIGHT_PATH_RENAME_SOURCE) { + return __WASI_EACCES; + } + let target_fd = wasi_try!(state.fs.get_fd(new_fd)); + if !has_rights(target_fd.rights, __WASI_RIGHT_PATH_RENAME_TARGET) { + return __WASI_EACCES; + } + } + + let (source_parent_inode, source_entry_name) = + wasi_try!(state.fs.get_parent_inode_at_path(old_fd, source_path, true)); + let (target_parent_inode, target_entry_name) = + wasi_try!(state.fs.get_parent_inode_at_path(new_fd, target_path, true)); + + let host_adjusted_target_path = match &state.fs.inodes[target_parent_inode].kind { + Kind::Dir { entries, path, .. } => { + if entries.contains_key(&target_entry_name) { + return __WASI_EEXIST; + } + let mut out_path = path.clone(); + // remove fd's own name which will be double counted + out_path.pop(); + out_path.push(target_path); + out_path + } + Kind::Root { .. } => return __WASI_ENOTCAPABLE, + Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => { + unreachable!("Fatal internal logic error: parent of inode is not a directory") + } + }; + let source_entry = match &mut state.fs.inodes[source_parent_inode].kind { + Kind::Dir { entries, .. } => wasi_try!(entries.remove(&source_entry_name), __WASI_EINVAL), + Kind::Root { .. } => return __WASI_ENOTCAPABLE, + Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => { + unreachable!("Fatal internal logic error: parent of inode is not a directory") + } + }; + + match &mut state.fs.inodes[source_entry].kind { + Kind::File { + handle, + ref mut path, + } => { + let result = if let Some(h) = handle { + h.rename_file(&host_adjusted_target_path) + .map_err(|e| e.into_wasi_err()) + } else { + let out = + std::fs::rename(&path, &host_adjusted_target_path).map_err(|_| __WASI_EIO); + *path = host_adjusted_target_path; + out + }; + // if the above operation failed we have to revert the previous change and then fail + if let Err(e) = result { + if let Kind::Dir { entries, .. } = &mut state.fs.inodes[source_parent_inode].kind { + entries.insert(source_entry_name, source_entry); + return e; + } + } + } + Kind::Dir { path, .. } => unimplemented!("wasi::path_rename on Directories"), + Kind::Buffer { .. } => {} + Kind::Symlink { .. } => {} + Kind::Root { .. } => unreachable!("The root can not be moved"), + } + + if let Kind::Dir { entries, .. } = &mut state.fs.inodes[target_parent_inode].kind { + let result = entries.insert(target_entry_name, source_entry); + assert!( + result.is_none(), + "Fatal error: race condition on filesystem detected or internal logic error" + ); + } + + __WASI_ESUCCESS } /// ### `path_symlink()` @@ -1964,14 +2070,8 @@ pub fn path_symlink( debug!("wasi::path_symlink"); let state = get_wasi_state(ctx); let memory = ctx.memory(0); - 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 old_path_str = get_input_str!(memory, old_path, old_path_len); + let new_path_str = get_input_str!(memory, new_path, new_path_len); let base_fd = wasi_try!(state.fs.get_fd(fd)); if !has_rights(base_fd.rights, __WASI_RIGHT_PATH_SYMLINK) { return __WASI_EACCES; @@ -2053,7 +2153,7 @@ pub fn path_unlink_file( 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)); + let path_str = get_input_str!(memory, path, path_len); debug!("Requested file: {}", path_str); let inode = wasi_try!(state.fs.get_inode_at_path(fd, path_str, false)); @@ -2097,16 +2197,43 @@ pub fn path_unlink_file( } _ => unimplemented!("wasi::path_unlink_file for Buffer"), } - let inode_was_removed = unsafe { state.fs.remove_inode(removed_inode) }; + // TODO: test this on Windows and actually make it portable + // make the file an orphan fd if the fd is still open + let fd_is_orphaned = if let Kind::File { handle, .. } = &state.fs.inodes[removed_inode].kind + { + handle.is_some() + } else { + false + }; + let removed_inode_val = unsafe { state.fs.remove_inode(removed_inode) }; assert!( - inode_was_removed, + removed_inode_val.is_some(), "Inode could not be removed because it doesn't exist" ); + + if fd_is_orphaned { + state + .fs + .orphan_fds + .insert(removed_inode, removed_inode_val.unwrap()); + } } __WASI_ESUCCESS } +/// ### `poll_oneoff()` +/// Concurrently poll for a set of events +/// Inputs: +/// - `const __wasi_subscription_t *in` +/// The events to subscribe to +/// - `__wasi_event_t *out` +/// The events that have occured +/// - `u32 nsubscriptions` +/// The number of subscriptions and the number of events +/// Output: +/// - `u32 nevents` +/// The number of events seen pub fn poll_oneoff( ctx: &mut Ctx, in_: WasmPtr<__wasi_subscription_t, Array>, @@ -2117,6 +2244,7 @@ pub fn poll_oneoff( debug!("wasi::poll_oneoff"); unimplemented!("wasi::poll_oneoff") } + pub fn proc_exit(ctx: &mut Ctx, code: __wasi_exitcode_t) -> Result { debug!("wasi::proc_exit, {}", code); Err(ExitCode { code })