diff --git a/crates/test-programs/reactor-tests/src/lib.rs b/crates/test-programs/reactor-tests/src/lib.rs index ff1c0cab78ff..532dda77f4e1 100644 --- a/crates/test-programs/reactor-tests/src/lib.rs +++ b/crates/test-programs/reactor-tests/src/lib.rs @@ -31,7 +31,7 @@ impl TestReactor for T { Ok(()) } } - fn pass_an_imported_record(stat: wasi::filesystem::filesystem::DescriptorStat) -> String { + fn pass_an_imported_record(stat: wasi::filesystem::types::DescriptorStat) -> String { format!("{stat:?}") } } diff --git a/crates/test-programs/tests/reactor.rs b/crates/test-programs/tests/reactor.rs index 87e1a6925e0d..c9868fe5107b 100644 --- a/crates/test-programs/tests/reactor.rs +++ b/crates/test-programs/tests/reactor.rs @@ -4,7 +4,7 @@ use wasmtime::{ Config, Engine, Store, }; use wasmtime_wasi::preview2::bindings::clocks::wall_clock; -use wasmtime_wasi::preview2::bindings::filesystem::filesystem; +use wasmtime_wasi::preview2::bindings::filesystem::types as filesystem; use wasmtime_wasi::preview2::{self, Table, WasiCtx, WasiCtxBuilder, WasiView}; lazy_static::lazy_static! { @@ -28,9 +28,9 @@ wasmtime::component::bindgen!({ async: true, with: { "wasi:io/streams": preview2::bindings::io::streams, - "wasi:filesystem/filesystem": preview2::bindings::filesystem::filesystem, + "wasi:filesystem/types": preview2::bindings::filesystem::types, + "wasi:filesystem/preopens": preview2::bindings::filesystem::preopens, "wasi:cli-base/environment": preview2::bindings::cli_base::environment, - "wasi:cli-base/preopens": preview2::bindings::cli_base::preopens, "wasi:cli-base/exit": preview2::bindings::cli_base::exit, "wasi:cli-base/stdin": preview2::bindings::cli_base::stdin, "wasi:cli-base/stdout": preview2::bindings::cli_base::stdout, @@ -68,10 +68,10 @@ async fn instantiate( let mut linker = Linker::new(&ENGINE); // All of the imports available to the world are provided by the wasi-common crate: - preview2::bindings::filesystem::filesystem::add_to_linker(&mut linker, |x| x)?; + preview2::bindings::filesystem::types::add_to_linker(&mut linker, |x| x)?; + preview2::bindings::filesystem::preopens::add_to_linker(&mut linker, |x| x)?; preview2::bindings::io::streams::add_to_linker(&mut linker, |x| x)?; preview2::bindings::cli_base::environment::add_to_linker(&mut linker, |x| x)?; - preview2::bindings::cli_base::preopens::add_to_linker(&mut linker, |x| x)?; preview2::bindings::cli_base::exit::add_to_linker(&mut linker, |x| x)?; preview2::bindings::cli_base::stdin::add_to_linker(&mut linker, |x| x)?; preview2::bindings::cli_base::stdout::add_to_linker(&mut linker, |x| x)?; @@ -129,8 +129,6 @@ async fn reactor_tests() -> Result<()> { nanoseconds: 789, seconds: 10, }, - device: 0, - inode: 0, link_count: 0, size: 0, status_change_timestamp: wall_clock::Datetime { diff --git a/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs b/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs index 2f0f958c5aba..164c7a437355 100644 --- a/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs +++ b/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs @@ -167,7 +167,7 @@ async fn fd_flags_set() { } #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn fd_readdir() { - run("fd_readdir", false).await.unwrap() + run("fd_readdir", true).await.unwrap() } #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn file_allocate() { diff --git a/crates/test-programs/wasi-tests/src/bin/path_open_read_write.rs b/crates/test-programs/wasi-tests/src/bin/path_open_read_write.rs index a432f50a92b2..0278fbf6d4f1 100644 --- a/crates/test-programs/wasi-tests/src/bin/path_open_read_write.rs +++ b/crates/test-programs/wasi-tests/src/bin/path_open_read_write.rs @@ -38,7 +38,6 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) { ); wasi::fd_close(f_readonly).expect("close readonly"); - drop(f_readonly); // =============== WRITE ONLY ================== let f_writeonly = wasi::path_open(dir_fd, 0, "file", 0, wasi::RIGHTS_FD_WRITE, 0, 0) @@ -64,7 +63,6 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) { assert_eq!(bytes_written, write_buffer.len()); wasi::fd_close(f_writeonly).expect("close writeonly"); - drop(f_writeonly); // ============== READ WRITE ======================= @@ -111,7 +109,6 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) { ); wasi::fd_close(f_readwrite).expect("close readwrite"); - drop(f_readwrite); wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); } diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs index 2a0f7928b832..e8412eb01631 100644 --- a/crates/wasi-preview1-component-adapter/src/descriptors.rs +++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs @@ -1,5 +1,5 @@ use crate::bindings::wasi::cli_base::{stderr, stdin, stdout}; -use crate::bindings::wasi::filesystem::filesystem; +use crate::bindings::wasi::filesystem::types as filesystem; use crate::bindings::wasi::io::streams::{self, InputStream, OutputStream}; use crate::bindings::wasi::sockets::tcp; use crate::{set_stderr_stream, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr}; @@ -166,7 +166,7 @@ impl Descriptors { })) .trapping_unwrap(); - #[link(wasm_import_module = "wasi:cli-base/preopens")] + #[link(wasm_import_module = "wasi:filesystem/preopens")] extern "C" { #[link_name = "get-directories"] fn get_preopens_import(rval: *mut PreopenList); diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index 69aa91959724..78fac2a611e7 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -2,7 +2,7 @@ use crate::bindings::wasi::cli_base::exit; use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock}; -use crate::bindings::wasi::filesystem::filesystem; +use crate::bindings::wasi::filesystem::types as filesystem; use crate::bindings::wasi::io::streams; use crate::bindings::wasi::poll::poll; use crate::bindings::wasi::random::random; @@ -642,10 +642,11 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { .. }) => { let stat = filesystem::stat(file.fd)?; + let metadata_hash = filesystem::metadata_hash(file.fd)?; let filetype = stat.type_.into(); *buf = Filestat { - dev: stat.device, - ino: stat.inode, + dev: 1, + ino: metadata_hash.lower, filetype, nlink: stat.link_count, size: stat.size, @@ -960,8 +961,6 @@ pub unsafe extern "C" fn fd_readdir( // for it. let ds = state.descriptors(); let dir = ds.get_dir(fd)?; - let stat = filesystem::stat(dir.fd)?; - let dot_inode = stat.inode; let mut iter; match stream { @@ -975,7 +974,7 @@ pub unsafe extern "C" fn fd_readdir( state, cookie, use_cache: true, - dot_inode, + dir_descriptor: dir.fd, } } @@ -990,7 +989,7 @@ pub unsafe extern "C" fn fd_readdir( cookie: wasi::DIRCOOKIE_START, use_cache: false, stream: DirectoryEntryStream(filesystem::read_directory(dir.fd)?), - dot_inode, + dir_descriptor: dir.fd, }; // Skip to the entry that is requested by the `cookie` @@ -1069,7 +1068,7 @@ pub unsafe extern "C" fn fd_readdir( use_cache: bool, cookie: Dircookie, stream: DirectoryEntryStream, - dot_inode: wasi::Inode, + dir_descriptor: filesystem::Descriptor, } impl<'a> Iterator for DirectoryEntryIterator<'a> { @@ -1086,9 +1085,13 @@ pub unsafe extern "C" fn fd_readdir( // Preview2 excludes them, so re-add them. match current_cookie { 0 => { + let metadata_hash = match filesystem::metadata_hash(self.dir_descriptor) { + Ok(h) => h, + Err(e) => return Some(Err(e.into())), + }; let dirent = wasi::Dirent { d_next: self.cookie, - d_ino: self.dot_inode, + d_ino: metadata_hash.lower, d_type: wasi::FILETYPE_DIRECTORY, d_namlen: 1, }; @@ -1128,11 +1131,18 @@ pub unsafe extern "C" fn fd_readdir( Err(e) => return Some(Err(e.into())), }; - let filesystem::DirectoryEntry { inode, type_, name } = entry; + let filesystem::DirectoryEntry { type_, name } = entry; + let d_ino = filesystem::metadata_hash_at( + self.dir_descriptor, + filesystem::PathFlags::empty(), + &name, + ) + .map(|h| h.lower) + .unwrap_or(0); let name = ManuallyDrop::new(name); let dirent = wasi::Dirent { d_next: self.cookie, - d_ino: inode.unwrap_or(0), + d_ino, d_namlen: u32::try_from(name.len()).trapping_unwrap(), d_type: type_.into(), }; @@ -1331,10 +1341,11 @@ pub unsafe extern "C" fn path_filestat_get( let ds = state.descriptors(); let file = ds.get_dir(fd)?; let stat = filesystem::stat_at(file.fd, at_flags, path)?; + let metadata_hash = filesystem::metadata_hash_at(file.fd, at_flags, path)?; let filetype = stat.type_.into(); *buf = Filestat { - dev: stat.device, - ino: stat.inode, + dev: 1, + ino: metadata_hash.lower, filetype, nlink: stat.link_count, size: stat.size, diff --git a/crates/wasi/src/preview2/command.rs b/crates/wasi/src/preview2/command.rs index 4b49491ea063..e213f3e4bfcf 100644 --- a/crates/wasi/src/preview2/command.rs +++ b/crates/wasi/src/preview2/command.rs @@ -5,11 +5,12 @@ wasmtime::component::bindgen!({ tracing: true, async: true, trappable_error_type: { - "wasi:filesystem/filesystem"::"error-code": Error, + "wasi:filesystem/types"::"error-code": Error, "wasi:io/streams"::"stream-error": Error, }, with: { - "wasi:filesystem/filesystem": crate::preview2::bindings::filesystem::filesystem, + "wasi:filesystem/types": crate::preview2::bindings::filesystem::types, + "wasi:filesystem/preopens": crate::preview2::bindings::filesystem::preopens, "wasi:clocks/monotonic_clock": crate::preview2::bindings::clocks::monotonic_clock, "wasi:poll/poll": crate::preview2::bindings::poll::poll, "wasi:io/streams": crate::preview2::bindings::io::streams, @@ -18,7 +19,6 @@ wasmtime::component::bindgen!({ "wasi:random/random": crate::preview2::bindings::random::random, "wasi:cli_base/environment": crate::preview2::bindings::cli_base::environment, "wasi:cli_base/exit": crate::preview2::bindings::cli_base::exit, - "wasi:cli_base/preopens": crate::preview2::bindings::cli_base::preopens, "wasi:cli_base/stdin": crate::preview2::bindings::cli_base::stdin, "wasi:cli_base/stdout": crate::preview2::bindings::cli_base::stdout, "wasi:cli_base/stderr": crate::preview2::bindings::cli_base::stderr, @@ -29,13 +29,13 @@ pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> any crate::preview2::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; crate::preview2::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; crate::preview2::bindings::clocks::timezone::add_to_linker(l, |t| t)?; - crate::preview2::bindings::filesystem::filesystem::add_to_linker(l, |t| t)?; + crate::preview2::bindings::filesystem::types::add_to_linker(l, |t| t)?; + crate::preview2::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; crate::preview2::bindings::poll::poll::add_to_linker(l, |t| t)?; crate::preview2::bindings::io::streams::add_to_linker(l, |t| t)?; crate::preview2::bindings::random::random::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::exit::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::environment::add_to_linker(l, |t| t)?; - crate::preview2::bindings::cli_base::preopens::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::stdin::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::stdout::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::stderr::add_to_linker(l, |t| t)?; @@ -50,11 +50,12 @@ pub mod sync { tracing: true, async: false, trappable_error_type: { - "wasi:filesystem/filesystem"::"error-code": Error, + "wasi:filesystem/types"::"error-code": Error, "wasi:io/streams"::"stream-error": Error, }, with: { - "wasi:filesystem/filesystem": crate::preview2::bindings::sync_io::filesystem::filesystem, + "wasi:filesystem/types": crate::preview2::bindings::sync_io::filesystem::types, + "wasi:filesystem/preopens": crate::preview2::bindings::filesystem::preopens, "wasi:clocks/monotonic_clock": crate::preview2::bindings::clocks::monotonic_clock, "wasi:poll/poll": crate::preview2::bindings::sync_io::poll::poll, "wasi:io/streams": crate::preview2::bindings::sync_io::io::streams, @@ -63,7 +64,6 @@ pub mod sync { "wasi:random/random": crate::preview2::bindings::random::random, "wasi:cli_base/environment": crate::preview2::bindings::cli_base::environment, "wasi:cli_base/exit": crate::preview2::bindings::cli_base::exit, - "wasi:cli_base/preopens": crate::preview2::bindings::cli_base::preopens, "wasi:cli_base/stdin": crate::preview2::bindings::cli_base::stdin, "wasi:cli_base/stdout": crate::preview2::bindings::cli_base::stdout, "wasi:cli_base/stderr": crate::preview2::bindings::cli_base::stderr, @@ -76,13 +76,13 @@ pub mod sync { crate::preview2::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; crate::preview2::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; crate::preview2::bindings::clocks::timezone::add_to_linker(l, |t| t)?; - crate::preview2::bindings::sync_io::filesystem::filesystem::add_to_linker(l, |t| t)?; + crate::preview2::bindings::sync_io::filesystem::types::add_to_linker(l, |t| t)?; + crate::preview2::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; crate::preview2::bindings::sync_io::poll::poll::add_to_linker(l, |t| t)?; crate::preview2::bindings::sync_io::io::streams::add_to_linker(l, |t| t)?; crate::preview2::bindings::random::random::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::exit::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::environment::add_to_linker(l, |t| t)?; - crate::preview2::bindings::cli_base::preopens::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::stdin::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::stdout::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli_base::stderr::add_to_linker(l, |t| t)?; diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs index 16390462e26e..f336acbff060 100644 --- a/crates/wasi/src/preview2/mod.rs +++ b/crates/wasi/src/preview2/mod.rs @@ -49,12 +49,12 @@ pub mod bindings { interfaces: " import wasi:poll/poll import wasi:io/streams - import wasi:filesystem/filesystem + import wasi:filesystem/types ", tracing: true, trappable_error_type: { "wasi:io/streams"::"stream-error": Error, - "wasi:filesystem/filesystem"::"error-code": Error, + "wasi:filesystem/types"::"error-code": Error, }, with: { "wasi:clocks/wall-clock": crate::preview2::bindings::clocks::wall_clock, @@ -99,33 +99,30 @@ pub mod bindings { interfaces: " import wasi:poll/poll import wasi:io/streams - import wasi:filesystem/filesystem + import wasi:filesystem/types ", tracing: true, async: true, trappable_error_type: { "wasi:io/streams"::"stream-error": Error, - "wasi:filesystem/filesystem"::"error-code": Error, + "wasi:filesystem/types"::"error-code": Error, }, with: { "wasi:clocks/wall-clock": crate::preview2::bindings::clocks::wall_clock, } }); } - pub use self::_internal_io::wasi::{filesystem, io, poll}; + pub use self::_internal_io::wasi::{io, poll}; pub(crate) mod _internal_rest { wasmtime::component::bindgen!({ path: "wit", interfaces: " - import wasi:clocks/wall-clock - import wasi:clocks/monotonic-clock - import wasi:clocks/timezone + import wasi:filesystem/preopens import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed import wasi:cli-base/environment - import wasi:cli-base/preopens import wasi:cli-base/exit import wasi:cli-base/stdin import wasi:cli-base/stdout @@ -133,19 +130,23 @@ pub mod bindings { ", tracing: true, trappable_error_type: { - "wasi:filesystem/filesystem"::"error-code": Error, + "wasi:filesystem/types"::"error-code": Error, "wasi:io/streams"::"stream-error": Error, }, with: { "wasi:clocks/wall-clock": crate::preview2::bindings::clocks::wall_clock, "wasi:poll/poll": crate::preview2::bindings::poll::poll, "wasi:io/streams": crate::preview2::bindings::io::streams, - "wasi:filesystem/filesystem": crate::preview2::bindings::filesystem::filesystem + "wasi:filesystem/types": crate::preview2::bindings::filesystem::types, } }); } - pub use self::_internal_rest::wasi::*; + pub use self::_internal_rest::wasi::{cli_base, random}; + pub mod filesystem { + pub use super::_internal_io::wasi::filesystem::types; + pub use super::_internal_rest::wasi::filesystem::preopens; + } } pub(crate) static RUNTIME: once_cell::sync::Lazy = diff --git a/crates/wasi/src/preview2/preview1/mod.rs b/crates/wasi/src/preview2/preview1/mod.rs index eb52888a404a..8cd1e798ea9e 100644 --- a/crates/wasi/src/preview2/preview1/mod.rs +++ b/crates/wasi/src/preview2/preview1/mod.rs @@ -1,6 +1,6 @@ -use crate::preview2::bindings::cli_base::{preopens, stderr, stdin, stdout}; +use crate::preview2::bindings::cli_base::{stderr, stdin, stdout}; use crate::preview2::bindings::clocks::{monotonic_clock, wall_clock}; -use crate::preview2::bindings::filesystem::filesystem; +use crate::preview2::bindings::filesystem::{preopens, types as filesystem}; use crate::preview2::bindings::io::streams; use crate::preview2::filesystem::TableFsExt; use crate::preview2::preview2::filesystem::TableReaddirExt; @@ -36,9 +36,9 @@ struct File { #[derive(Clone, Debug)] enum Descriptor { - Stdin(preopens::InputStream), - Stdout(preopens::OutputStream), - Stderr(preopens::OutputStream), + Stdin(streams::InputStream), + Stdout(streams::OutputStream), + Stderr(streams::OutputStream), PreopenDirectory((filesystem::Descriptor, String)), File(File), } @@ -321,8 +321,8 @@ pub fn add_to_linker< T: WasiPreview1View + bindings::cli_base::environment::Host + bindings::cli_base::exit::Host - + bindings::cli_base::preopens::Host - + bindings::filesystem::filesystem::Host + + bindings::filesystem::types::Host + + bindings::filesystem::preopens::Host + bindings::sync_io::poll::poll::Host + bindings::random::random::Host + bindings::io::streams::Host @@ -636,9 +636,9 @@ impl< T: WasiPreview1View + bindings::cli_base::environment::Host + bindings::cli_base::exit::Host - + bindings::cli_base::preopens::Host - + bindings::filesystem::filesystem::Host - + bindings::sync_io::poll::poll::Host + + bindings::filesystem::preopens::Host + + bindings::filesystem::types::Host + + bindings::poll::poll::Host + bindings::random::random::Host + bindings::io::streams::Host + bindings::clocks::monotonic_clock::Host @@ -1010,8 +1010,6 @@ impl< } Descriptor::PreopenDirectory((fd, _)) | Descriptor::File(File { fd, .. }) => { let filesystem::DescriptorStat { - device: dev, - inode: ino, type_, link_count: nlink, size, @@ -1023,13 +1021,18 @@ impl< .context("failed to call `stat`") .unwrap_or_else(types::Error::trap) })?; + let metadata_hash = self.metadata_hash(fd).await.map_err(|e| { + e.try_into() + .context("failed to call `metadata_hash`") + .unwrap_or_else(types::Error::trap) + })?; let filetype = type_.try_into().map_err(types::Error::trap)?; let atim = data_access_timestamp.try_into()?; let mtim = data_modification_timestamp.try_into()?; let ctim = status_change_timestamp.try_into()?; Ok(types::Filestat { - dev, - ino, + dev: 1, + ino: metadata_hash.lower, filetype, nlink, size, @@ -1408,11 +1411,9 @@ impl< .context("failed to call `read-directory`") .unwrap_or_else(types::Error::trap) })?; - let filesystem::DescriptorStat { - inode: fd_inode, .. - } = self.stat(fd).await.map_err(|e| { + let dir_metadata_hash = self.metadata_hash(fd).await.map_err(|e| { e.try_into() - .context("failed to call `stat`") + .context("failed to call `metadata-hash`") .unwrap_or_else(types::Error::trap) })?; let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?; @@ -1421,7 +1422,7 @@ impl< ( types::Dirent { d_next: 1u64.to_le(), - d_ino: fd_inode.to_le(), + d_ino: dir_metadata_hash.lower.to_le(), d_type: types::Filetype::Directory, d_namlen: 1u32.to_le(), }, @@ -1430,40 +1431,47 @@ impl< ( types::Dirent { d_next: 2u64.to_le(), - d_ino: fd_inode.to_le(), // NOTE: incorrect, but legacy implementation returns `fd` inode here + d_ino: dir_metadata_hash.lower.to_le(), // NOTE: incorrect, but legacy implementation returns `fd` inode here d_type: types::Filetype::Directory, d_namlen: 2u32.to_le(), }, "..".into(), ), - ] - .into_iter() - .map(Ok::<_, types::Error>); + ]; - let dir = self + let mut dir = Vec::new(); + for (entry, d_next) in self .table_mut() // remove iterator from table and use it directly: .delete_readdir(stream)? .into_iter() .zip(3u64..) - .map(|(entry, d_next)| { - let filesystem::DirectoryEntry { inode, type_, name } = entry.map_err(|e| { + { + let filesystem::DirectoryEntry { type_, name } = entry.map_err(|e| { + e.try_into() + .context("failed to inspect `read-directory` entry") + .unwrap_or_else(types::Error::trap) + })?; + let metadata_hash = self + .metadata_hash_at(fd, filesystem::PathFlags::empty(), name.clone()) + .await + .map_err(|e| { e.try_into() - .context("failed to inspect `read-directory` entry") + .context("failed to call `metadata-hash-at`") .unwrap_or_else(types::Error::trap) })?; - let d_type = type_.try_into().map_err(types::Error::trap)?; - let d_namlen: u32 = name.len().try_into().map_err(|_| types::Errno::Overflow)?; - Ok(( - types::Dirent { - d_next: d_next.to_le(), - d_ino: inode.unwrap_or_default().to_le(), - d_type, // endian-invariant - d_namlen: d_namlen.to_le(), - }, - name, - )) - }); + let d_type = type_.try_into().map_err(types::Error::trap)?; + let d_namlen: u32 = name.len().try_into().map_err(|_| types::Errno::Overflow)?; + dir.push(( + types::Dirent { + d_next: d_next.to_le(), + d_ino: metadata_hash.lower.to_le(), + d_type, // endian-invariant + d_namlen: d_namlen.to_le(), + }, + name, + )) + } // assume that `types::Dirent` size always fits in `u32` const DIRENT_SIZE: u32 = size_of::() as _; @@ -1474,9 +1482,7 @@ impl< ); let mut buf = *buf; let mut cap = buf_len; - for entry in head.chain(dir).skip(cookie) { - let (ref entry, mut path) = entry?; - + for (ref entry, mut path) in head.into_iter().chain(dir.into_iter()).skip(cookie) { assert_eq!( 1, size_of_val(&entry.d_type), @@ -1531,26 +1537,35 @@ impl< let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(path)?; let filesystem::DescriptorStat { - device: dev, - inode: ino, type_, link_count: nlink, size, data_access_timestamp, data_modification_timestamp, status_change_timestamp, - } = self.stat_at(dirfd, flags.into(), path).await.map_err(|e| { - e.try_into() - .context("failed to call `stat-at`") - .unwrap_or_else(types::Error::trap) - })?; + } = self + .stat_at(dirfd, flags.into(), path.clone()) + .await + .map_err(|e| { + e.try_into() + .context("failed to call `stat-at`") + .unwrap_or_else(types::Error::trap) + })?; + let metadata_hash = self + .metadata_hash_at(dirfd, flags.into(), path) + .await + .map_err(|e| { + e.try_into() + .context("failed to call `metadata-hash-at`") + .unwrap_or_else(types::Error::trap) + })?; let filetype = type_.try_into().map_err(types::Error::trap)?; let atim = data_access_timestamp.try_into()?; let mtim = data_modification_timestamp.try_into()?; let ctim = status_change_timestamp.try_into()?; Ok(types::Filestat { - dev, - ino, + dev: 1, + ino: metadata_hash.lower, filetype, nlink, size, diff --git a/crates/wasi/src/preview2/preview2/env.rs b/crates/wasi/src/preview2/preview2/env.rs index 329c0dd08fe1..edb4d118d979 100644 --- a/crates/wasi/src/preview2/preview2/env.rs +++ b/crates/wasi/src/preview2/preview2/env.rs @@ -1,5 +1,4 @@ -use crate::preview2::bindings::cli_base::{environment, preopens, stderr, stdin, stdout}; -use crate::preview2::bindings::filesystem::filesystem; +use crate::preview2::bindings::cli_base::{environment, stderr, stdin, stdout}; use crate::preview2::bindings::io::streams; use crate::preview2::WasiView; @@ -12,12 +11,6 @@ impl environment::Host for T { } } -impl preopens::Host for T { - fn get_directories(&mut self) -> Result, anyhow::Error> { - Ok(self.ctx().preopens.clone()) - } -} - impl stdin::Host for T { fn get_stdin(&mut self) -> Result { Ok(self.ctx().stdin) diff --git a/crates/wasi/src/preview2/preview2/filesystem.rs b/crates/wasi/src/preview2/preview2/filesystem.rs index 27f40c7d9393..1fc1e17e22f8 100644 --- a/crates/wasi/src/preview2/preview2/filesystem.rs +++ b/crates/wasi/src/preview2/preview2/filesystem.rs @@ -1,30 +1,36 @@ use crate::preview2::bindings::clocks::wall_clock; -use crate::preview2::bindings::filesystem::filesystem; +use crate::preview2::bindings::filesystem::{preopens, types}; use crate::preview2::bindings::io::streams; use crate::preview2::filesystem::{Dir, File, TableFsExt}; use crate::preview2::{DirPerms, FilePerms, Table, TableError, WasiView}; -use filesystem::ErrorCode; +use types::ErrorCode; mod sync; -impl From for filesystem::Error { +impl From for types::Error { fn from(error: TableError) -> Self { Self::trap(error.into()) } } +impl preopens::Host for T { + fn get_directories(&mut self) -> Result, anyhow::Error> { + Ok(self.ctx().preopens.clone()) + } +} + #[async_trait::async_trait] -impl filesystem::Host for T { +impl types::Host for T { async fn advise( &mut self, - fd: filesystem::Descriptor, - offset: filesystem::Filesize, - len: filesystem::Filesize, - advice: filesystem::Advice, - ) -> Result<(), filesystem::Error> { - use filesystem::Advice; + fd: types::Descriptor, + offset: types::Filesize, + len: types::Filesize, + advice: types::Advice, + ) -> Result<(), types::Error> { use system_interface::fs::{Advice as A, FileIoExt}; + use types::Advice; let advice = match advice { Advice::Normal => A::Normal, @@ -41,7 +47,7 @@ impl filesystem::Host for T { Ok(()) } - async fn sync_data(&mut self, fd: filesystem::Descriptor) -> Result<(), filesystem::Error> { + async fn sync_data(&mut self, fd: types::Descriptor) -> Result<(), types::Error> { let table = self.table(); if table.is_file(fd) { let f = table.get_file(fd)?; @@ -70,10 +76,10 @@ impl filesystem::Host for T { async fn get_flags( &mut self, - fd: filesystem::Descriptor, - ) -> Result { - use filesystem::DescriptorFlags; + fd: types::Descriptor, + ) -> Result { use system_interface::fs::{FdFlags, GetSetFdFlags}; + use types::DescriptorFlags; fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags { let mut out = DescriptorFlags::empty(); @@ -119,8 +125,8 @@ impl filesystem::Host for T { async fn get_type( &mut self, - fd: filesystem::Descriptor, - ) -> Result { + fd: types::Descriptor, + ) -> Result { let table = self.table(); if table.is_file(fd) { @@ -128,7 +134,7 @@ impl filesystem::Host for T { let meta = f.spawn_blocking(|f| f.metadata()).await?; Ok(descriptortype_from(meta.file_type())) } else if table.is_dir(fd) { - Ok(filesystem::DescriptorType::Directory) + Ok(types::DescriptorType::Directory) } else { Err(ErrorCode::BadDescriptor.into()) } @@ -136,9 +142,9 @@ impl filesystem::Host for T { async fn set_size( &mut self, - fd: filesystem::Descriptor, - size: filesystem::Filesize, - ) -> Result<(), filesystem::Error> { + fd: types::Descriptor, + size: types::Filesize, + ) -> Result<(), types::Error> { let f = self.table().get_file(fd)?; if !f.perms.contains(FilePerms::WRITE) { Err(ErrorCode::NotPermitted)?; @@ -149,10 +155,10 @@ impl filesystem::Host for T { async fn set_times( &mut self, - fd: filesystem::Descriptor, - atim: filesystem::NewTimestamp, - mtim: filesystem::NewTimestamp, - ) -> Result<(), filesystem::Error> { + fd: types::Descriptor, + atim: types::NewTimestamp, + mtim: types::NewTimestamp, + ) -> Result<(), types::Error> { use fs_set_times::SetTimes; let table = self.table(); @@ -181,10 +187,10 @@ impl filesystem::Host for T { async fn read( &mut self, - fd: filesystem::Descriptor, - len: filesystem::Filesize, - offset: filesystem::Filesize, - ) -> Result<(Vec, bool), filesystem::Error> { + fd: types::Descriptor, + len: types::Filesize, + offset: types::Filesize, + ) -> Result<(Vec, bool), types::Error> { use std::io::IoSliceMut; use system_interface::fs::FileIoExt; @@ -216,10 +222,10 @@ impl filesystem::Host for T { async fn write( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, buf: Vec, - offset: filesystem::Filesize, - ) -> Result { + offset: types::Filesize, + ) -> Result { use std::io::IoSlice; use system_interface::fs::FileIoExt; @@ -233,15 +239,13 @@ impl filesystem::Host for T { .spawn_blocking(move |f| f.write_vectored_at(&[IoSlice::new(&buf)], offset)) .await?; - Ok(filesystem::Filesize::try_from(bytes_written).expect("usize fits in Filesize")) + Ok(types::Filesize::try_from(bytes_written).expect("usize fits in Filesize")) } async fn read_directory( &mut self, - fd: filesystem::Descriptor, - ) -> Result { - use cap_fs_ext::{DirEntryExt, MetadataExt}; - + fd: types::Descriptor, + ) -> Result { let table = self.table_mut(); let d = table.get_dir(fd)?; if !d.perms.contains(DirPerms::READ) { @@ -260,23 +264,22 @@ impl filesystem::Host for T { let entries = d .spawn_blocking(|d| { - // Both `entries` and `full_metadata` perform syscalls, which is why they are done - // within this `block` call, rather than delay calculating the full metadata + // Both `entries` and `metadata` perform syscalls, which is why they are done + // within this `block` call, rather than delay calculating the metadata // for entries when they're demanded later in the iterator chain. Ok::<_, std::io::Error>( d.entries()? .map(|entry| { let entry = entry?; - let meta = entry.full_metadata()?; - let inode = Some(meta.ino()); + let meta = entry.metadata()?; let type_ = descriptortype_from(meta.file_type()); let name = entry .file_name() .into_string() .map_err(|_| ReaddirError::IllegalSequence)?; - Ok(filesystem::DirectoryEntry { inode, type_, name }) + Ok(types::DirectoryEntry { type_, name }) }) - .collect::>>(), + .collect::>>(), ) }) .await? @@ -298,7 +301,7 @@ impl filesystem::Host for T { }); let entries = entries.map(|r| match r { Ok(r) => Ok(r), - Err(ReaddirError::Io(e)) => Err(filesystem::Error::from(e)), + Err(ReaddirError::Io(e)) => Err(types::Error::from(e)), Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()), }); Ok(table.push_readdir(ReaddirIterator::new(entries))?) @@ -306,8 +309,8 @@ impl filesystem::Host for T { async fn read_directory_entry( &mut self, - stream: filesystem::DirectoryEntryStream, - ) -> Result, filesystem::Error> { + stream: types::DirectoryEntryStream, + ) -> Result, types::Error> { let table = self.table(); let readdir = table.get_readdir(stream)?; readdir.next() @@ -315,13 +318,13 @@ impl filesystem::Host for T { async fn drop_directory_entry_stream( &mut self, - stream: filesystem::DirectoryEntryStream, + stream: types::DirectoryEntryStream, ) -> anyhow::Result<()> { self.table_mut().delete_readdir(stream)?; Ok(()) } - async fn sync(&mut self, fd: filesystem::Descriptor) -> Result<(), filesystem::Error> { + async fn sync(&mut self, fd: types::Descriptor) -> Result<(), types::Error> { let table = self.table(); if table.is_file(fd) { let f = table.get_file(fd)?; @@ -350,9 +353,9 @@ impl filesystem::Host for T { async fn create_directory_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, path: String, - ) -> Result<(), filesystem::Error> { + ) -> Result<(), types::Error> { let table = self.table(); let d = table.get_dir(fd)?; if !d.perms.contains(DirPerms::MUTATE) { @@ -362,10 +365,7 @@ impl filesystem::Host for T { Ok(()) } - async fn stat( - &mut self, - fd: filesystem::Descriptor, - ) -> Result { + async fn stat(&mut self, fd: types::Descriptor) -> Result { let table = self.table(); if table.is_file(fd) { let f = table.get_file(fd)?; @@ -384,10 +384,10 @@ impl filesystem::Host for T { async fn stat_at( &mut self, - fd: filesystem::Descriptor, - path_flags: filesystem::PathFlags, + fd: types::Descriptor, + path_flags: types::PathFlags, path: String, - ) -> Result { + ) -> Result { let table = self.table(); let d = table.get_dir(fd)?; if !d.perms.contains(DirPerms::READ) { @@ -404,12 +404,12 @@ impl filesystem::Host for T { async fn set_times_at( &mut self, - fd: filesystem::Descriptor, - path_flags: filesystem::PathFlags, + fd: types::Descriptor, + path_flags: types::PathFlags, path: String, - atim: filesystem::NewTimestamp, - mtim: filesystem::NewTimestamp, - ) -> Result<(), filesystem::Error> { + atim: types::NewTimestamp, + mtim: types::NewTimestamp, + ) -> Result<(), types::Error> { use cap_fs_ext::DirExt; let table = self.table(); @@ -443,13 +443,13 @@ impl filesystem::Host for T { async fn link_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, // TODO delete the path flags from this function - old_path_flags: filesystem::PathFlags, + old_path_flags: types::PathFlags, old_path: String, - new_descriptor: filesystem::Descriptor, + new_descriptor: types::Descriptor, new_path: String, - ) -> Result<(), filesystem::Error> { + ) -> Result<(), types::Error> { let table = self.table(); let old_dir = table.get_dir(fd)?; if !old_dir.perms.contains(DirPerms::MUTATE) { @@ -471,18 +471,18 @@ impl filesystem::Host for T { async fn open_at( &mut self, - fd: filesystem::Descriptor, - path_flags: filesystem::PathFlags, + fd: types::Descriptor, + path_flags: types::PathFlags, path: String, - oflags: filesystem::OpenFlags, - flags: filesystem::DescriptorFlags, + oflags: types::OpenFlags, + flags: types::DescriptorFlags, // TODO: These are the permissions to use when creating a new file. // Not implemented yet. - _mode: filesystem::Modes, - ) -> Result { + _mode: types::Modes, + ) -> Result { use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; - use filesystem::{DescriptorFlags, OpenFlags}; use system_interface::fs::{FdFlags, GetSetFdFlags}; + use types::{DescriptorFlags, OpenFlags}; let table = self.table_mut(); if table.is_file(fd) { @@ -587,7 +587,7 @@ impl filesystem::Host for T { } } - async fn drop_descriptor(&mut self, fd: filesystem::Descriptor) -> anyhow::Result<()> { + async fn drop_descriptor(&mut self, fd: types::Descriptor) -> anyhow::Result<()> { let table = self.table_mut(); // The Drop will close the file/dir, but if the close syscall @@ -604,9 +604,9 @@ impl filesystem::Host for T { async fn readlink_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, path: String, - ) -> Result { + ) -> Result { let table = self.table(); let d = table.get_dir(fd)?; if !d.perms.contains(DirPerms::READ) { @@ -621,9 +621,9 @@ impl filesystem::Host for T { async fn remove_directory_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, path: String, - ) -> Result<(), filesystem::Error> { + ) -> Result<(), types::Error> { let table = self.table(); let d = table.get_dir(fd)?; if !d.perms.contains(DirPerms::MUTATE) { @@ -634,11 +634,11 @@ impl filesystem::Host for T { async fn rename_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, old_path: String, - new_fd: filesystem::Descriptor, + new_fd: types::Descriptor, new_path: String, - ) -> Result<(), filesystem::Error> { + ) -> Result<(), types::Error> { let table = self.table(); let old_dir = table.get_dir(fd)?; if !old_dir.perms.contains(DirPerms::MUTATE) { @@ -656,10 +656,10 @@ impl filesystem::Host for T { async fn symlink_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, src_path: String, dest_path: String, - ) -> Result<(), filesystem::Error> { + ) -> Result<(), types::Error> { // On windows, Dir.symlink is provided by DirExt #[cfg(windows)] use cap_fs_ext::DirExt; @@ -675,9 +675,9 @@ impl filesystem::Host for T { async fn unlink_file_at( &mut self, - fd: filesystem::Descriptor, + fd: types::Descriptor, path: String, - ) -> Result<(), filesystem::Error> { + ) -> Result<(), types::Error> { use cap_fs_ext::DirExt; let table = self.table(); @@ -691,68 +691,59 @@ impl filesystem::Host for T { async fn access_at( &mut self, - _fd: filesystem::Descriptor, - _path_flags: filesystem::PathFlags, + _fd: types::Descriptor, + _path_flags: types::PathFlags, _path: String, - _access: filesystem::AccessType, - ) -> Result<(), filesystem::Error> { + _access: types::AccessType, + ) -> Result<(), types::Error> { todo!("filesystem access_at is not implemented") } async fn change_file_permissions_at( &mut self, - _fd: filesystem::Descriptor, - _path_flags: filesystem::PathFlags, + _fd: types::Descriptor, + _path_flags: types::PathFlags, _path: String, - _mode: filesystem::Modes, - ) -> Result<(), filesystem::Error> { + _mode: types::Modes, + ) -> Result<(), types::Error> { todo!("filesystem change_file_permissions_at is not implemented") } async fn change_directory_permissions_at( &mut self, - _fd: filesystem::Descriptor, - _path_flags: filesystem::PathFlags, + _fd: types::Descriptor, + _path_flags: types::PathFlags, _path: String, - _mode: filesystem::Modes, - ) -> Result<(), filesystem::Error> { + _mode: types::Modes, + ) -> Result<(), types::Error> { todo!("filesystem change_directory_permissions_at is not implemented") } - async fn lock_shared(&mut self, _fd: filesystem::Descriptor) -> Result<(), filesystem::Error> { + async fn lock_shared(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { todo!("filesystem lock_shared is not implemented") } - async fn lock_exclusive( - &mut self, - _fd: filesystem::Descriptor, - ) -> Result<(), filesystem::Error> { + async fn lock_exclusive(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { todo!("filesystem lock_exclusive is not implemented") } - async fn try_lock_shared( - &mut self, - _fd: filesystem::Descriptor, - ) -> Result<(), filesystem::Error> { + async fn try_lock_shared(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { todo!("filesystem try_lock_shared is not implemented") } - async fn try_lock_exclusive( - &mut self, - _fd: filesystem::Descriptor, - ) -> Result<(), filesystem::Error> { + async fn try_lock_exclusive(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { todo!("filesystem try_lock_exclusive is not implemented") } - async fn unlock(&mut self, _fd: filesystem::Descriptor) -> Result<(), filesystem::Error> { + async fn unlock(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { todo!("filesystem unlock is not implemented") } async fn read_via_stream( &mut self, - fd: filesystem::Descriptor, - offset: filesystem::Filesize, - ) -> Result { + fd: types::Descriptor, + offset: types::Filesize, + ) -> Result { use crate::preview2::{ filesystem::FileInputStream, stream::{InternalInputStream, InternalTableStreamExt}, @@ -762,7 +753,7 @@ impl filesystem::Host for T { let f = self.table().get_file(fd)?; if !f.perms.contains(FilePerms::READ) { - Err(filesystem::ErrorCode::BadDescriptor)?; + Err(types::ErrorCode::BadDescriptor)?; } // Duplicate the file descriptor so that we get an indepenent lifetime. let clone = std::sync::Arc::clone(&f.file); @@ -780,9 +771,9 @@ impl filesystem::Host for T { async fn write_via_stream( &mut self, - fd: filesystem::Descriptor, - offset: filesystem::Filesize, - ) -> Result { + fd: types::Descriptor, + offset: types::Filesize, + ) -> Result { use crate::preview2::{ filesystem::FileOutputStream, stream::{InternalOutputStream, InternalTableStreamExt}, @@ -792,7 +783,7 @@ impl filesystem::Host for T { let f = self.table().get_file(fd)?; if !f.perms.contains(FilePerms::WRITE) { - Err(filesystem::ErrorCode::BadDescriptor)?; + Err(types::ErrorCode::BadDescriptor)?; } // Duplicate the file descriptor so that we get an indepenent lifetime. @@ -811,8 +802,8 @@ impl filesystem::Host for T { async fn append_via_stream( &mut self, - fd: filesystem::Descriptor, - ) -> Result { + fd: types::Descriptor, + ) -> Result { use crate::preview2::{ filesystem::FileOutputStream, stream::{InternalOutputStream, InternalTableStreamExt}, @@ -822,7 +813,7 @@ impl filesystem::Host for T { let f = self.table().get_file(fd)?; if !f.perms.contains(FilePerms::WRITE) { - Err(filesystem::ErrorCode::BadDescriptor)?; + Err(types::ErrorCode::BadDescriptor)?; } // Duplicate the file descriptor so that we get an indepenent lifetime. let clone = std::sync::Arc::clone(&f.file); @@ -837,10 +828,106 @@ impl filesystem::Host for T { Ok(index) } + + async fn is_same_object( + &mut self, + a: types::Descriptor, + b: types::Descriptor, + ) -> anyhow::Result { + use cap_fs_ext::MetadataExt; + let table = self.table(); + let meta_a = get_descriptor_metadata(table, a).await?; + let meta_b = get_descriptor_metadata(table, b).await?; + if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() { + // MetadataHashValue does not derive eq, so use a pair of + // comparisons to check equality: + debug_assert_eq!( + calculate_metadata_hash(&meta_a).upper, + calculate_metadata_hash(&meta_b).upper + ); + debug_assert_eq!( + calculate_metadata_hash(&meta_a).lower, + calculate_metadata_hash(&meta_b).lower + ); + Ok(true) + } else { + // Hash collisions are possible, so don't assert the negative here + Ok(false) + } + } + async fn metadata_hash( + &mut self, + fd: types::Descriptor, + ) -> Result { + let table = self.table(); + let meta = get_descriptor_metadata(table, fd).await?; + Ok(calculate_metadata_hash(&meta)) + } + async fn metadata_hash_at( + &mut self, + fd: types::Descriptor, + path_flags: types::PathFlags, + path: String, + ) -> Result { + let table = self.table(); + let d = table.get_dir(fd)?; + // No permissions check on metadata: if dir opened, allowed to stat it + let meta = d + .spawn_blocking(move |d| { + if symlink_follow(path_flags) { + d.metadata(path) + } else { + d.symlink_metadata(path) + } + }) + .await?; + Ok(calculate_metadata_hash(&meta)) + } +} + +async fn get_descriptor_metadata( + table: &Table, + fd: types::Descriptor, +) -> Result { + if table.is_file(fd) { + let f = table.get_file(fd)?; + // No permissions check on metadata: if opened, allowed to stat it + Ok(f.spawn_blocking(|f| f.metadata()).await?) + } else if table.is_dir(fd) { + let d = table.get_dir(fd)?; + // No permissions check on metadata: if opened, allowed to stat it + Ok(d.spawn_blocking(|d| d.dir_metadata()).await?) + } else { + Err(ErrorCode::BadDescriptor.into()) + } +} + +fn calculate_metadata_hash(meta: &cap_std::fs::Metadata) -> types::MetadataHashValue { + use cap_fs_ext::MetadataExt; + // Without incurring any deps, std provides us with a 64 bit hash + // function: + use std::hash::Hasher; + // Note that this means that the metadata hash (which becomes a preview1 ino) may + // change when a different rustc release is used to build this host implementation: + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + hasher.write_u64(meta.dev()); + hasher.write_u64(meta.ino()); + let lower = hasher.finish(); + // MetadataHashValue has a pair of 64-bit members for representing a + // single 128-bit number. However, we only have 64 bits of entropy. To + // synthesize the upper 64 bits, lets xor the lower half with an arbitrary + // constant, in this case the 64 bit integer corresponding to the IEEE + // double representation of (a number as close as possible to) pi. + // This seems better than just repeating the same bits in the upper and + // lower parts outright, which could make folks wonder if the struct was + // mangled in the ABI, or worse yet, lead to consumers of this interface + // expecting them to be equal. + let upper = lower ^ 4614256656552045848u64; + types::MetadataHashValue { lower, upper } } #[cfg(unix)] -fn from_raw_os_error(err: Option) -> Option { +fn from_raw_os_error(err: Option) -> Option { use rustix::io::Errno as RustixErrno; if err.is_none() { return None; @@ -880,7 +967,7 @@ fn from_raw_os_error(err: Option) -> Option { }) } #[cfg(windows)] -fn from_raw_os_error(raw_os_error: Option) -> Option { +fn from_raw_os_error(raw_os_error: Option) -> Option { use windows_sys::Win32::Foundation; Some(match raw_os_error.map(|code| code as u32) { Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry.into(), @@ -909,8 +996,8 @@ fn from_raw_os_error(raw_os_error: Option) -> Option { }) } -impl From for filesystem::Error { - fn from(err: std::io::Error) -> filesystem::Error { +impl From for types::Error { + fn from(err: std::io::Error) -> types::Error { match from_raw_os_error(err.raw_os_error()) { Some(errno) => errno, None => match err.kind() { @@ -918,29 +1005,28 @@ impl From for filesystem::Error { std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted.into(), std::io::ErrorKind::AlreadyExists => ErrorCode::Exist.into(), std::io::ErrorKind::InvalidInput => ErrorCode::Invalid.into(), - _ => filesystem::Error::trap(anyhow::anyhow!(err).context("Unknown OS error")), + _ => types::Error::trap(anyhow::anyhow!(err).context("Unknown OS error")), }, } } } -impl From for filesystem::Error { - fn from(err: cap_rand::Error) -> filesystem::Error { +impl From for types::Error { + fn from(err: cap_rand::Error) -> types::Error { // I picked Error::Io as a 'reasonable default', FIXME dan is this ok? - from_raw_os_error(err.raw_os_error()) - .unwrap_or_else(|| filesystem::Error::from(ErrorCode::Io)) + from_raw_os_error(err.raw_os_error()).unwrap_or_else(|| types::Error::from(ErrorCode::Io)) } } -impl From for filesystem::Error { - fn from(_err: std::num::TryFromIntError) -> filesystem::Error { +impl From for types::Error { + fn from(_err: std::num::TryFromIntError) -> types::Error { ErrorCode::Overflow.into() } } -fn descriptortype_from(ft: cap_std::fs::FileType) -> filesystem::DescriptorType { +fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType { use cap_fs_ext::FileTypeExt; - use filesystem::DescriptorType; + use types::DescriptorType; if ft.is_dir() { DescriptorType::Directory } else if ft.is_symlink() { @@ -957,10 +1043,10 @@ fn descriptortype_from(ft: cap_std::fs::FileType) -> filesystem::DescriptorType } fn systemtimespec_from( - t: filesystem::NewTimestamp, -) -> Result, filesystem::Error> { - use filesystem::NewTimestamp; + t: types::NewTimestamp, +) -> Result, types::Error> { use fs_set_times::SystemTimeSpec; + use types::NewTimestamp; match t { NewTimestamp::NoChange => Ok(None), NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)), @@ -968,7 +1054,7 @@ fn systemtimespec_from( } } -fn systemtime_from(t: wall_clock::Datetime) -> Result { +fn systemtime_from(t: wall_clock::Datetime) -> Result { use std::time::{Duration, SystemTime}; SystemTime::UNIX_EPOCH .checked_add(Duration::new(t.seconds, t.nanoseconds)) @@ -980,13 +1066,9 @@ fn datetime_from(t: std::time::SystemTime) -> wall_clock::Datetime { wall_clock::Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap() } -fn descriptorstat_from(meta: cap_std::fs::Metadata) -> filesystem::DescriptorStat { +fn descriptorstat_from(meta: cap_std::fs::Metadata) -> types::DescriptorStat { use cap_fs_ext::MetadataExt; - filesystem::DescriptorStat { - // FIXME didn't we agree that the wit could be changed to make the device and ino fields - // optional? - device: meta.dev(), - inode: meta.ino(), + types::DescriptorStat { type_: descriptortype_from(meta.file_type()), link_count: meta.nlink(), size: meta.len(), @@ -1015,34 +1097,30 @@ fn descriptorstat_from(meta: cap_std::fs::Metadata) -> filesystem::DescriptorSta } } -fn symlink_follow(path_flags: filesystem::PathFlags) -> bool { - path_flags.contains(filesystem::PathFlags::SYMLINK_FOLLOW) +fn symlink_follow(path_flags: types::PathFlags) -> bool { + path_flags.contains(types::PathFlags::SYMLINK_FOLLOW) } pub(crate) struct ReaddirIterator( std::sync::Mutex< - Box< - dyn Iterator> - + Send - + 'static, - >, + Box> + Send + 'static>, >, ); impl ReaddirIterator { fn new( - i: impl Iterator> + Send + 'static, + i: impl Iterator> + Send + 'static, ) -> Self { ReaddirIterator(std::sync::Mutex::new(Box::new(i))) } - fn next(&self) -> Result, filesystem::Error> { + fn next(&self) -> Result, types::Error> { self.0.lock().unwrap().next().transpose() } } impl IntoIterator for ReaddirIterator { - type Item = Result; - type IntoIter = Box>; + type Item = Result; + type IntoIter = Box + Send>; fn into_iter(self) -> Self::IntoIter { self.0.into_inner().unwrap() @@ -1068,8 +1146,8 @@ impl TableReaddirExt for Table { } } -fn mask_file_perms(p: FilePerms, flags: filesystem::DescriptorFlags) -> FilePerms { - use filesystem::DescriptorFlags; +fn mask_file_perms(p: FilePerms, flags: types::DescriptorFlags) -> FilePerms { + use types::DescriptorFlags; let mut out = FilePerms::empty(); if p.contains(FilePerms::READ) && flags.contains(DescriptorFlags::READ) { out |= FilePerms::READ; diff --git a/crates/wasi/src/preview2/preview2/filesystem/sync.rs b/crates/wasi/src/preview2/preview2/filesystem/sync.rs index 64f5973b32ef..d535f76b7a0f 100644 --- a/crates/wasi/src/preview2/preview2/filesystem/sync.rs +++ b/crates/wasi/src/preview2/preview2/filesystem/sync.rs @@ -1,5 +1,5 @@ -use crate::preview2::bindings::filesystem::filesystem as async_filesystem; -use crate::preview2::bindings::sync_io::filesystem::filesystem as sync_filesystem; +use crate::preview2::bindings::filesystem::types as async_filesystem; +use crate::preview2::bindings::sync_io::filesystem::types as sync_filesystem; use crate::preview2::bindings::sync_io::io::streams; use crate::preview2::in_tokio; @@ -388,6 +388,33 @@ impl sync_filesystem::Host for T { async_filesystem::Host::append_via_stream(self, fd).await })?) } + + fn is_same_object( + &mut self, + a: sync_filesystem::Descriptor, + b: sync_filesystem::Descriptor, + ) -> anyhow::Result { + Ok(in_tokio(async { + async_filesystem::Host::is_same_object(self, a, b).await + })?) + } + fn metadata_hash( + &mut self, + fd: sync_filesystem::Descriptor, + ) -> Result { + Ok(in_tokio(async { async_filesystem::Host::metadata_hash(self, fd).await })?.into()) + } + fn metadata_hash_at( + &mut self, + fd: sync_filesystem::Descriptor, + path_flags: sync_filesystem::PathFlags, + path: String, + ) -> Result { + Ok(in_tokio(async { + async_filesystem::Host::metadata_hash_at(self, fd, path_flags.into(), path).await + })? + .into()) + } } impl From for sync_filesystem::ErrorCode { @@ -502,7 +529,6 @@ impl From for sync_filesystem::DescriptorType impl From for sync_filesystem::DirectoryEntry { fn from(other: async_filesystem::DirectoryEntry) -> Self { Self { - inode: other.inode, type_: other.type_.into(), name: other.name, } @@ -512,8 +538,6 @@ impl From for sync_filesystem::DirectoryEntry impl From for sync_filesystem::DescriptorStat { fn from(other: async_filesystem::DescriptorStat) -> Self { Self { - device: other.device, - inode: other.inode, type_: other.type_.into(), link_count: other.link_count, size: other.size, @@ -611,3 +635,11 @@ impl From for async_filesystem::AccessType { } } } +impl From for sync_filesystem::MetadataHashValue { + fn from(other: async_filesystem::MetadataHashValue) -> Self { + Self { + lower: other.lower, + upper: other.upper, + } + } +} diff --git a/crates/wasi/wit/deps/filesystem/preopens.wit b/crates/wasi/wit/deps/filesystem/preopens.wit new file mode 100644 index 000000000000..f45661b8a849 --- /dev/null +++ b/crates/wasi/wit/deps/filesystem/preopens.wit @@ -0,0 +1,6 @@ +interface preopens { + use types.{descriptor} + + /// Return the set of preopened directories, and their path. + get-directories: func() -> list> +} diff --git a/crates/wasi/wit/deps/filesystem/filesystem.wit b/crates/wasi/wit/deps/filesystem/types.wit similarity index 89% rename from crates/wasi/wit/deps/filesystem/filesystem.wit rename to crates/wasi/wit/deps/filesystem/types.wit index 930f37567044..e72a742de6c8 100644 --- a/crates/wasi/wit/deps/filesystem/filesystem.wit +++ b/crates/wasi/wit/deps/filesystem/types.wit @@ -1,5 +1,3 @@ -package wasi:filesystem - /// WASI filesystem is a filesystem API primarily intended to let users run WASI /// programs that access their files on their existing filesystems, without /// significant overhead. @@ -19,7 +17,7 @@ package wasi:filesystem /// `..` and symbolic link steps, reaches a directory outside of the base /// directory, or reaches a symlink to an absolute or rooted path in the /// underlying filesystem, the function fails with `error-code::not-permitted`. -interface filesystem { +interface types { use wasi:io/streams.{input-stream, output-stream} use wasi:clocks/wall-clock.{datetime} @@ -96,10 +94,6 @@ interface filesystem { /// /// Note: This was called `filestat` in earlier versions of WASI. record descriptor-stat { - /// Device ID of device containing the file. - device: device, - /// File serial number. - inode: inode, /// File type. %type: descriptor-type, /// Number of hard links to the file. @@ -160,14 +154,6 @@ interface filesystem { /// Number of hard links to an inode. type link-count = u64 - /// Identifier for a device containing a file system. Can be used in - /// combination with `inode` to uniquely identify a file or directory in - /// the filesystem. - type device = u64 - - /// Filesystem object serial number that is unique within its file system. - type inode = u64 - /// When setting a timestamp, this gives the value to set it to. variant new-timestamp { /// Leave the timestamp set to its previous value. @@ -181,14 +167,6 @@ interface filesystem { /// A directory entry. record directory-entry { - /// The serial number of the object referred to by this directory entry. - /// May be none if the inode value is not known. - /// - /// When this is none, libc implementations might do an extra `stat-at` - /// call to retrieve the inode number to fill their `d_ino` fields, so - /// implementations which can set this to a non-none value should do so. - inode: option, - /// The type of the file referred to by this directory entry. %type: descriptor-type, @@ -306,21 +284,34 @@ interface filesystem { /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). type descriptor = u32 - /// Return a stream for reading from a file. + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. /// /// Multiple read, write, and append streams may be active on the same open /// file and they do not interfere with each other. /// - /// Note: This allows using `wasi:io/streams.read`, which is similar to `read` in POSIX. + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. read-via-stream: func( this: descriptor, /// The offset within the file at which to start reading. offset: filesize, ) -> result - /// Return a stream for writing to a file. + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. /// - /// Note: This allows using `wasi:io/streams.write`, which is similar to `write` in + /// Note: This allows using `write-stream`, which is similar to `write` in /// POSIX. write-via-stream: func( this: descriptor, @@ -328,9 +319,11 @@ interface filesystem { offset: filesize, ) -> result - /// Return a stream for appending to a file. + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. /// - /// Note: This allows using `wasi:io/streams.write`, which is similar to `write` with + /// Note: This allows using `write-stream`, which is similar to `write` with /// `O_APPEND` in in POSIX. append-via-stream: func( this: descriptor, @@ -464,14 +457,20 @@ interface filesystem { /// Return the attributes of an open file or directory. /// - /// Note: This is similar to `fstat` in POSIX. + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. /// /// Note: This was called `fd_filestat_get` in earlier versions of WASI. stat: func(this: descriptor) -> result /// Return the attributes of a file or directory. /// - /// Note: This is similar to `fstatat` in POSIX. + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. /// /// Note: This was called `path_filestat_get` in earlier versions of WASI. stat-at: func( @@ -779,4 +778,47 @@ interface filesystem { /// Dispose of the specified `directory-entry-stream`, after which it may no longer /// be used. drop-directory-entry-stream: func(this: directory-entry-stream) + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(this: descriptor, other: descriptor) -> bool + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func( + this: descriptor, + ) -> result + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result } diff --git a/crates/wasi/wit/deps/filesystem/world.wit b/crates/wasi/wit/deps/filesystem/world.wit new file mode 100644 index 000000000000..b51f484f8383 --- /dev/null +++ b/crates/wasi/wit/deps/filesystem/world.wit @@ -0,0 +1,6 @@ +package wasi:filesystem + +world example-world { + import types + import preopens +} diff --git a/crates/wasi/wit/deps/preview/command-extended.wit b/crates/wasi/wit/deps/preview/command-extended.wit index 2beb119da319..8fe716038e28 100644 --- a/crates/wasi/wit/deps/preview/command-extended.wit +++ b/crates/wasi/wit/deps/preview/command-extended.wit @@ -4,7 +4,8 @@ world command-extended { import wasi:clocks/wall-clock import wasi:clocks/monotonic-clock import wasi:clocks/timezone - import wasi:filesystem/filesystem + import wasi:filesystem/types + import wasi:filesystem/preopens import wasi:sockets/instance-network import wasi:sockets/ip-name-lookup import wasi:sockets/network @@ -18,7 +19,6 @@ world command-extended { import wasi:poll/poll import wasi:io/streams import wasi:cli-base/environment - import wasi:cli-base/preopens import wasi:cli-base/exit import wasi:cli-base/stdin import wasi:cli-base/stdout diff --git a/crates/wasi/wit/deps/preview/command.wit b/crates/wasi/wit/deps/preview/command.wit index 62608a05ccf9..dcd06d795753 100644 --- a/crates/wasi/wit/deps/preview/command.wit +++ b/crates/wasi/wit/deps/preview/command.wit @@ -2,7 +2,8 @@ world command { import wasi:clocks/wall-clock import wasi:clocks/monotonic-clock import wasi:clocks/timezone - import wasi:filesystem/filesystem + import wasi:filesystem/types + import wasi:filesystem/preopens import wasi:sockets/instance-network import wasi:sockets/ip-name-lookup import wasi:sockets/network @@ -16,7 +17,6 @@ world command { import wasi:poll/poll import wasi:io/streams import wasi:cli-base/environment - import wasi:cli-base/preopens import wasi:cli-base/exit import wasi:cli-base/stdin import wasi:cli-base/stdout diff --git a/crates/wasi/wit/deps/preview/reactor.wit b/crates/wasi/wit/deps/preview/reactor.wit index 4a262c551d3f..aff49ea4d186 100644 --- a/crates/wasi/wit/deps/preview/reactor.wit +++ b/crates/wasi/wit/deps/preview/reactor.wit @@ -2,7 +2,8 @@ world reactor { import wasi:clocks/wall-clock import wasi:clocks/monotonic-clock import wasi:clocks/timezone - import wasi:filesystem/filesystem + import wasi:filesystem/types + import wasi:filesystem/preopens import wasi:sockets/instance-network import wasi:sockets/ip-name-lookup import wasi:sockets/network @@ -16,7 +17,6 @@ world reactor { import wasi:logging/handler import wasi:http/outgoing-handler import wasi:cli-base/environment - import wasi:cli-base/preopens import wasi:cli-base/exit import wasi:cli-base/stdin import wasi:cli-base/stdout diff --git a/crates/wasi/wit/deps/wasi-cli-base/preopens.wit b/crates/wasi/wit/deps/wasi-cli-base/preopens.wit deleted file mode 100644 index f268f6b0b2e6..000000000000 --- a/crates/wasi/wit/deps/wasi-cli-base/preopens.wit +++ /dev/null @@ -1,7 +0,0 @@ -interface preopens { - use wasi:filesystem/filesystem.{descriptor} - use wasi:io/streams.{input-stream, output-stream} - - /// Return the set of of preopened directories, and their path. - get-directories: func() -> list> -} diff --git a/crates/wasi/wit/test.wit b/crates/wasi/wit/test.wit index 9c0fda58d0b8..09498f62cf8f 100644 --- a/crates/wasi/wit/test.wit +++ b/crates/wasi/wit/test.wit @@ -3,8 +3,8 @@ world test-reactor { import wasi:cli-base/environment import wasi:io/streams - import wasi:cli-base/preopens - import wasi:filesystem/filesystem + import wasi:filesystem/types + import wasi:filesystem/preopens import wasi:cli-base/exit export add-strings: func(s: list) -> u32 @@ -14,7 +14,7 @@ world test-reactor { export write-strings-to: func(o: output-stream) -> result - use wasi:filesystem/filesystem.{descriptor-stat} + use wasi:filesystem/types.{descriptor-stat} export pass-an-imported-record: func(d: descriptor-stat) -> string }