From 2ca08408227e173cbc768c1031ea480efc3e7a03 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 15 May 2026 00:22:52 -0400 Subject: [PATCH 01/11] Filesystem gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intercept wasi:filesystem calls allowing per call access control decisions. The 'gate' is a component that virtualizes the wasi:filesystem interfaces and also import a latch interface. The latch defines the access control check and returns a decision. For any call, a latch can either allow, deny or abstain. Denials include an error-code as the reason. Latches can be composed together to define more advanced behavior. The latch-N components compose N latches together. An allow or deny decision is returned immediately while an abstain decision allows the next latch to have an opinion. For example, if we want to make a component's access to the filesystem be read-only while other parts of the runtime need read-write, we can compose the ro-consume with a gate, while the rw-consumer has full access to the filesystem. The latch is itself a composition of two other latches, the first is the read-only latch which denies calls that would modify the filesystem. The allow latch approves calls that have otherwise not been denied. The composition enabled additional behavior be added, such as denying access to certain directories, or any other. behavior a use chooses to implement. Replace the latch2 component with latch3 when there is a third latch to orchestrate. ``` host ↗ ↖ ↗ latch-readonly rw-consumer gate → latch2 ↑ ↘ latch-allow ro-consumer ``` Signed-off-by: Scott Andrews --- Cargo.lock | 49 ++ README.md | 9 +- components/gate/Cargo.toml | 11 + components/gate/README.md | 3 + components/gate/src/lib.rs | 938 +++++++++++++++++++++++++++ components/latch-2/Cargo.toml | 11 + components/latch-2/README.md | 3 + components/latch-2/src/lib.rs | 273 ++++++++ components/latch-3/Cargo.toml | 11 + components/latch-3/README.md | 3 + components/latch-3/src/lib.rs | 273 ++++++++ components/latch-4/Cargo.toml | 11 + components/latch-4/README.md | 3 + components/latch-4/src/lib.rs | 273 ++++++++ components/latch-allow/Cargo.toml | 11 + components/latch-allow/README.md | 3 + components/latch-allow/src/lib.rs | 19 + components/latch-deny/Cargo.toml | 11 + components/latch-deny/README.md | 3 + components/latch-deny/src/lib.rs | 22 + components/latch-readonly/Cargo.toml | 11 + components/latch-readonly/README.md | 3 + components/latch-readonly/src/lib.rs | 71 ++ wit/latch.wit | 165 +++++ wit/worlds.wit | 26 + 25 files changed, 2215 insertions(+), 1 deletion(-) create mode 100644 components/gate/Cargo.toml create mode 100644 components/gate/README.md create mode 100644 components/gate/src/lib.rs create mode 100644 components/latch-2/Cargo.toml create mode 100644 components/latch-2/README.md create mode 100644 components/latch-2/src/lib.rs create mode 100644 components/latch-3/Cargo.toml create mode 100644 components/latch-3/README.md create mode 100644 components/latch-3/src/lib.rs create mode 100644 components/latch-4/Cargo.toml create mode 100644 components/latch-4/README.md create mode 100644 components/latch-4/src/lib.rs create mode 100644 components/latch-allow/Cargo.toml create mode 100644 components/latch-allow/README.md create mode 100644 components/latch-allow/src/lib.rs create mode 100644 components/latch-deny/Cargo.toml create mode 100644 components/latch-deny/README.md create mode 100644 components/latch-deny/src/lib.rs create mode 100644 components/latch-readonly/Cargo.toml create mode 100644 components/latch-readonly/README.md create mode 100644 components/latch-readonly/src/lib.rs create mode 100644 wit/latch.wit diff --git a/Cargo.lock b/Cargo.lock index 0842d54..f1e83ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,13 @@ dependencies = [ "slab", ] +[[package]] +name = "gate" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "hashbrown" version = "0.17.0" @@ -190,6 +197,48 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "latch-2" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "latch-3" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "latch-4" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "latch-allow" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "latch-deny" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "latch-readonly" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "leb128fmt" version = "0.1.0" diff --git a/README.md b/README.md index 13c337c..3d47b73 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,14 @@ A collection of utility components that remix wasi:filesystem types and interfac ## Components - [`chroot`](./components/chroot/) -- [`readonly`](./components/readonly/) +- [`gate`](./components/gate/) +- [`latch-2`](./components/latch-2/) +- [`latch-3`](./components/latch-3/) +- [`latch-4`](./components/latch-4/) +- [`latch-allow`](./components/latch-allow/) +- [`latch-deny`](./components/latch-deny/) +- [`latch-readonly`](./components/latch-readonly/) +- ~~[`readonly`](./components/readonly/)~~ (deprecated, favor gate with readonly latch) - [`tracing`](./components/tracing/) ## Build diff --git a/components/gate/Cargo.toml b/components/gate/Cargo.toml new file mode 100644 index 0000000..51d9cae --- /dev/null +++ b/components/gate/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gate" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/gate/README.md b/components/gate/README.md new file mode 100644 index 0000000..2a982e1 --- /dev/null +++ b/components/gate/README.md @@ -0,0 +1,3 @@ +# `gate` + +Filesystem gate access control. diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs new file mode 100644 index 0000000..bc1b48e --- /dev/null +++ b/components/gate/src/lib.rs @@ -0,0 +1,938 @@ +#![no_main] + +use std::rc::Rc; + +use crate::componentized::filesystem::latch::Decision::{Abstain, Allow, Deny}; +use crate::componentized::filesystem::latch::{self, check, DescriptorOperation, Operation}; +use crate::exports::wasi::filesystem::preopens::Guest as Preopens; +use crate::exports::wasi::filesystem::types::{ + Advice, Descriptor, DescriptorBorrow, DescriptorFlags, DescriptorStat, DescriptorType, + DirectoryEntry, DirectoryEntryStream, Error, ErrorCode, Filesize, Guest as Types, InputStream, + MetadataHashValue, NewTimestamp, OpenFlags, OutputStream, PathFlags, +}; +use crate::wasi::filesystem::preopens; +use crate::wasi::filesystem::types; +use crate::wasi::logging::logging::{log, Level}; + +#[macro_export] +macro_rules! warn { + ($dst:expr, $($arg:tt)*) => { + log(Level::Warn, "componentized-gate", &format!($dst, $($arg)*)); + }; + ($dst:expr) => { + log(Level::Warn, "componentized-gate", &format!($dst)); + }; +} + +#[derive(Debug, Clone)] +struct FilesystemGate {} + +impl Preopens for FilesystemGate { + #[doc = " Return the set of preopened directories, and their path."] + fn get_directories() -> Vec<(Descriptor, String)> { + preopens::get_directories() + .into_iter() + .map(|(fd, path)| { + let fd = Descriptor::new(GateDescriptor::new(fd)); + (fd, path) + }) + .collect() + } +} + +impl Types for FilesystemGate { + type Descriptor = GateDescriptor; + type DirectoryEntryStream = GateDirectoryEntryStream; + + #[doc = " Attempts to extract a filesystem-related `error-code` from the stream"] + #[doc = " `error` provided."] + #[doc = ""] + #[doc = " Stream operations which return `stream-error::last-operation-failed`"] + #[doc = " have a payload with more information about the operation that failed."] + #[doc = " This payload can be passed through to this function to see if there\'s"] + #[doc = " filesystem-related information about the error to return."] + #[doc = ""] + #[doc = " Note that this function is fallible because not all stream-related"] + #[doc = " errors are filesystem-related errors."] + fn filesystem_error_code(err: &Error) -> Option { + types::filesystem_error_code(err).map(error_code_map) + } +} + +#[derive(Debug, Clone)] +struct GateDescriptor { + fd: Rc, +} + +impl GateDescriptor { + fn new(fd: types::Descriptor) -> Self { + Self { fd: Rc::new(fd) } + } +} + +impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { + #[doc = " Return a stream for reading from a file, if available."] + #[doc = ""] + #[doc = " May fail with an error-code describing why the file cannot be read."] + #[doc = ""] + #[doc = " Multiple read, write, and append streams may be active on the same open"] + #[doc = " file and they do not interfere with each other."] + #[doc = ""] + #[doc = " Note: This allows using `read-stream`, which is similar to `read` in POSIX."] + fn read_via_stream(&self, offset: Filesize) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::ReadViaStream(latch::DescriptorReadViaStreamArgs { offset }), + ))) { + Allow => self.fd.read_via_stream(offset).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self:?} OFFSET={offset}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Return a stream for writing to a file, if available."] + #[doc = ""] + #[doc = " May fail with an error-code describing why the file cannot be written."] + #[doc = ""] + #[doc = " Note: This allows using `write-stream`, which is similar to `write` in"] + #[doc = " POSIX."] + fn write_via_stream(&self, offset: Filesize) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::WriteViaStream(latch::DescriptorWriteViaStreamArgs { offset }), + ))) { + Allow => self.fd.write_via_stream(offset).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self:?} OFFSET={offset}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Return a stream for appending to a file, if available."] + #[doc = ""] + #[doc = " May fail with an error-code describing why the file cannot be appended."] + #[doc = ""] + #[doc = " Note: This allows using `write-stream`, which is similar to `write` with"] + #[doc = " `O_APPEND` in in POSIX."] + fn append_via_stream(&self) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::AppendViaStream, + ))) { + Allow => self.fd.append_via_stream().map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Provide file advisory information on a descriptor."] + #[doc = ""] + #[doc = " This is similar to `posix_fadvise` in POSIX."] + fn advise(&self, offset: Filesize, length: Filesize, advice: Advice) -> Result<(), ErrorCode> { + let advice = advice_map_in(advice); + + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::Advise(latch::DescriptorAdviseArgs { + offset, + length, + advice, + }), + ))) { + Allow => self + .fd + .advise(offset, length, advice) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.advise FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Synchronize the data of a file to disk."] + #[doc = ""] + #[doc = " This function succeeds with no effect if the file descriptor is not"] + #[doc = " opened for writing."] + #[doc = ""] + #[doc = " Note: This is similar to `fdatasync` in POSIX."] + fn sync_data(&self) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::SyncData, + ))) { + Allow => self.fd.sync_data().map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Get flags associated with a descriptor."] + #[doc = ""] + #[doc = " Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX."] + #[doc = ""] + #[doc = " Note: This returns the value that was the `fs_flags` value returned"] + #[doc = " from `fdstat_get` in earlier versions of WASI."] + fn get_flags(&self) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::GetFlags, + ))) { + Allow => self + .fd + .get_flags() + .map(descriptor_flags_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Get the dynamic type of a descriptor."] + #[doc = ""] + #[doc = " Note: This returns the same value as the `type` field of the `fd-stat`"] + #[doc = " returned by `stat`, `stat-at` and similar."] + #[doc = ""] + #[doc = " Note: This returns similar flags to the `st_mode & S_IFMT` value provided"] + #[doc = " by `fstat` in POSIX."] + #[doc = ""] + #[doc = " Note: This returns the value that was the `fs_filetype` value returned"] + #[doc = " from `fdstat_get` in earlier versions of WASI."] + fn get_type(&self) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::GetType, + ))) { + Allow => self + .fd + .get_type() + .map(descriptor_type_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Adjust the size of an open file. If this increases the file\'s size, the"] + #[doc = " extra bytes are filled with zeros."] + #[doc = ""] + #[doc = " Note: This was called `fd_filestat_set_size` in earlier versions of WASI."] + fn set_size(&self, size: Filesize) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::SetSize(latch::DescriptorSetSizeArgs { size }), + ))) { + Allow => self.fd.set_size(size).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self:?} SIZE={size}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Adjust the timestamps of an open file or directory."] + #[doc = ""] + #[doc = " Note: This is similar to `futimens` in POSIX."] + #[doc = ""] + #[doc = " Note: This was called `fd_filestat_set_times` in earlier versions of WASI."] + fn set_times( + &self, + data_access_timestamp: NewTimestamp, + data_modification_timestamp: NewTimestamp, + ) -> Result<(), ErrorCode> { + let data_access_timestamp = new_timestamp_map_in(data_access_timestamp); + let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); + + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::SetTimes(latch::DescriptorSetTimesArgs { + data_access_timestamp, + data_modification_timestamp, + }), + ))) { + Allow => self + .fd + .set_times(data_access_timestamp, data_modification_timestamp) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self:?} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Read from a descriptor, without using and updating the descriptor\'s offset."] + #[doc = ""] + #[doc = " This function returns a list of bytes containing the data that was"] + #[doc = " read, along with a bool which, when true, indicates that the end of the"] + #[doc = " file was reached. The returned list will contain up to `length` bytes; it"] + #[doc = " may return fewer than requested, if the end of the file is reached or"] + #[doc = " if the I/O operation is interrupted."] + #[doc = ""] + #[doc = " In the future, this may change to return a `stream`."] + #[doc = ""] + #[doc = " Note: This is similar to `pread` in POSIX."] + fn read(&self, length: Filesize, offset: Filesize) -> Result<(Vec, bool), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::Read(latch::DescriptorReadArgs { length, offset }), + ))) { + Allow => self.fd.read(length, offset).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read FD={self:?} LENGTH={length} OFFSET={offset}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Write to a descriptor, without using and updating the descriptor\'s offset."] + #[doc = ""] + #[doc = " It is valid to write past the end of a file; the file is extended to the"] + #[doc = " extent of the write, with bytes between the previous end and the start of"] + #[doc = " the write set to zero."] + #[doc = ""] + #[doc = " In the future, this may change to take a `stream`."] + #[doc = ""] + #[doc = " Note: This is similar to `pwrite` in POSIX."] + fn write(&self, buffer: Vec, offset: Filesize) -> Result { + let buffer_length: u64 = buffer + .len() + .try_into() + .expect("buffer length 64-bits or less"); + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::Write(latch::DescriptorWriteArgs { + buffer_length, + offset, + }), + ))) { + Allow => self.fd.write(&buffer, offset).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write FD={self:?} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Read directory entries from a directory."] + #[doc = ""] + #[doc = " On filesystems where directories contain entries referring to themselves"] + #[doc = " and their parents, often named `.` and `..` respectively, these entries"] + #[doc = " are omitted."] + #[doc = ""] + #[doc = " This always returns a new stream which starts at the beginning of the"] + #[doc = " directory. Multiple streams may be active on the same directory, and they"] + #[doc = " do not interfere with each other."] + fn read_directory(&self) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::ReadDirectory, + ))) { + Allow => self + .fd + .read_directory() + .map(directory_entry_stream_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Synchronize the data and metadata of a file to disk."] + #[doc = ""] + #[doc = " This function succeeds with no effect if the file descriptor is not"] + #[doc = " opened for writing."] + #[doc = ""] + #[doc = " Note: This is similar to `fsync` in POSIX."] + fn sync(&self) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::Sync, + ))) { + Allow => self.fd.sync().map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Create a directory."] + #[doc = ""] + #[doc = " Note: This is similar to `mkdirat` in POSIX."] + fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::CreateDirectoryAt(latch::DescriptorCreateDirectoryAtArgs { + path: path.clone(), + }), + ))) { + Allow => self.fd.create_directory_at(&path).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self:?} PATH={path}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Return the attributes of an open file or directory."] + #[doc = ""] + #[doc = " Note: This is similar to `fstat` in POSIX, except that it does not return"] + #[doc = " device and inode information. For testing whether two descriptors refer to"] + #[doc = " the same underlying filesystem object, use `is-same-object`. To obtain"] + #[doc = " additional data that can be used do determine whether a file has been"] + #[doc = " modified, use `metadata-hash`."] + #[doc = ""] + #[doc = " Note: This was called `fd_filestat_get` in earlier versions of WASI."] + fn stat(&self) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::Stat, + ))) { + Allow => self + .fd + .stat() + .map(descriptor_stat_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Return the attributes of a file or directory."] + #[doc = ""] + #[doc = " Note: This is similar to `fstatat` in POSIX, except that it does not"] + #[doc = " return device and inode information. See the `stat` description for a"] + #[doc = " discussion of alternatives."] + #[doc = ""] + #[doc = " Note: This was called `path_filestat_get` in earlier versions of WASI."] + fn stat_at(&self, path_flags: PathFlags, path: String) -> Result { + let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); + + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::StatAt(latch::DescriptorStatAtArgs { + path_flags, + path: path.clone(), + }), + ))) { + Allow => self + .fd + .stat_at(path_flags, &path) + .map(descriptor_stat_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self:?} PATH={path}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Adjust the timestamps of a file or directory."] + #[doc = ""] + #[doc = " Note: This is similar to `utimensat` in POSIX."] + #[doc = ""] + #[doc = " Note: This was called `path_filestat_set_times` in earlier versions of"] + #[doc = " WASI."] + fn set_times_at( + &self, + path_flags: PathFlags, + path: String, + data_access_timestamp: NewTimestamp, + data_modification_timestamp: NewTimestamp, + ) -> Result<(), ErrorCode> { + let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); + let data_access_timestamp = new_timestamp_map_in(data_access_timestamp); + let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::SetTimesAt(latch::DescriptorSetTimesAtArgs { + path_flags, + path: path.clone(), + data_access_timestamp, + data_modification_timestamp, + }), + ))) { + Allow => self + .fd + .set_times_at( + path_flags, + &path, + data_access_timestamp, + data_modification_timestamp, + ) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self:?} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Create a hard link."] + #[doc = ""] + #[doc = " Note: This is similar to `linkat` in POSIX."] + fn link_at( + &self, + old_path_flags: PathFlags, + old_path: String, + new_descriptor: DescriptorBorrow<'_>, + new_path: String, + ) -> Result<(), ErrorCode> { + let old_path_flags = types::PathFlags::from_bits(old_path_flags.bits()).unwrap(); + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::LinkAt(latch::DescriptorLinkAtArgs { + old_path_flags, + old_path: old_path.clone(), + new_descriptor: &new_descriptor.get::().fd, + new_path: new_path.clone(), + }), + ))) { + Allow => self + .fd + .link_at( + old_path_flags, + &old_path, + &new_descriptor.get::().fd, + &new_path, + ) + .map_err(error_code_map), + Deny(code) => { + warn!( + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self:?} OLD-PATH={old_path} NEW-PATH={new_path}", + ); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Open a file or directory."] + #[doc = ""] + #[doc = " The returned descriptor is not guaranteed to be the lowest-numbered"] + #[doc = " descriptor not currently open/ it is randomized to prevent applications"] + #[doc = " from depending on making assumptions about indexes, since this is"] + #[doc = " error-prone in multi-threaded contexts. The returned descriptor is"] + #[doc = " guaranteed to be less than 2**31."] + #[doc = ""] + #[doc = " If `flags` contains `descriptor-flags::mutate-directory`, and the base"] + #[doc = " descriptor doesn\'t have `descriptor-flags::mutate-directory` set,"] + #[doc = " `open-at` fails with `error-code::read-only`."] + #[doc = ""] + #[doc = " If `flags` contains `write` or `mutate-directory`, or `open-flags`"] + #[doc = " contains `truncate` or `create`, and the base descriptor doesn\'t have"] + #[doc = " `descriptor-flags::mutate-directory` set, `open-at` fails with"] + #[doc = " `error-code::read-only`."] + #[doc = ""] + #[doc = " Note: This is similar to `openat` in POSIX."] + fn open_at( + &self, + path_flags: PathFlags, + path: String, + open_flags: OpenFlags, + flags: DescriptorFlags, + ) -> Result { + let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); + let open_flags = types::OpenFlags::from_bits(open_flags.bits()).unwrap(); + let flags = types::DescriptorFlags::from_bits(flags.bits()).unwrap(); + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::OpenAt(latch::DescriptorOpenAtArgs { + path_flags, + path: path.clone(), + open_flags, + flags, + }), + ))) { + Allow => self + .fd + .open_at(path_flags, &path, open_flags, flags) + .map(descriptor_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self:?} PATH={path}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Read the contents of a symbolic link."] + #[doc = ""] + #[doc = " If the contents contain an absolute or rooted path in the underlying"] + #[doc = " filesystem, this function fails with `error-code::not-permitted`."] + #[doc = ""] + #[doc = " Note: This is similar to `readlinkat` in POSIX."] + fn readlink_at(&self, path: String) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::ReadlinkAt(latch::DescriptorReadlinkAtArgs { path: path.clone() }), + ))) { + Allow => self.fd.readlink_at(&path).map_err(error_code_map), + Deny(code) => { + warn!( + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self:?} PATH={path}", + ); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Remove a directory."] + #[doc = ""] + #[doc = " Return `error-code::not-empty` if the directory is not empty."] + #[doc = ""] + #[doc = " Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX."] + fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::RemoveDirectoryAt(latch::DescriptorRemoveDirectoryAtArgs { + path: path.clone(), + }), + ))) { + Allow => self.fd.remove_directory_at(&path).map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self:?} PATH={path}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Rename a filesystem object."] + #[doc = ""] + #[doc = " Note: This is similar to `renameat` in POSIX."] + fn rename_at( + &self, + old_path: String, + new_descriptor: DescriptorBorrow<'_>, + new_path: String, + ) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::RenameAt(latch::DescriptorRenameAtArgs { + old_path: old_path.clone(), + new_descriptor: &new_descriptor.get::().fd, + new_path: new_path.clone(), + }), + ))) { + Allow => { + let new_descriptor: &Self = new_descriptor.get(); + self.fd + .rename_at(&old_path, &new_descriptor.fd, &new_path) + .map_err(error_code_map) + } + Deny(code) => { + warn!( + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self:?} OLD-PATH={old_path} NEW-PATH={new_path}", + ); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Create a symbolic link (also known as a \"symlink\")."] + #[doc = ""] + #[doc = " If `old-path` starts with `/`, the function fails with"] + #[doc = " `error-code::not-permitted`."] + #[doc = ""] + #[doc = " Note: This is similar to `symlinkat` in POSIX."] + fn symlink_at(&self, old_path: String, new_path: String) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::SymlinkAt(latch::DescriptorSymlinkAtArgs { + old_path: old_path.clone(), + new_path: new_path.clone(), + }), + ))) { + Allow => self + .fd + .symlink_at(&old_path, &new_path) + .map_err(error_code_map), + Deny(code) => { + warn!( + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self:?} OLD-PATH={old_path} NEW-PATH={new_path}", + ); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Unlink a filesystem object that is not a directory."] + #[doc = ""] + #[doc = " Return `error-code::is-directory` if the path refers to a directory."] + #[doc = " Note: This is similar to `unlinkat(fd, path, 0)` in POSIX."] + fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::UnlinkFileAt(latch::DescriptorUnlinkFileAtArgs { + path: path.clone(), + }), + ))) { + Allow => self.fd.unlink_file_at(&path).map_err(error_code_map), + Deny(code) => { + warn!( + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self:?} PATH={path}", + ); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Test whether two descriptors refer to the same filesystem object."] + #[doc = ""] + #[doc = " In POSIX, this corresponds to testing whether the two descriptors have the"] + #[doc = " same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers."] + #[doc = " wasi-filesystem does not expose device and inode numbers, so this function"] + #[doc = " may be used instead."] + fn is_same_object(&self, other: DescriptorBorrow<'_>) -> bool { + let other: &Self = other.get(); + self.fd.is_same_object(&other.fd) + } + + #[doc = " Return a hash of the metadata associated with a filesystem object referred"] + #[doc = " to by a descriptor."] + #[doc = ""] + #[doc = " This returns a hash of the last-modification timestamp and file size, and"] + #[doc = " may also include the inode number, device number, birth timestamp, and"] + #[doc = " other metadata fields that may change when the file is modified or"] + #[doc = " replaced. It may also include a secret value chosen by the"] + #[doc = " implementation and not otherwise exposed."] + #[doc = ""] + #[doc = " Implementations are encourated to provide the following properties:"] + #[doc = ""] + #[doc = " - If the file is not modified or replaced, the computed hash value should"] + #[doc = " usually not change."] + #[doc = " - If the object is modified or replaced, the computed hash value should"] + #[doc = " usually change."] + #[doc = " - The inputs to the hash should not be easily computable from the"] + #[doc = " computed hash."] + #[doc = ""] + #[doc = " However, none of these is required."] + fn metadata_hash(&self) -> Result { + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::MetadataHash, + ))) { + Allow => self + .fd + .metadata_hash() + .map(metadata_hash_value_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self:?}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } + + #[doc = " Return a hash of the metadata associated with a filesystem object referred"] + #[doc = " to by a directory descriptor and a relative path."] + #[doc = ""] + #[doc = " This performs the same hash computation as `metadata-hash`."] + fn metadata_hash_at( + &self, + path_flags: PathFlags, + path: String, + ) -> Result { + let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); + match check(&Operation::Descriptor(( + &self.fd, + DescriptorOperation::MetadataHashAt(latch::DescriptorMetadataHashAtArgs { + path_flags, + path: path.clone(), + }), + ))) { + Allow => self + .fd + .metadata_hash_at(path_flags, &path) + .map(metadata_hash_value_map) + .map_err(error_code_map), + Deny(code) => { + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self:?} PATH={path}"); + Err(error_code_map(code)) + } + Abstain => panic!("missing latch decision"), + } + } +} + +#[derive(Debug, Clone)] +struct GateDirectoryEntryStream { + des: Rc, +} + +impl GateDirectoryEntryStream { + fn new(des: types::DirectoryEntryStream) -> Self { + Self { des: Rc::new(des) } + } +} + +impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GateDirectoryEntryStream { + #[doc = " Read a single directory entry from a `directory-entry-stream`."] + fn read_directory_entry(&self) -> Result, ErrorCode> { + self.des + .read_directory_entry() + .map(|de| de.map(directory_entry_map)) + .map_err(error_code_map) + } +} + +fn advice_map_in(advice: Advice) -> types::Advice { + match advice { + Advice::Normal => types::Advice::Normal, + Advice::Sequential => types::Advice::Sequential, + Advice::Random => types::Advice::Random, + Advice::WillNeed => types::Advice::WillNeed, + Advice::DontNeed => types::Advice::DontNeed, + Advice::NoReuse => types::Advice::NoReuse, + } +} + +fn descriptor_map(descriptor: types::Descriptor) -> Descriptor { + Descriptor::new(GateDescriptor::new(descriptor)) +} + +fn descriptor_flags_map(descriptor_flags: types::DescriptorFlags) -> DescriptorFlags { + DescriptorFlags::from_bits(descriptor_flags.bits()).unwrap() +} + +fn descriptor_stat_map(descriptor_stat: types::DescriptorStat) -> DescriptorStat { + DescriptorStat { + type_: descriptor_type_map(descriptor_stat.type_), + link_count: descriptor_stat.link_count, + size: descriptor_stat.size, + data_access_timestamp: descriptor_stat.data_access_timestamp, + data_modification_timestamp: descriptor_stat.data_modification_timestamp, + status_change_timestamp: descriptor_stat.status_change_timestamp, + } +} + +fn descriptor_type_map(descriptor_type: types::DescriptorType) -> DescriptorType { + match descriptor_type { + types::DescriptorType::Unknown => DescriptorType::Unknown, + types::DescriptorType::BlockDevice => DescriptorType::BlockDevice, + types::DescriptorType::CharacterDevice => DescriptorType::CharacterDevice, + types::DescriptorType::Directory => DescriptorType::Directory, + types::DescriptorType::Fifo => DescriptorType::Fifo, + types::DescriptorType::SymbolicLink => DescriptorType::SymbolicLink, + types::DescriptorType::RegularFile => DescriptorType::RegularFile, + types::DescriptorType::Socket => DescriptorType::Socket, + } +} + +fn directory_entry_map(directory_entry: types::DirectoryEntry) -> DirectoryEntry { + DirectoryEntry { + name: directory_entry.name, + type_: descriptor_type_map(directory_entry.type_), + } +} + +fn directory_entry_stream_map( + directory_entry_stream: types::DirectoryEntryStream, +) -> DirectoryEntryStream { + DirectoryEntryStream::new(GateDirectoryEntryStream::new(directory_entry_stream)) +} + +fn error_code_map(error_code: types::ErrorCode) -> ErrorCode { + match error_code { + types::ErrorCode::Access => ErrorCode::Access, + types::ErrorCode::WouldBlock => ErrorCode::WouldBlock, + types::ErrorCode::Already => ErrorCode::Already, + types::ErrorCode::BadDescriptor => ErrorCode::BadDescriptor, + types::ErrorCode::Busy => ErrorCode::Busy, + types::ErrorCode::Deadlock => ErrorCode::Deadlock, + types::ErrorCode::Quota => ErrorCode::Quota, + types::ErrorCode::Exist => ErrorCode::Exist, + types::ErrorCode::FileTooLarge => ErrorCode::FileTooLarge, + types::ErrorCode::IllegalByteSequence => ErrorCode::IllegalByteSequence, + types::ErrorCode::InProgress => ErrorCode::InProgress, + types::ErrorCode::Interrupted => ErrorCode::Interrupted, + types::ErrorCode::Invalid => ErrorCode::Invalid, + types::ErrorCode::Io => ErrorCode::Io, + types::ErrorCode::IsDirectory => ErrorCode::IsDirectory, + types::ErrorCode::Loop => ErrorCode::Loop, + types::ErrorCode::TooManyLinks => ErrorCode::TooManyLinks, + types::ErrorCode::MessageSize => ErrorCode::MessageSize, + types::ErrorCode::NameTooLong => ErrorCode::NameTooLong, + types::ErrorCode::NoDevice => ErrorCode::NoDevice, + types::ErrorCode::NoEntry => ErrorCode::NoEntry, + types::ErrorCode::NoLock => ErrorCode::NoLock, + types::ErrorCode::InsufficientMemory => ErrorCode::InsufficientMemory, + types::ErrorCode::InsufficientSpace => ErrorCode::InsufficientSpace, + types::ErrorCode::NotDirectory => ErrorCode::NotDirectory, + types::ErrorCode::NotEmpty => ErrorCode::NotEmpty, + types::ErrorCode::NotRecoverable => ErrorCode::NotRecoverable, + types::ErrorCode::Unsupported => ErrorCode::Unsupported, + types::ErrorCode::NoTty => ErrorCode::NoTty, + types::ErrorCode::NoSuchDevice => ErrorCode::NoSuchDevice, + types::ErrorCode::Overflow => ErrorCode::Overflow, + types::ErrorCode::NotPermitted => ErrorCode::NotPermitted, + types::ErrorCode::Pipe => ErrorCode::Pipe, + types::ErrorCode::ReadOnly => ErrorCode::ReadOnly, + types::ErrorCode::InvalidSeek => ErrorCode::InvalidSeek, + types::ErrorCode::TextFileBusy => ErrorCode::TextFileBusy, + types::ErrorCode::CrossDevice => ErrorCode::CrossDevice, + } +} + +fn metadata_hash_value_map(metadata_hash_value: types::MetadataHashValue) -> MetadataHashValue { + MetadataHashValue { + lower: metadata_hash_value.lower, + upper: metadata_hash_value.upper, + } +} + +fn new_timestamp_map_in(timestamp: NewTimestamp) -> types::NewTimestamp { + match timestamp { + NewTimestamp::NoChange => types::NewTimestamp::NoChange, + NewTimestamp::Now => types::NewTimestamp::Now, + NewTimestamp::Timestamp(dt) => types::NewTimestamp::Timestamp(dt), + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem", + generate_all +}); + +export!(FilesystemGate); diff --git a/components/latch-2/Cargo.toml b/components/latch-2/Cargo.toml new file mode 100644 index 0000000..6fc6748 --- /dev/null +++ b/components/latch-2/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "latch-2" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-2/README.md b/components/latch-2/README.md new file mode 100644 index 0000000..7d3ce33 --- /dev/null +++ b/components/latch-2/README.md @@ -0,0 +1,3 @@ +# `latch-2` + +Filesystem latch that aggregates two other filesystem latches. diff --git a/components/latch-2/src/lib.rs b/components/latch-2/src/lib.rs new file mode 100644 index 0000000..f0ce617 --- /dev/null +++ b/components/latch-2/src/lib.rs @@ -0,0 +1,273 @@ +#![no_main] + +use crate::{ + componentized::filesystem::{latch, latch1, latch2}, + exports::componentized::filesystem::latch::{ + Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, + DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, + DescriptorReadArgs, DescriptorReadViaStreamArgs, DescriptorReadlinkAtArgs, + DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, + DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, + DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, + DescriptorWriteViaStreamArgs, Guest as Latch, Operation, + }, +}; + +struct AggregateLatch {} + +impl Latch for AggregateLatch { + fn check(operation: Operation) -> Decision { + let operation = operation_map(operation); + let checks = vec![latch1::check, latch2::check]; + for check in checks { + match check(&operation) { + latch::Decision::Abstain => {} + latch::Decision::Allow => return Decision::Allow, + latch::Decision::Deny(error_code) => return Decision::Deny(error_code), + } + } + Decision::Abstain + } +} + +fn operation_map(operation: Operation) -> latch::Operation { + match operation { + Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( + (descriptor, descriptor_operation_map(descriptor_operation)), + ), + } +} + +fn descriptor_operation_map( + descriptor_operation: DescriptorOperation, +) -> latch::DescriptorOperation { + match descriptor_operation { + DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args) => { + latch::DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args_map( + descriptor_read_via_stream_args, + )) + } + DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args) => { + latch::DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args_map( + descriptor_write_via_stream_args, + )) + } + DescriptorOperation::AppendViaStream => latch::DescriptorOperation::AppendViaStream, + DescriptorOperation::Advise(descriptor_advise_args) => { + latch::DescriptorOperation::Advise(descriptor_advise_args_map(descriptor_advise_args)) + } + DescriptorOperation::SyncData => latch::DescriptorOperation::SyncData, + DescriptorOperation::GetFlags => latch::DescriptorOperation::GetFlags, + DescriptorOperation::GetType => latch::DescriptorOperation::GetType, + DescriptorOperation::SetSize(descriptor_set_size_args) => { + latch::DescriptorOperation::SetSize(descriptor_set_size_args_map( + descriptor_set_size_args, + )) + } + DescriptorOperation::SetTimes(descriptor_set_times_args) => { + latch::DescriptorOperation::SetTimes(descriptor_set_times_args_map( + descriptor_set_times_args, + )) + } + DescriptorOperation::Read(descriptor_read_args) => { + latch::DescriptorOperation::Read(descriptor_read_args_map(descriptor_read_args)) + } + DescriptorOperation::Write(descriptor_write_args) => { + latch::DescriptorOperation::Write(descriptor_write_args_map(descriptor_write_args)) + } + DescriptorOperation::ReadDirectory => latch::DescriptorOperation::ReadDirectory, + DescriptorOperation::Sync => latch::DescriptorOperation::Sync, + DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args) => { + latch::DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args_map( + descriptor_create_directory_at_args, + )) + } + DescriptorOperation::Stat => latch::DescriptorOperation::Stat, + DescriptorOperation::StatAt(descriptor_stat_at_args) => { + latch::DescriptorOperation::StatAt(descriptor_stat_at_args_map(descriptor_stat_at_args)) + } + DescriptorOperation::SetTimesAt(descriptor_set_times_at_args) => { + latch::DescriptorOperation::SetTimesAt(descriptor_set_times_at_args_map( + descriptor_set_times_at_args, + )) + } + DescriptorOperation::LinkAt(descriptor_link_at_args) => { + latch::DescriptorOperation::LinkAt(descriptor_link_at_args_map(descriptor_link_at_args)) + } + DescriptorOperation::OpenAt(descriptor_open_at_args) => { + latch::DescriptorOperation::OpenAt(descriptor_open_at_args_map(descriptor_open_at_args)) + } + DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args) => { + latch::DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args_map( + descriptor_readlink_at_args, + )) + } + DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args) => { + latch::DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args_map( + descriptor_remove_directory_at_args, + )) + } + DescriptorOperation::RenameAt(descriptor_rename_at_args) => { + latch::DescriptorOperation::RenameAt(descriptor_rename_at_args_map( + descriptor_rename_at_args, + )) + } + DescriptorOperation::SymlinkAt(descriptor_symlink_at_args) => { + latch::DescriptorOperation::SymlinkAt(descriptor_symlink_at_args_map( + descriptor_symlink_at_args, + )) + } + DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args) => { + latch::DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args_map( + descriptor_unlink_file_at_args, + )) + } + DescriptorOperation::MetadataHash => latch::DescriptorOperation::MetadataHash, + DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args) => { + latch::DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args_map( + descriptor_metadata_hash_at_args, + )) + } + } +} + +fn descriptor_read_via_stream_args_map( + args: DescriptorReadViaStreamArgs, +) -> latch::DescriptorReadViaStreamArgs { + latch::DescriptorReadViaStreamArgs { + offset: args.offset, + } +} + +fn descriptor_write_via_stream_args_map( + args: DescriptorWriteViaStreamArgs, +) -> latch::DescriptorWriteViaStreamArgs { + latch::DescriptorWriteViaStreamArgs { + offset: args.offset, + } +} + +fn descriptor_advise_args_map(args: DescriptorAdviseArgs) -> latch::DescriptorAdviseArgs { + latch::DescriptorAdviseArgs { + offset: args.offset, + length: args.length, + advice: args.advice, + } +} + +fn descriptor_set_size_args_map(args: DescriptorSetSizeArgs) -> latch::DescriptorSetSizeArgs { + latch::DescriptorSetSizeArgs { size: args.size } +} + +fn descriptor_set_times_args_map(args: DescriptorSetTimesArgs) -> latch::DescriptorSetTimesArgs { + latch::DescriptorSetTimesArgs { + data_access_timestamp: args.data_access_timestamp, + data_modification_timestamp: args.data_modification_timestamp, + } +} + +fn descriptor_read_args_map(args: DescriptorReadArgs) -> latch::DescriptorReadArgs { + latch::DescriptorReadArgs { + length: args.length, + offset: args.offset, + } +} + +fn descriptor_write_args_map(args: DescriptorWriteArgs) -> latch::DescriptorWriteArgs { + latch::DescriptorWriteArgs { + buffer_length: args.buffer_length, + offset: args.offset, + } +} + +fn descriptor_create_directory_at_args_map( + args: DescriptorCreateDirectoryAtArgs, +) -> latch::DescriptorCreateDirectoryAtArgs { + latch::DescriptorCreateDirectoryAtArgs { path: args.path } +} + +fn descriptor_stat_at_args_map(args: DescriptorStatAtArgs) -> latch::DescriptorStatAtArgs { + latch::DescriptorStatAtArgs { + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_set_times_at_args_map( + args: DescriptorSetTimesAtArgs, +) -> latch::DescriptorSetTimesAtArgs { + latch::DescriptorSetTimesAtArgs { + data_access_timestamp: args.data_access_timestamp, + data_modification_timestamp: args.data_modification_timestamp, + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_link_at_args_map(args: DescriptorLinkAtArgs) -> latch::DescriptorLinkAtArgs { + latch::DescriptorLinkAtArgs { + new_descriptor: args.new_descriptor, + new_path: args.new_path, + old_path: args.old_path, + old_path_flags: args.old_path_flags, + } +} + +fn descriptor_open_at_args_map(args: DescriptorOpenAtArgs) -> latch::DescriptorOpenAtArgs { + latch::DescriptorOpenAtArgs { + flags: args.flags, + open_flags: args.open_flags, + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_readlink_at_args_map( + args: DescriptorReadlinkAtArgs, +) -> latch::DescriptorReadlinkAtArgs { + latch::DescriptorReadlinkAtArgs { path: args.path } +} + +fn descriptor_remove_directory_at_args_map( + args: DescriptorRemoveDirectoryAtArgs, +) -> latch::DescriptorRemoveDirectoryAtArgs { + latch::DescriptorRemoveDirectoryAtArgs { path: args.path } +} + +fn descriptor_rename_at_args_map(args: DescriptorRenameAtArgs) -> latch::DescriptorRenameAtArgs { + latch::DescriptorRenameAtArgs { + new_descriptor: args.new_descriptor, + new_path: args.new_path, + old_path: args.old_path, + } +} + +fn descriptor_symlink_at_args_map(args: DescriptorSymlinkAtArgs) -> latch::DescriptorSymlinkAtArgs { + latch::DescriptorSymlinkAtArgs { + new_path: args.new_path, + old_path: args.old_path, + } +} + +fn descriptor_unlink_file_at_args_map( + args: DescriptorUnlinkFileAtArgs, +) -> latch::DescriptorUnlinkFileAtArgs { + latch::DescriptorUnlinkFileAtArgs { path: args.path } +} + +fn descriptor_metadata_hash_at_args_map( + args: DescriptorMetadataHashAtArgs, +) -> latch::DescriptorMetadataHashAtArgs { + latch::DescriptorMetadataHashAtArgs { + path: args.path, + path_flags: args.path_flags, + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch2", + generate_all +}); + +export!(AggregateLatch); diff --git a/components/latch-3/Cargo.toml b/components/latch-3/Cargo.toml new file mode 100644 index 0000000..a348e23 --- /dev/null +++ b/components/latch-3/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "latch-3" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-3/README.md b/components/latch-3/README.md new file mode 100644 index 0000000..0438bd8 --- /dev/null +++ b/components/latch-3/README.md @@ -0,0 +1,3 @@ +# `latch-3` + +Filesystem latch that aggregates three other filesystem latches. diff --git a/components/latch-3/src/lib.rs b/components/latch-3/src/lib.rs new file mode 100644 index 0000000..7127cf5 --- /dev/null +++ b/components/latch-3/src/lib.rs @@ -0,0 +1,273 @@ +#![no_main] + +use crate::{ + componentized::filesystem::{latch, latch1, latch2, latch3}, + exports::componentized::filesystem::latch::{ + Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, + DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, + DescriptorReadArgs, DescriptorReadViaStreamArgs, DescriptorReadlinkAtArgs, + DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, + DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, + DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, + DescriptorWriteViaStreamArgs, Guest as Latch, Operation, + }, +}; + +struct AggregateLatch {} + +impl Latch for AggregateLatch { + fn check(operation: Operation) -> Decision { + let operation = operation_map(operation); + let checks = vec![latch1::check, latch2::check, latch3::check]; + for check in checks { + match check(&operation) { + latch::Decision::Abstain => {} + latch::Decision::Allow => return Decision::Allow, + latch::Decision::Deny(error_code) => return Decision::Deny(error_code), + } + } + Decision::Abstain + } +} + +fn operation_map(operation: Operation) -> latch::Operation { + match operation { + Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( + (descriptor, descriptor_operation_map(descriptor_operation)), + ), + } +} + +fn descriptor_operation_map( + descriptor_operation: DescriptorOperation, +) -> latch::DescriptorOperation { + match descriptor_operation { + DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args) => { + latch::DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args_map( + descriptor_read_via_stream_args, + )) + } + DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args) => { + latch::DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args_map( + descriptor_write_via_stream_args, + )) + } + DescriptorOperation::AppendViaStream => latch::DescriptorOperation::AppendViaStream, + DescriptorOperation::Advise(descriptor_advise_args) => { + latch::DescriptorOperation::Advise(descriptor_advise_args_map(descriptor_advise_args)) + } + DescriptorOperation::SyncData => latch::DescriptorOperation::SyncData, + DescriptorOperation::GetFlags => latch::DescriptorOperation::GetFlags, + DescriptorOperation::GetType => latch::DescriptorOperation::GetType, + DescriptorOperation::SetSize(descriptor_set_size_args) => { + latch::DescriptorOperation::SetSize(descriptor_set_size_args_map( + descriptor_set_size_args, + )) + } + DescriptorOperation::SetTimes(descriptor_set_times_args) => { + latch::DescriptorOperation::SetTimes(descriptor_set_times_args_map( + descriptor_set_times_args, + )) + } + DescriptorOperation::Read(descriptor_read_args) => { + latch::DescriptorOperation::Read(descriptor_read_args_map(descriptor_read_args)) + } + DescriptorOperation::Write(descriptor_write_args) => { + latch::DescriptorOperation::Write(descriptor_write_args_map(descriptor_write_args)) + } + DescriptorOperation::ReadDirectory => latch::DescriptorOperation::ReadDirectory, + DescriptorOperation::Sync => latch::DescriptorOperation::Sync, + DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args) => { + latch::DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args_map( + descriptor_create_directory_at_args, + )) + } + DescriptorOperation::Stat => latch::DescriptorOperation::Stat, + DescriptorOperation::StatAt(descriptor_stat_at_args) => { + latch::DescriptorOperation::StatAt(descriptor_stat_at_args_map(descriptor_stat_at_args)) + } + DescriptorOperation::SetTimesAt(descriptor_set_times_at_args) => { + latch::DescriptorOperation::SetTimesAt(descriptor_set_times_at_args_map( + descriptor_set_times_at_args, + )) + } + DescriptorOperation::LinkAt(descriptor_link_at_args) => { + latch::DescriptorOperation::LinkAt(descriptor_link_at_args_map(descriptor_link_at_args)) + } + DescriptorOperation::OpenAt(descriptor_open_at_args) => { + latch::DescriptorOperation::OpenAt(descriptor_open_at_args_map(descriptor_open_at_args)) + } + DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args) => { + latch::DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args_map( + descriptor_readlink_at_args, + )) + } + DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args) => { + latch::DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args_map( + descriptor_remove_directory_at_args, + )) + } + DescriptorOperation::RenameAt(descriptor_rename_at_args) => { + latch::DescriptorOperation::RenameAt(descriptor_rename_at_args_map( + descriptor_rename_at_args, + )) + } + DescriptorOperation::SymlinkAt(descriptor_symlink_at_args) => { + latch::DescriptorOperation::SymlinkAt(descriptor_symlink_at_args_map( + descriptor_symlink_at_args, + )) + } + DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args) => { + latch::DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args_map( + descriptor_unlink_file_at_args, + )) + } + DescriptorOperation::MetadataHash => latch::DescriptorOperation::MetadataHash, + DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args) => { + latch::DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args_map( + descriptor_metadata_hash_at_args, + )) + } + } +} + +fn descriptor_read_via_stream_args_map( + args: DescriptorReadViaStreamArgs, +) -> latch::DescriptorReadViaStreamArgs { + latch::DescriptorReadViaStreamArgs { + offset: args.offset, + } +} + +fn descriptor_write_via_stream_args_map( + args: DescriptorWriteViaStreamArgs, +) -> latch::DescriptorWriteViaStreamArgs { + latch::DescriptorWriteViaStreamArgs { + offset: args.offset, + } +} + +fn descriptor_advise_args_map(args: DescriptorAdviseArgs) -> latch::DescriptorAdviseArgs { + latch::DescriptorAdviseArgs { + offset: args.offset, + length: args.length, + advice: args.advice, + } +} + +fn descriptor_set_size_args_map(args: DescriptorSetSizeArgs) -> latch::DescriptorSetSizeArgs { + latch::DescriptorSetSizeArgs { size: args.size } +} + +fn descriptor_set_times_args_map(args: DescriptorSetTimesArgs) -> latch::DescriptorSetTimesArgs { + latch::DescriptorSetTimesArgs { + data_access_timestamp: args.data_access_timestamp, + data_modification_timestamp: args.data_modification_timestamp, + } +} + +fn descriptor_read_args_map(args: DescriptorReadArgs) -> latch::DescriptorReadArgs { + latch::DescriptorReadArgs { + length: args.length, + offset: args.offset, + } +} + +fn descriptor_write_args_map(args: DescriptorWriteArgs) -> latch::DescriptorWriteArgs { + latch::DescriptorWriteArgs { + buffer_length: args.buffer_length, + offset: args.offset, + } +} + +fn descriptor_create_directory_at_args_map( + args: DescriptorCreateDirectoryAtArgs, +) -> latch::DescriptorCreateDirectoryAtArgs { + latch::DescriptorCreateDirectoryAtArgs { path: args.path } +} + +fn descriptor_stat_at_args_map(args: DescriptorStatAtArgs) -> latch::DescriptorStatAtArgs { + latch::DescriptorStatAtArgs { + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_set_times_at_args_map( + args: DescriptorSetTimesAtArgs, +) -> latch::DescriptorSetTimesAtArgs { + latch::DescriptorSetTimesAtArgs { + data_access_timestamp: args.data_access_timestamp, + data_modification_timestamp: args.data_modification_timestamp, + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_link_at_args_map(args: DescriptorLinkAtArgs) -> latch::DescriptorLinkAtArgs { + latch::DescriptorLinkAtArgs { + new_descriptor: args.new_descriptor, + new_path: args.new_path, + old_path: args.old_path, + old_path_flags: args.old_path_flags, + } +} + +fn descriptor_open_at_args_map(args: DescriptorOpenAtArgs) -> latch::DescriptorOpenAtArgs { + latch::DescriptorOpenAtArgs { + flags: args.flags, + open_flags: args.open_flags, + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_readlink_at_args_map( + args: DescriptorReadlinkAtArgs, +) -> latch::DescriptorReadlinkAtArgs { + latch::DescriptorReadlinkAtArgs { path: args.path } +} + +fn descriptor_remove_directory_at_args_map( + args: DescriptorRemoveDirectoryAtArgs, +) -> latch::DescriptorRemoveDirectoryAtArgs { + latch::DescriptorRemoveDirectoryAtArgs { path: args.path } +} + +fn descriptor_rename_at_args_map(args: DescriptorRenameAtArgs) -> latch::DescriptorRenameAtArgs { + latch::DescriptorRenameAtArgs { + new_descriptor: args.new_descriptor, + new_path: args.new_path, + old_path: args.old_path, + } +} + +fn descriptor_symlink_at_args_map(args: DescriptorSymlinkAtArgs) -> latch::DescriptorSymlinkAtArgs { + latch::DescriptorSymlinkAtArgs { + new_path: args.new_path, + old_path: args.old_path, + } +} + +fn descriptor_unlink_file_at_args_map( + args: DescriptorUnlinkFileAtArgs, +) -> latch::DescriptorUnlinkFileAtArgs { + latch::DescriptorUnlinkFileAtArgs { path: args.path } +} + +fn descriptor_metadata_hash_at_args_map( + args: DescriptorMetadataHashAtArgs, +) -> latch::DescriptorMetadataHashAtArgs { + latch::DescriptorMetadataHashAtArgs { + path: args.path, + path_flags: args.path_flags, + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch3", + generate_all +}); + +export!(AggregateLatch); diff --git a/components/latch-4/Cargo.toml b/components/latch-4/Cargo.toml new file mode 100644 index 0000000..00f20b4 --- /dev/null +++ b/components/latch-4/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "latch-4" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-4/README.md b/components/latch-4/README.md new file mode 100644 index 0000000..dd8d6f6 --- /dev/null +++ b/components/latch-4/README.md @@ -0,0 +1,3 @@ +# `latch-4` + +Filesystem latch that aggregates four other filesystem latches. diff --git a/components/latch-4/src/lib.rs b/components/latch-4/src/lib.rs new file mode 100644 index 0000000..1270f59 --- /dev/null +++ b/components/latch-4/src/lib.rs @@ -0,0 +1,273 @@ +#![no_main] + +use crate::{ + componentized::filesystem::{latch, latch1, latch2, latch3, latch4}, + exports::componentized::filesystem::latch::{ + Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, + DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, + DescriptorReadArgs, DescriptorReadViaStreamArgs, DescriptorReadlinkAtArgs, + DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, + DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, + DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, + DescriptorWriteViaStreamArgs, Guest as Latch, Operation, + }, +}; + +struct AggregateLatch {} + +impl Latch for AggregateLatch { + fn check(operation: Operation) -> Decision { + let operation = operation_map(operation); + let checks = vec![latch1::check, latch2::check, latch3::check, latch4::check]; + for check in checks { + match check(&operation) { + latch::Decision::Abstain => {} + latch::Decision::Allow => return Decision::Allow, + latch::Decision::Deny(error_code) => return Decision::Deny(error_code), + } + } + Decision::Abstain + } +} + +fn operation_map(operation: Operation) -> latch::Operation { + match operation { + Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( + (descriptor, descriptor_operation_map(descriptor_operation)), + ), + } +} + +fn descriptor_operation_map( + descriptor_operation: DescriptorOperation, +) -> latch::DescriptorOperation { + match descriptor_operation { + DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args) => { + latch::DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args_map( + descriptor_read_via_stream_args, + )) + } + DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args) => { + latch::DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args_map( + descriptor_write_via_stream_args, + )) + } + DescriptorOperation::AppendViaStream => latch::DescriptorOperation::AppendViaStream, + DescriptorOperation::Advise(descriptor_advise_args) => { + latch::DescriptorOperation::Advise(descriptor_advise_args_map(descriptor_advise_args)) + } + DescriptorOperation::SyncData => latch::DescriptorOperation::SyncData, + DescriptorOperation::GetFlags => latch::DescriptorOperation::GetFlags, + DescriptorOperation::GetType => latch::DescriptorOperation::GetType, + DescriptorOperation::SetSize(descriptor_set_size_args) => { + latch::DescriptorOperation::SetSize(descriptor_set_size_args_map( + descriptor_set_size_args, + )) + } + DescriptorOperation::SetTimes(descriptor_set_times_args) => { + latch::DescriptorOperation::SetTimes(descriptor_set_times_args_map( + descriptor_set_times_args, + )) + } + DescriptorOperation::Read(descriptor_read_args) => { + latch::DescriptorOperation::Read(descriptor_read_args_map(descriptor_read_args)) + } + DescriptorOperation::Write(descriptor_write_args) => { + latch::DescriptorOperation::Write(descriptor_write_args_map(descriptor_write_args)) + } + DescriptorOperation::ReadDirectory => latch::DescriptorOperation::ReadDirectory, + DescriptorOperation::Sync => latch::DescriptorOperation::Sync, + DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args) => { + latch::DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args_map( + descriptor_create_directory_at_args, + )) + } + DescriptorOperation::Stat => latch::DescriptorOperation::Stat, + DescriptorOperation::StatAt(descriptor_stat_at_args) => { + latch::DescriptorOperation::StatAt(descriptor_stat_at_args_map(descriptor_stat_at_args)) + } + DescriptorOperation::SetTimesAt(descriptor_set_times_at_args) => { + latch::DescriptorOperation::SetTimesAt(descriptor_set_times_at_args_map( + descriptor_set_times_at_args, + )) + } + DescriptorOperation::LinkAt(descriptor_link_at_args) => { + latch::DescriptorOperation::LinkAt(descriptor_link_at_args_map(descriptor_link_at_args)) + } + DescriptorOperation::OpenAt(descriptor_open_at_args) => { + latch::DescriptorOperation::OpenAt(descriptor_open_at_args_map(descriptor_open_at_args)) + } + DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args) => { + latch::DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args_map( + descriptor_readlink_at_args, + )) + } + DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args) => { + latch::DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args_map( + descriptor_remove_directory_at_args, + )) + } + DescriptorOperation::RenameAt(descriptor_rename_at_args) => { + latch::DescriptorOperation::RenameAt(descriptor_rename_at_args_map( + descriptor_rename_at_args, + )) + } + DescriptorOperation::SymlinkAt(descriptor_symlink_at_args) => { + latch::DescriptorOperation::SymlinkAt(descriptor_symlink_at_args_map( + descriptor_symlink_at_args, + )) + } + DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args) => { + latch::DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args_map( + descriptor_unlink_file_at_args, + )) + } + DescriptorOperation::MetadataHash => latch::DescriptorOperation::MetadataHash, + DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args) => { + latch::DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args_map( + descriptor_metadata_hash_at_args, + )) + } + } +} + +fn descriptor_read_via_stream_args_map( + args: DescriptorReadViaStreamArgs, +) -> latch::DescriptorReadViaStreamArgs { + latch::DescriptorReadViaStreamArgs { + offset: args.offset, + } +} + +fn descriptor_write_via_stream_args_map( + args: DescriptorWriteViaStreamArgs, +) -> latch::DescriptorWriteViaStreamArgs { + latch::DescriptorWriteViaStreamArgs { + offset: args.offset, + } +} + +fn descriptor_advise_args_map(args: DescriptorAdviseArgs) -> latch::DescriptorAdviseArgs { + latch::DescriptorAdviseArgs { + offset: args.offset, + length: args.length, + advice: args.advice, + } +} + +fn descriptor_set_size_args_map(args: DescriptorSetSizeArgs) -> latch::DescriptorSetSizeArgs { + latch::DescriptorSetSizeArgs { size: args.size } +} + +fn descriptor_set_times_args_map(args: DescriptorSetTimesArgs) -> latch::DescriptorSetTimesArgs { + latch::DescriptorSetTimesArgs { + data_access_timestamp: args.data_access_timestamp, + data_modification_timestamp: args.data_modification_timestamp, + } +} + +fn descriptor_read_args_map(args: DescriptorReadArgs) -> latch::DescriptorReadArgs { + latch::DescriptorReadArgs { + length: args.length, + offset: args.offset, + } +} + +fn descriptor_write_args_map(args: DescriptorWriteArgs) -> latch::DescriptorWriteArgs { + latch::DescriptorWriteArgs { + buffer_length: args.buffer_length, + offset: args.offset, + } +} + +fn descriptor_create_directory_at_args_map( + args: DescriptorCreateDirectoryAtArgs, +) -> latch::DescriptorCreateDirectoryAtArgs { + latch::DescriptorCreateDirectoryAtArgs { path: args.path } +} + +fn descriptor_stat_at_args_map(args: DescriptorStatAtArgs) -> latch::DescriptorStatAtArgs { + latch::DescriptorStatAtArgs { + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_set_times_at_args_map( + args: DescriptorSetTimesAtArgs, +) -> latch::DescriptorSetTimesAtArgs { + latch::DescriptorSetTimesAtArgs { + data_access_timestamp: args.data_access_timestamp, + data_modification_timestamp: args.data_modification_timestamp, + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_link_at_args_map(args: DescriptorLinkAtArgs) -> latch::DescriptorLinkAtArgs { + latch::DescriptorLinkAtArgs { + new_descriptor: args.new_descriptor, + new_path: args.new_path, + old_path: args.old_path, + old_path_flags: args.old_path_flags, + } +} + +fn descriptor_open_at_args_map(args: DescriptorOpenAtArgs) -> latch::DescriptorOpenAtArgs { + latch::DescriptorOpenAtArgs { + flags: args.flags, + open_flags: args.open_flags, + path: args.path, + path_flags: args.path_flags, + } +} + +fn descriptor_readlink_at_args_map( + args: DescriptorReadlinkAtArgs, +) -> latch::DescriptorReadlinkAtArgs { + latch::DescriptorReadlinkAtArgs { path: args.path } +} + +fn descriptor_remove_directory_at_args_map( + args: DescriptorRemoveDirectoryAtArgs, +) -> latch::DescriptorRemoveDirectoryAtArgs { + latch::DescriptorRemoveDirectoryAtArgs { path: args.path } +} + +fn descriptor_rename_at_args_map(args: DescriptorRenameAtArgs) -> latch::DescriptorRenameAtArgs { + latch::DescriptorRenameAtArgs { + new_descriptor: args.new_descriptor, + new_path: args.new_path, + old_path: args.old_path, + } +} + +fn descriptor_symlink_at_args_map(args: DescriptorSymlinkAtArgs) -> latch::DescriptorSymlinkAtArgs { + latch::DescriptorSymlinkAtArgs { + new_path: args.new_path, + old_path: args.old_path, + } +} + +fn descriptor_unlink_file_at_args_map( + args: DescriptorUnlinkFileAtArgs, +) -> latch::DescriptorUnlinkFileAtArgs { + latch::DescriptorUnlinkFileAtArgs { path: args.path } +} + +fn descriptor_metadata_hash_at_args_map( + args: DescriptorMetadataHashAtArgs, +) -> latch::DescriptorMetadataHashAtArgs { + latch::DescriptorMetadataHashAtArgs { + path: args.path, + path_flags: args.path_flags, + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch4", + generate_all +}); + +export!(AggregateLatch); diff --git a/components/latch-allow/Cargo.toml b/components/latch-allow/Cargo.toml new file mode 100644 index 0000000..8127f2a --- /dev/null +++ b/components/latch-allow/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "latch-allow" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-allow/README.md b/components/latch-allow/README.md new file mode 100644 index 0000000..08965e4 --- /dev/null +++ b/components/latch-allow/README.md @@ -0,0 +1,3 @@ +# `latch-allow` + +Filesystem latch that implicitly allows all operations. diff --git a/components/latch-allow/src/lib.rs b/components/latch-allow/src/lib.rs new file mode 100644 index 0000000..2d6b414 --- /dev/null +++ b/components/latch-allow/src/lib.rs @@ -0,0 +1,19 @@ +#![no_main] + +use crate::exports::componentized::filesystem::latch::{Decision, Guest as Latch, Operation}; + +struct AllowLatch {} + +impl Latch for AllowLatch { + fn check(_: Operation) -> Decision { + Decision::Allow + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch", + generate_all +}); + +export!(AllowLatch); diff --git a/components/latch-deny/Cargo.toml b/components/latch-deny/Cargo.toml new file mode 100644 index 0000000..d13196b --- /dev/null +++ b/components/latch-deny/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "latch-deny" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-deny/README.md b/components/latch-deny/README.md new file mode 100644 index 0000000..d46f427 --- /dev/null +++ b/components/latch-deny/README.md @@ -0,0 +1,3 @@ +# `latch-deny` + +Filesystem latch that implicitly denies all operations. diff --git a/components/latch-deny/src/lib.rs b/components/latch-deny/src/lib.rs new file mode 100644 index 0000000..05dd59a --- /dev/null +++ b/components/latch-deny/src/lib.rs @@ -0,0 +1,22 @@ +#![no_main] + +use crate::{ + exports::componentized::filesystem::latch::{Decision, Guest as Latch, Operation}, + wasi::filesystem::types::ErrorCode, +}; + +struct DenyLatch {} + +impl Latch for DenyLatch { + fn check(_: Operation) -> Decision { + Decision::Deny(ErrorCode::NotPermitted) + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch", + generate_all +}); + +export!(DenyLatch); diff --git a/components/latch-readonly/Cargo.toml b/components/latch-readonly/Cargo.toml new file mode 100644 index 0000000..8e3f98b --- /dev/null +++ b/components/latch-readonly/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "latch-readonly" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-readonly/README.md b/components/latch-readonly/README.md new file mode 100644 index 0000000..79e5e96 --- /dev/null +++ b/components/latch-readonly/README.md @@ -0,0 +1,3 @@ +# `latch-readonly` + +Filesystem latch that denies operations which would mutate the filesystem. diff --git a/components/latch-readonly/src/lib.rs b/components/latch-readonly/src/lib.rs new file mode 100644 index 0000000..327e264 --- /dev/null +++ b/components/latch-readonly/src/lib.rs @@ -0,0 +1,71 @@ +#![no_main] + +use crate::{ + exports::componentized::filesystem::latch::{ + Decision::{self, Abstain, Deny}, + DescriptorOpenAtArgs, DescriptorOperation, Guest as Latch, Operation, + }, + wasi::filesystem::types::{DescriptorFlags, ErrorCode::ReadOnly, OpenFlags}, +}; + +struct ReadOnlyLatch {} + +impl Latch for ReadOnlyLatch { + fn check(operation: Operation) -> Decision { + match operation { + Operation::Descriptor((_, descriptor_operation)) => match descriptor_operation { + DescriptorOperation::ReadViaStream(_) => Abstain, + DescriptorOperation::WriteViaStream(_) => Deny(ReadOnly), + DescriptorOperation::AppendViaStream => Deny(ReadOnly), + DescriptorOperation::Advise(_) => Abstain, + DescriptorOperation::SyncData => Deny(ReadOnly), + DescriptorOperation::GetFlags => Abstain, + DescriptorOperation::GetType => Abstain, + DescriptorOperation::SetSize(_) => Deny(ReadOnly), + DescriptorOperation::SetTimes(_) => Deny(ReadOnly), + DescriptorOperation::Read(_) => Abstain, + DescriptorOperation::Write(_) => Deny(ReadOnly), + DescriptorOperation::ReadDirectory => Abstain, + DescriptorOperation::Sync => Deny(ReadOnly), + DescriptorOperation::CreateDirectoryAt(_) => Deny(ReadOnly), + DescriptorOperation::Stat => Abstain, + DescriptorOperation::StatAt(_) => Abstain, + DescriptorOperation::SetTimesAt(_) => Deny(ReadOnly), + DescriptorOperation::LinkAt(_) => Deny(ReadOnly), + DescriptorOperation::OpenAt(DescriptorOpenAtArgs { + open_flags, flags, .. + }) => { + if open_flags.intersects( + OpenFlags::CREATE + .union(OpenFlags::EXCLUSIVE) + .union(OpenFlags::TRUNCATE), + ) || flags.intersects( + DescriptorFlags::WRITE + .union(DescriptorFlags::FILE_INTEGRITY_SYNC) + .union(DescriptorFlags::DATA_INTEGRITY_SYNC) + .union(DescriptorFlags::REQUESTED_WRITE_SYNC), + ) { + Deny(ReadOnly) + } else { + Abstain + } + } + DescriptorOperation::ReadlinkAt(_) => Abstain, + DescriptorOperation::RemoveDirectoryAt(_) => Deny(ReadOnly), + DescriptorOperation::RenameAt(_) => Deny(ReadOnly), + DescriptorOperation::SymlinkAt(_) => Deny(ReadOnly), + DescriptorOperation::UnlinkFileAt(_) => Deny(ReadOnly), + DescriptorOperation::MetadataHash => Abstain, + DescriptorOperation::MetadataHashAt(_) => Abstain, + }, + } + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch", + generate_all +}); + +export!(ReadOnlyLatch); diff --git a/wit/latch.wit b/wit/latch.wit new file mode 100644 index 0000000..ac2c49f --- /dev/null +++ b/wit/latch.wit @@ -0,0 +1,165 @@ + +interface latch { + use wasi:filesystem/types@0.2.6.{advice, descriptor, descriptor-flags, error-code, filesize, open-flags, new-timestamp, path-flags}; + + record descriptor-get-directory-args { + path: string, + } + + record descriptor-read-via-stream-args { + offset: filesize, + } + + record descriptor-write-via-stream-args { + offset: filesize, + } + + record descriptor-advise-args { + offset: filesize, + length: filesize, + advice: advice, + } + + record descriptor-set-size-args { + size: filesize, + } + + record descriptor-set-times-args { + data-access-timestamp: new-timestamp, + data-modification-timestamp: new-timestamp, + } + + record descriptor-read-args { + length: filesize, + offset: filesize, + } + + record descriptor-write-args { + buffer-length: u64, + offset: filesize, + } + + record descriptor-create-directory-at-args { + path: string, + } + + record descriptor-stat-at-args { + path-flags: path-flags, + path: string, + } + + record descriptor-set-times-at-args { + path-flags: path-flags, + path: string, + data-access-timestamp: new-timestamp, + data-modification-timestamp: new-timestamp, + } + + record descriptor-link-at-args { + old-path-flags: path-flags, + old-path: string, + new-descriptor: borrow, + new-path: string, + } + + record descriptor-open-at-args { + path-flags: path-flags, + path: string, + open-flags: open-flags, + %flags: descriptor-flags, + } + + record descriptor-readlink-at-args { + path: string, + } + + record descriptor-remove-directory-at-args { + path: string, + } + + record descriptor-rename-at-args { + old-path: string, + new-descriptor: borrow, + new-path: string, + } + + record descriptor-symlink-at-args { + old-path: string, + new-path: string, + } + + record descriptor-unlink-file-at-args { + path: string, + } + + + record descriptor-metadata-hash-at-args { + path-flags: path-flags, + path: string, + } + + variant descriptor-operation { + read-via-stream(descriptor-read-via-stream-args), + write-via-stream(descriptor-write-via-stream-args), + append-via-stream, + advise(descriptor-advise-args), + sync-data, + get-flags, + get-type, + set-size(descriptor-set-size-args), + set-times(descriptor-set-times-args), + read(descriptor-read-args), + write(descriptor-write-args), + read-directory, + sync, + create-directory-at(descriptor-create-directory-at-args), + stat, + stat-at(descriptor-stat-at-args), + set-times-at( descriptor-set-times-at-args), + link-at(descriptor-link-at-args), + open-at(descriptor-open-at-args), + readlink-at(descriptor-readlink-at-args), + remove-directory-at(descriptor-remove-directory-at-args), + rename-at(descriptor-rename-at-args), + symlink-at(descriptor-symlink-at-args), + unlink-file-at(descriptor-unlink-file-at-args), + metadata-hash, + metadata-hash-at(descriptor-metadata-hash-at-args), + } + + variant operation { + descriptor(tuple, descriptor-operation>), + } + + variant decision { + abstain, + allow, + deny(error-code), + } + + check: func(operation: operation) -> decision; +} + +interface latch1 { + use latch.{operation, decision}; + + check: func(operation: operation) -> decision; +} + +interface latch2 { + use latch.{operation, decision}; + + check: func(operation: operation) -> decision; +} + +interface latch3 { + use latch.{operation, decision}; + + check: func(operation: operation) -> decision; +} + +interface latch4 { + use latch.{operation, decision}; + + check: func(operation: operation) -> decision; +} diff --git a/wit/worlds.wit b/wit/worlds.wit index d527e48..6d65fad 100644 --- a/wit/worlds.wit +++ b/wit/worlds.wit @@ -1,6 +1,7 @@ package componentized:filesystem; world filesystem { + import latch; import wasi:config/store@0.2.0-rc.1; import wasi:logging/logging@0.1.0-draft; import wasi:filesystem/preopens@0.3.0; @@ -8,3 +9,28 @@ world filesystem { import wasi:filesystem/types@0.3.0; export wasi:filesystem/types@0.3.0; } + +world filesystem-latch { + export latch; +} + +world filesystem-latch2 { + import latch1; + import latch2; + export latch; +} + +world filesystem-latch3 { + import latch1; + import latch2; + import latch3; + export latch; +} + +world filesystem-latch4 { + import latch1; + import latch2; + import latch3; + import latch4; + export latch; +} From 8affb58695c99a60accf9891b0bb999f267d21ba Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 18 May 2026 13:51:16 -0400 Subject: [PATCH 02/11] centralize logic for latch-n components Signed-off-by: Scott Andrews --- Cargo.lock | 28 +- Cargo.toml | 2 + components/latch-2/src/lib.rs | 273 --------- components/latch-4/src/lib.rs | 273 --------- components/{latch-2 => latch-n2}/Cargo.toml | 3 +- components/{latch-2 => latch-n2}/README.md | 2 +- components/latch-n2/src/lib.rs | 18 + components/{latch-4 => latch-n3}/Cargo.toml | 3 +- components/{latch-3 => latch-n3}/README.md | 2 +- components/latch-n3/src/lib.rs | 18 + components/{latch-3 => latch-n4}/Cargo.toml | 3 +- components/{latch-4 => latch-n4}/README.md | 2 +- components/latch-n4/src/lib.rs | 18 + components/latch-n5/Cargo.toml | 12 + components/latch-n5/README.md | 3 + components/latch-n5/src/lib.rs | 26 + crates/latch-n/Cargo.toml | 8 + .../latch-3 => crates/latch-n}/src/lib.rs | 50 +- wit/deps/wasi-clocks-0.3.0/package.wit | 19 - wit/deps/wasi-config-0.2.0-rc.1/package.wit | 33 - wit/deps/wasi-filesystem-0.3.0/package.wit | 575 ------------------ wit/deps/wasi-logging-0.1.0-draft/package.wit | 36 -- wit/worlds.wit | 17 +- 23 files changed, 168 insertions(+), 1256 deletions(-) delete mode 100644 components/latch-2/src/lib.rs delete mode 100644 components/latch-4/src/lib.rs rename components/{latch-2 => latch-n2}/Cargo.toml (75%) rename components/{latch-2 => latch-n2}/README.md (83%) create mode 100644 components/latch-n2/src/lib.rs rename components/{latch-4 => latch-n3}/Cargo.toml (75%) rename components/{latch-3 => latch-n3}/README.md (83%) create mode 100644 components/latch-n3/src/lib.rs rename components/{latch-3 => latch-n4}/Cargo.toml (75%) rename components/{latch-4 => latch-n4}/README.md (83%) create mode 100644 components/latch-n4/src/lib.rs create mode 100644 components/latch-n5/Cargo.toml create mode 100644 components/latch-n5/README.md create mode 100644 components/latch-n5/src/lib.rs create mode 100644 crates/latch-n/Cargo.toml rename {components/latch-3 => crates/latch-n}/src/lib.rs (91%) delete mode 100644 wit/deps/wasi-clocks-0.3.0/package.wit delete mode 100644 wit/deps/wasi-config-0.2.0-rc.1/package.wit delete mode 100644 wit/deps/wasi-filesystem-0.3.0/package.wit delete mode 100644 wit/deps/wasi-logging-0.1.0-draft/package.wit diff --git a/Cargo.lock b/Cargo.lock index f1e83ba..667e639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,37 +198,55 @@ dependencies = [ ] [[package]] -name = "latch-2" +name = "latch-allow" version = "0.1.0" dependencies = [ "wit-bindgen", ] [[package]] -name = "latch-3" +name = "latch-deny" version = "0.1.0" dependencies = [ "wit-bindgen", ] [[package]] -name = "latch-4" +name = "latch-n" version = "0.1.0" dependencies = [ "wit-bindgen", ] [[package]] -name = "latch-allow" +name = "latch-n2" version = "0.1.0" dependencies = [ + "latch-n", "wit-bindgen", ] [[package]] -name = "latch-deny" +name = "latch-n3" +version = "0.1.0" +dependencies = [ + "latch-n", + "wit-bindgen", +] + +[[package]] +name = "latch-n4" +version = "0.1.0" +dependencies = [ + "latch-n", + "wit-bindgen", +] + +[[package]] +name = "latch-n5" version = "0.1.0" dependencies = [ + "latch-n", "wit-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 8f15ac0..69c0a33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,11 @@ resolver = "2" members = [ "components/*", + "crates/*", ] [workspace.dependencies] chrono = { git = "https://github.com/chronotope/chrono.git", branch = "0.5.x" } heck = "0.5" +latch-n = { path = "./crates/latch-n" } wit-bindgen = { version = "0.58.0", git = "https://github.com/bytecodealliance/wit-bindgen.git", branch = "main" } diff --git a/components/latch-2/src/lib.rs b/components/latch-2/src/lib.rs deleted file mode 100644 index f0ce617..0000000 --- a/components/latch-2/src/lib.rs +++ /dev/null @@ -1,273 +0,0 @@ -#![no_main] - -use crate::{ - componentized::filesystem::{latch, latch1, latch2}, - exports::componentized::filesystem::latch::{ - Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, - DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, - DescriptorReadArgs, DescriptorReadViaStreamArgs, DescriptorReadlinkAtArgs, - DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, - DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, - DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, - DescriptorWriteViaStreamArgs, Guest as Latch, Operation, - }, -}; - -struct AggregateLatch {} - -impl Latch for AggregateLatch { - fn check(operation: Operation) -> Decision { - let operation = operation_map(operation); - let checks = vec![latch1::check, latch2::check]; - for check in checks { - match check(&operation) { - latch::Decision::Abstain => {} - latch::Decision::Allow => return Decision::Allow, - latch::Decision::Deny(error_code) => return Decision::Deny(error_code), - } - } - Decision::Abstain - } -} - -fn operation_map(operation: Operation) -> latch::Operation { - match operation { - Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( - (descriptor, descriptor_operation_map(descriptor_operation)), - ), - } -} - -fn descriptor_operation_map( - descriptor_operation: DescriptorOperation, -) -> latch::DescriptorOperation { - match descriptor_operation { - DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args) => { - latch::DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args_map( - descriptor_read_via_stream_args, - )) - } - DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args) => { - latch::DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args_map( - descriptor_write_via_stream_args, - )) - } - DescriptorOperation::AppendViaStream => latch::DescriptorOperation::AppendViaStream, - DescriptorOperation::Advise(descriptor_advise_args) => { - latch::DescriptorOperation::Advise(descriptor_advise_args_map(descriptor_advise_args)) - } - DescriptorOperation::SyncData => latch::DescriptorOperation::SyncData, - DescriptorOperation::GetFlags => latch::DescriptorOperation::GetFlags, - DescriptorOperation::GetType => latch::DescriptorOperation::GetType, - DescriptorOperation::SetSize(descriptor_set_size_args) => { - latch::DescriptorOperation::SetSize(descriptor_set_size_args_map( - descriptor_set_size_args, - )) - } - DescriptorOperation::SetTimes(descriptor_set_times_args) => { - latch::DescriptorOperation::SetTimes(descriptor_set_times_args_map( - descriptor_set_times_args, - )) - } - DescriptorOperation::Read(descriptor_read_args) => { - latch::DescriptorOperation::Read(descriptor_read_args_map(descriptor_read_args)) - } - DescriptorOperation::Write(descriptor_write_args) => { - latch::DescriptorOperation::Write(descriptor_write_args_map(descriptor_write_args)) - } - DescriptorOperation::ReadDirectory => latch::DescriptorOperation::ReadDirectory, - DescriptorOperation::Sync => latch::DescriptorOperation::Sync, - DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args) => { - latch::DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args_map( - descriptor_create_directory_at_args, - )) - } - DescriptorOperation::Stat => latch::DescriptorOperation::Stat, - DescriptorOperation::StatAt(descriptor_stat_at_args) => { - latch::DescriptorOperation::StatAt(descriptor_stat_at_args_map(descriptor_stat_at_args)) - } - DescriptorOperation::SetTimesAt(descriptor_set_times_at_args) => { - latch::DescriptorOperation::SetTimesAt(descriptor_set_times_at_args_map( - descriptor_set_times_at_args, - )) - } - DescriptorOperation::LinkAt(descriptor_link_at_args) => { - latch::DescriptorOperation::LinkAt(descriptor_link_at_args_map(descriptor_link_at_args)) - } - DescriptorOperation::OpenAt(descriptor_open_at_args) => { - latch::DescriptorOperation::OpenAt(descriptor_open_at_args_map(descriptor_open_at_args)) - } - DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args) => { - latch::DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args_map( - descriptor_readlink_at_args, - )) - } - DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args) => { - latch::DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args_map( - descriptor_remove_directory_at_args, - )) - } - DescriptorOperation::RenameAt(descriptor_rename_at_args) => { - latch::DescriptorOperation::RenameAt(descriptor_rename_at_args_map( - descriptor_rename_at_args, - )) - } - DescriptorOperation::SymlinkAt(descriptor_symlink_at_args) => { - latch::DescriptorOperation::SymlinkAt(descriptor_symlink_at_args_map( - descriptor_symlink_at_args, - )) - } - DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args) => { - latch::DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args_map( - descriptor_unlink_file_at_args, - )) - } - DescriptorOperation::MetadataHash => latch::DescriptorOperation::MetadataHash, - DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args) => { - latch::DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args_map( - descriptor_metadata_hash_at_args, - )) - } - } -} - -fn descriptor_read_via_stream_args_map( - args: DescriptorReadViaStreamArgs, -) -> latch::DescriptorReadViaStreamArgs { - latch::DescriptorReadViaStreamArgs { - offset: args.offset, - } -} - -fn descriptor_write_via_stream_args_map( - args: DescriptorWriteViaStreamArgs, -) -> latch::DescriptorWriteViaStreamArgs { - latch::DescriptorWriteViaStreamArgs { - offset: args.offset, - } -} - -fn descriptor_advise_args_map(args: DescriptorAdviseArgs) -> latch::DescriptorAdviseArgs { - latch::DescriptorAdviseArgs { - offset: args.offset, - length: args.length, - advice: args.advice, - } -} - -fn descriptor_set_size_args_map(args: DescriptorSetSizeArgs) -> latch::DescriptorSetSizeArgs { - latch::DescriptorSetSizeArgs { size: args.size } -} - -fn descriptor_set_times_args_map(args: DescriptorSetTimesArgs) -> latch::DescriptorSetTimesArgs { - latch::DescriptorSetTimesArgs { - data_access_timestamp: args.data_access_timestamp, - data_modification_timestamp: args.data_modification_timestamp, - } -} - -fn descriptor_read_args_map(args: DescriptorReadArgs) -> latch::DescriptorReadArgs { - latch::DescriptorReadArgs { - length: args.length, - offset: args.offset, - } -} - -fn descriptor_write_args_map(args: DescriptorWriteArgs) -> latch::DescriptorWriteArgs { - latch::DescriptorWriteArgs { - buffer_length: args.buffer_length, - offset: args.offset, - } -} - -fn descriptor_create_directory_at_args_map( - args: DescriptorCreateDirectoryAtArgs, -) -> latch::DescriptorCreateDirectoryAtArgs { - latch::DescriptorCreateDirectoryAtArgs { path: args.path } -} - -fn descriptor_stat_at_args_map(args: DescriptorStatAtArgs) -> latch::DescriptorStatAtArgs { - latch::DescriptorStatAtArgs { - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_set_times_at_args_map( - args: DescriptorSetTimesAtArgs, -) -> latch::DescriptorSetTimesAtArgs { - latch::DescriptorSetTimesAtArgs { - data_access_timestamp: args.data_access_timestamp, - data_modification_timestamp: args.data_modification_timestamp, - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_link_at_args_map(args: DescriptorLinkAtArgs) -> latch::DescriptorLinkAtArgs { - latch::DescriptorLinkAtArgs { - new_descriptor: args.new_descriptor, - new_path: args.new_path, - old_path: args.old_path, - old_path_flags: args.old_path_flags, - } -} - -fn descriptor_open_at_args_map(args: DescriptorOpenAtArgs) -> latch::DescriptorOpenAtArgs { - latch::DescriptorOpenAtArgs { - flags: args.flags, - open_flags: args.open_flags, - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_readlink_at_args_map( - args: DescriptorReadlinkAtArgs, -) -> latch::DescriptorReadlinkAtArgs { - latch::DescriptorReadlinkAtArgs { path: args.path } -} - -fn descriptor_remove_directory_at_args_map( - args: DescriptorRemoveDirectoryAtArgs, -) -> latch::DescriptorRemoveDirectoryAtArgs { - latch::DescriptorRemoveDirectoryAtArgs { path: args.path } -} - -fn descriptor_rename_at_args_map(args: DescriptorRenameAtArgs) -> latch::DescriptorRenameAtArgs { - latch::DescriptorRenameAtArgs { - new_descriptor: args.new_descriptor, - new_path: args.new_path, - old_path: args.old_path, - } -} - -fn descriptor_symlink_at_args_map(args: DescriptorSymlinkAtArgs) -> latch::DescriptorSymlinkAtArgs { - latch::DescriptorSymlinkAtArgs { - new_path: args.new_path, - old_path: args.old_path, - } -} - -fn descriptor_unlink_file_at_args_map( - args: DescriptorUnlinkFileAtArgs, -) -> latch::DescriptorUnlinkFileAtArgs { - latch::DescriptorUnlinkFileAtArgs { path: args.path } -} - -fn descriptor_metadata_hash_at_args_map( - args: DescriptorMetadataHashAtArgs, -) -> latch::DescriptorMetadataHashAtArgs { - latch::DescriptorMetadataHashAtArgs { - path: args.path, - path_flags: args.path_flags, - } -} - -wit_bindgen::generate!({ - path: "../../wit", - world: "filesystem-latch2", - generate_all -}); - -export!(AggregateLatch); diff --git a/components/latch-4/src/lib.rs b/components/latch-4/src/lib.rs deleted file mode 100644 index 1270f59..0000000 --- a/components/latch-4/src/lib.rs +++ /dev/null @@ -1,273 +0,0 @@ -#![no_main] - -use crate::{ - componentized::filesystem::{latch, latch1, latch2, latch3, latch4}, - exports::componentized::filesystem::latch::{ - Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, - DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, - DescriptorReadArgs, DescriptorReadViaStreamArgs, DescriptorReadlinkAtArgs, - DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, - DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, - DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, - DescriptorWriteViaStreamArgs, Guest as Latch, Operation, - }, -}; - -struct AggregateLatch {} - -impl Latch for AggregateLatch { - fn check(operation: Operation) -> Decision { - let operation = operation_map(operation); - let checks = vec![latch1::check, latch2::check, latch3::check, latch4::check]; - for check in checks { - match check(&operation) { - latch::Decision::Abstain => {} - latch::Decision::Allow => return Decision::Allow, - latch::Decision::Deny(error_code) => return Decision::Deny(error_code), - } - } - Decision::Abstain - } -} - -fn operation_map(operation: Operation) -> latch::Operation { - match operation { - Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( - (descriptor, descriptor_operation_map(descriptor_operation)), - ), - } -} - -fn descriptor_operation_map( - descriptor_operation: DescriptorOperation, -) -> latch::DescriptorOperation { - match descriptor_operation { - DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args) => { - latch::DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args_map( - descriptor_read_via_stream_args, - )) - } - DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args) => { - latch::DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args_map( - descriptor_write_via_stream_args, - )) - } - DescriptorOperation::AppendViaStream => latch::DescriptorOperation::AppendViaStream, - DescriptorOperation::Advise(descriptor_advise_args) => { - latch::DescriptorOperation::Advise(descriptor_advise_args_map(descriptor_advise_args)) - } - DescriptorOperation::SyncData => latch::DescriptorOperation::SyncData, - DescriptorOperation::GetFlags => latch::DescriptorOperation::GetFlags, - DescriptorOperation::GetType => latch::DescriptorOperation::GetType, - DescriptorOperation::SetSize(descriptor_set_size_args) => { - latch::DescriptorOperation::SetSize(descriptor_set_size_args_map( - descriptor_set_size_args, - )) - } - DescriptorOperation::SetTimes(descriptor_set_times_args) => { - latch::DescriptorOperation::SetTimes(descriptor_set_times_args_map( - descriptor_set_times_args, - )) - } - DescriptorOperation::Read(descriptor_read_args) => { - latch::DescriptorOperation::Read(descriptor_read_args_map(descriptor_read_args)) - } - DescriptorOperation::Write(descriptor_write_args) => { - latch::DescriptorOperation::Write(descriptor_write_args_map(descriptor_write_args)) - } - DescriptorOperation::ReadDirectory => latch::DescriptorOperation::ReadDirectory, - DescriptorOperation::Sync => latch::DescriptorOperation::Sync, - DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args) => { - latch::DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args_map( - descriptor_create_directory_at_args, - )) - } - DescriptorOperation::Stat => latch::DescriptorOperation::Stat, - DescriptorOperation::StatAt(descriptor_stat_at_args) => { - latch::DescriptorOperation::StatAt(descriptor_stat_at_args_map(descriptor_stat_at_args)) - } - DescriptorOperation::SetTimesAt(descriptor_set_times_at_args) => { - latch::DescriptorOperation::SetTimesAt(descriptor_set_times_at_args_map( - descriptor_set_times_at_args, - )) - } - DescriptorOperation::LinkAt(descriptor_link_at_args) => { - latch::DescriptorOperation::LinkAt(descriptor_link_at_args_map(descriptor_link_at_args)) - } - DescriptorOperation::OpenAt(descriptor_open_at_args) => { - latch::DescriptorOperation::OpenAt(descriptor_open_at_args_map(descriptor_open_at_args)) - } - DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args) => { - latch::DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args_map( - descriptor_readlink_at_args, - )) - } - DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args) => { - latch::DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args_map( - descriptor_remove_directory_at_args, - )) - } - DescriptorOperation::RenameAt(descriptor_rename_at_args) => { - latch::DescriptorOperation::RenameAt(descriptor_rename_at_args_map( - descriptor_rename_at_args, - )) - } - DescriptorOperation::SymlinkAt(descriptor_symlink_at_args) => { - latch::DescriptorOperation::SymlinkAt(descriptor_symlink_at_args_map( - descriptor_symlink_at_args, - )) - } - DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args) => { - latch::DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args_map( - descriptor_unlink_file_at_args, - )) - } - DescriptorOperation::MetadataHash => latch::DescriptorOperation::MetadataHash, - DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args) => { - latch::DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args_map( - descriptor_metadata_hash_at_args, - )) - } - } -} - -fn descriptor_read_via_stream_args_map( - args: DescriptorReadViaStreamArgs, -) -> latch::DescriptorReadViaStreamArgs { - latch::DescriptorReadViaStreamArgs { - offset: args.offset, - } -} - -fn descriptor_write_via_stream_args_map( - args: DescriptorWriteViaStreamArgs, -) -> latch::DescriptorWriteViaStreamArgs { - latch::DescriptorWriteViaStreamArgs { - offset: args.offset, - } -} - -fn descriptor_advise_args_map(args: DescriptorAdviseArgs) -> latch::DescriptorAdviseArgs { - latch::DescriptorAdviseArgs { - offset: args.offset, - length: args.length, - advice: args.advice, - } -} - -fn descriptor_set_size_args_map(args: DescriptorSetSizeArgs) -> latch::DescriptorSetSizeArgs { - latch::DescriptorSetSizeArgs { size: args.size } -} - -fn descriptor_set_times_args_map(args: DescriptorSetTimesArgs) -> latch::DescriptorSetTimesArgs { - latch::DescriptorSetTimesArgs { - data_access_timestamp: args.data_access_timestamp, - data_modification_timestamp: args.data_modification_timestamp, - } -} - -fn descriptor_read_args_map(args: DescriptorReadArgs) -> latch::DescriptorReadArgs { - latch::DescriptorReadArgs { - length: args.length, - offset: args.offset, - } -} - -fn descriptor_write_args_map(args: DescriptorWriteArgs) -> latch::DescriptorWriteArgs { - latch::DescriptorWriteArgs { - buffer_length: args.buffer_length, - offset: args.offset, - } -} - -fn descriptor_create_directory_at_args_map( - args: DescriptorCreateDirectoryAtArgs, -) -> latch::DescriptorCreateDirectoryAtArgs { - latch::DescriptorCreateDirectoryAtArgs { path: args.path } -} - -fn descriptor_stat_at_args_map(args: DescriptorStatAtArgs) -> latch::DescriptorStatAtArgs { - latch::DescriptorStatAtArgs { - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_set_times_at_args_map( - args: DescriptorSetTimesAtArgs, -) -> latch::DescriptorSetTimesAtArgs { - latch::DescriptorSetTimesAtArgs { - data_access_timestamp: args.data_access_timestamp, - data_modification_timestamp: args.data_modification_timestamp, - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_link_at_args_map(args: DescriptorLinkAtArgs) -> latch::DescriptorLinkAtArgs { - latch::DescriptorLinkAtArgs { - new_descriptor: args.new_descriptor, - new_path: args.new_path, - old_path: args.old_path, - old_path_flags: args.old_path_flags, - } -} - -fn descriptor_open_at_args_map(args: DescriptorOpenAtArgs) -> latch::DescriptorOpenAtArgs { - latch::DescriptorOpenAtArgs { - flags: args.flags, - open_flags: args.open_flags, - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_readlink_at_args_map( - args: DescriptorReadlinkAtArgs, -) -> latch::DescriptorReadlinkAtArgs { - latch::DescriptorReadlinkAtArgs { path: args.path } -} - -fn descriptor_remove_directory_at_args_map( - args: DescriptorRemoveDirectoryAtArgs, -) -> latch::DescriptorRemoveDirectoryAtArgs { - latch::DescriptorRemoveDirectoryAtArgs { path: args.path } -} - -fn descriptor_rename_at_args_map(args: DescriptorRenameAtArgs) -> latch::DescriptorRenameAtArgs { - latch::DescriptorRenameAtArgs { - new_descriptor: args.new_descriptor, - new_path: args.new_path, - old_path: args.old_path, - } -} - -fn descriptor_symlink_at_args_map(args: DescriptorSymlinkAtArgs) -> latch::DescriptorSymlinkAtArgs { - latch::DescriptorSymlinkAtArgs { - new_path: args.new_path, - old_path: args.old_path, - } -} - -fn descriptor_unlink_file_at_args_map( - args: DescriptorUnlinkFileAtArgs, -) -> latch::DescriptorUnlinkFileAtArgs { - latch::DescriptorUnlinkFileAtArgs { path: args.path } -} - -fn descriptor_metadata_hash_at_args_map( - args: DescriptorMetadataHashAtArgs, -) -> latch::DescriptorMetadataHashAtArgs { - latch::DescriptorMetadataHashAtArgs { - path: args.path, - path_flags: args.path_flags, - } -} - -wit_bindgen::generate!({ - path: "../../wit", - world: "filesystem-latch4", - generate_all -}); - -export!(AggregateLatch); diff --git a/components/latch-2/Cargo.toml b/components/latch-n2/Cargo.toml similarity index 75% rename from components/latch-2/Cargo.toml rename to components/latch-n2/Cargo.toml index 6fc6748..ddd1054 100644 --- a/components/latch-2/Cargo.toml +++ b/components/latch-n2/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "latch-2" +name = "latch-n2" version = "0.1.0" edition = "2021" license = "Apache-2.0" @@ -8,4 +8,5 @@ license = "Apache-2.0" crate-type = ["cdylib"] [dependencies] +latch-n = { workspace = true } wit-bindgen = { workspace = true } diff --git a/components/latch-2/README.md b/components/latch-n2/README.md similarity index 83% rename from components/latch-2/README.md rename to components/latch-n2/README.md index 7d3ce33..d302838 100644 --- a/components/latch-2/README.md +++ b/components/latch-n2/README.md @@ -1,3 +1,3 @@ -# `latch-2` +# `latch-n2` Filesystem latch that aggregates two other filesystem latches. diff --git a/components/latch-n2/src/lib.rs b/components/latch-n2/src/lib.rs new file mode 100644 index 0000000..e157ad0 --- /dev/null +++ b/components/latch-n2/src/lib.rs @@ -0,0 +1,18 @@ +#![no_main] + +use latch_n::bindings::componentized::filesystem::{latch as latch0, latch1}; +use latch_n::bindings::exports::componentized::filesystem::latch::{ + Decision, Guest as Latch, Operation, +}; + +struct LatchN2 {} + +impl Latch for LatchN2 { + #[allow(async_fn_in_trait)] + fn check(operation: Operation<'_>) -> Decision { + let checks = vec![latch0::check, latch1::check]; + latch_n::check(operation, checks) + } +} + +latch_n::export!(LatchN2 with_types_in latch_n::bindings); diff --git a/components/latch-4/Cargo.toml b/components/latch-n3/Cargo.toml similarity index 75% rename from components/latch-4/Cargo.toml rename to components/latch-n3/Cargo.toml index 00f20b4..beb0e00 100644 --- a/components/latch-4/Cargo.toml +++ b/components/latch-n3/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "latch-4" +name = "latch-n3" version = "0.1.0" edition = "2021" license = "Apache-2.0" @@ -8,4 +8,5 @@ license = "Apache-2.0" crate-type = ["cdylib"] [dependencies] +latch-n = { workspace = true } wit-bindgen = { workspace = true } diff --git a/components/latch-3/README.md b/components/latch-n3/README.md similarity index 83% rename from components/latch-3/README.md rename to components/latch-n3/README.md index 0438bd8..8ff8624 100644 --- a/components/latch-3/README.md +++ b/components/latch-n3/README.md @@ -1,3 +1,3 @@ -# `latch-3` +# `latch-n3` Filesystem latch that aggregates three other filesystem latches. diff --git a/components/latch-n3/src/lib.rs b/components/latch-n3/src/lib.rs new file mode 100644 index 0000000..18f89a8 --- /dev/null +++ b/components/latch-n3/src/lib.rs @@ -0,0 +1,18 @@ +#![no_main] + +use latch_n::bindings::componentized::filesystem::{latch as latch0, latch1, latch2}; +use latch_n::bindings::exports::componentized::filesystem::latch::{ + Decision, Guest as Latch, Operation, +}; + +struct LatchN3 {} + +impl Latch for LatchN3 { + #[allow(async_fn_in_trait)] + fn check(operation: Operation<'_>) -> Decision { + let checks = vec![latch0::check, latch1::check, latch2::check]; + latch_n::check(operation, checks) + } +} + +latch_n::export!(LatchN3 with_types_in latch_n::bindings); diff --git a/components/latch-3/Cargo.toml b/components/latch-n4/Cargo.toml similarity index 75% rename from components/latch-3/Cargo.toml rename to components/latch-n4/Cargo.toml index a348e23..456bb99 100644 --- a/components/latch-3/Cargo.toml +++ b/components/latch-n4/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "latch-3" +name = "latch-n4" version = "0.1.0" edition = "2021" license = "Apache-2.0" @@ -8,4 +8,5 @@ license = "Apache-2.0" crate-type = ["cdylib"] [dependencies] +latch-n = { workspace = true } wit-bindgen = { workspace = true } diff --git a/components/latch-4/README.md b/components/latch-n4/README.md similarity index 83% rename from components/latch-4/README.md rename to components/latch-n4/README.md index dd8d6f6..f9586c5 100644 --- a/components/latch-4/README.md +++ b/components/latch-n4/README.md @@ -1,3 +1,3 @@ -# `latch-4` +# `latch-n4` Filesystem latch that aggregates four other filesystem latches. diff --git a/components/latch-n4/src/lib.rs b/components/latch-n4/src/lib.rs new file mode 100644 index 0000000..e733807 --- /dev/null +++ b/components/latch-n4/src/lib.rs @@ -0,0 +1,18 @@ +#![no_main] + +use latch_n::bindings::componentized::filesystem::{latch as latch0, latch1, latch2, latch3}; +use latch_n::bindings::exports::componentized::filesystem::latch::{ + Decision, Guest as Latch, Operation, +}; + +struct LatchN4 {} + +impl Latch for LatchN4 { + #[allow(async_fn_in_trait)] + fn check(operation: Operation<'_>) -> Decision { + let checks = vec![latch0::check, latch1::check, latch2::check, latch3::check]; + latch_n::check(operation, checks) + } +} + +latch_n::export!(LatchN4 with_types_in latch_n::bindings); diff --git a/components/latch-n5/Cargo.toml b/components/latch-n5/Cargo.toml new file mode 100644 index 0000000..0aeb5b3 --- /dev/null +++ b/components/latch-n5/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "latch-n5" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +latch-n = { workspace = true } +wit-bindgen = { workspace = true } diff --git a/components/latch-n5/README.md b/components/latch-n5/README.md new file mode 100644 index 0000000..407bcb2 --- /dev/null +++ b/components/latch-n5/README.md @@ -0,0 +1,3 @@ +# `latch-n5` + +Filesystem latch that aggregates five other filesystem latches. diff --git a/components/latch-n5/src/lib.rs b/components/latch-n5/src/lib.rs new file mode 100644 index 0000000..e76e136 --- /dev/null +++ b/components/latch-n5/src/lib.rs @@ -0,0 +1,26 @@ +#![no_main] + +use latch_n::bindings::componentized::filesystem::{ + latch as latch0, latch1, latch2, latch3, latch4, +}; +use latch_n::bindings::exports::componentized::filesystem::latch::{ + Decision, Guest as Latch, Operation, +}; + +struct LatchN5 {} + +impl Latch for LatchN5 { + #[allow(async_fn_in_trait)] + fn check(operation: Operation<'_>) -> Decision { + let checks = vec![ + latch0::check, + latch1::check, + latch2::check, + latch3::check, + latch4::check, + ]; + latch_n::check(operation, checks) + } +} + +latch_n::export!(LatchN5 with_types_in latch_n::bindings); diff --git a/crates/latch-n/Cargo.toml b/crates/latch-n/Cargo.toml new file mode 100644 index 0000000..89c2b9e --- /dev/null +++ b/crates/latch-n/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "latch-n" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[dependencies] +wit-bindgen = { workspace = true } diff --git a/components/latch-3/src/lib.rs b/crates/latch-n/src/lib.rs similarity index 91% rename from components/latch-3/src/lib.rs rename to crates/latch-n/src/lib.rs index 7127cf5..34cc1b4 100644 --- a/components/latch-3/src/lib.rs +++ b/crates/latch-n/src/lib.rs @@ -1,7 +1,7 @@ #![no_main] -use crate::{ - componentized::filesystem::{latch, latch1, latch2, latch3}, +use crate::bindings::{ + componentized::filesystem::latch, exports::componentized::filesystem::latch::{ Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, @@ -9,25 +9,23 @@ use crate::{ DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, - DescriptorWriteViaStreamArgs, Guest as Latch, Operation, + DescriptorWriteViaStreamArgs, Operation, }, }; -struct AggregateLatch {} - -impl Latch for AggregateLatch { - fn check(operation: Operation) -> Decision { - let operation = operation_map(operation); - let checks = vec![latch1::check, latch2::check, latch3::check]; - for check in checks { - match check(&operation) { - latch::Decision::Abstain => {} - latch::Decision::Allow => return Decision::Allow, - latch::Decision::Deny(error_code) => return Decision::Deny(error_code), - } +pub fn check( + operation: Operation, + checks: Vec) -> latch::Decision>, +) -> Decision { + let operation = operation_map(operation); + for check in checks { + match check(&operation) { + latch::Decision::Abstain => {} + latch::Decision::Allow => return Decision::Allow, + latch::Decision::Deny(error_code) => return Decision::Deny(error_code), } - Decision::Abstain } + Decision::Abstain } fn operation_map(operation: Operation) -> latch::Operation { @@ -264,10 +262,18 @@ fn descriptor_metadata_hash_at_args_map( } } -wit_bindgen::generate!({ - path: "../../wit", - world: "filesystem-latch3", - generate_all -}); +pub mod bindings { + wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch-n", + pub_export_macro: true, + generate_all + }); +} -export!(AggregateLatch); +#[macro_export] +macro_rules! export { + ($($t:tt)*) => { + $crate::bindings::export!($($t)*); + }; +} diff --git a/wit/deps/wasi-clocks-0.3.0/package.wit b/wit/deps/wasi-clocks-0.3.0/package.wit deleted file mode 100644 index 2594e02..0000000 --- a/wit/deps/wasi-clocks-0.3.0/package.wit +++ /dev/null @@ -1,19 +0,0 @@ -package wasi:clocks@0.3.0; - -interface types { - type duration = u64; -} - -interface system-clock { - use types.{duration}; - - record instant { - seconds: s64, - nanoseconds: u32, - } - - now: func() -> instant; - - get-resolution: func() -> duration; -} - diff --git a/wit/deps/wasi-config-0.2.0-rc.1/package.wit b/wit/deps/wasi-config-0.2.0-rc.1/package.wit deleted file mode 100644 index d8950ee..0000000 --- a/wit/deps/wasi-config-0.2.0-rc.1/package.wit +++ /dev/null @@ -1,33 +0,0 @@ -package wasi:config@0.2.0-rc.1; - -interface store { - /// An error type that encapsulates the different errors that can occur fetching configuration values. - variant error { - /// This indicates an error from an "upstream" config source. - /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), - /// the error message is a string. - upstream(string), - /// This indicates an error from an I/O operation. - /// As this could be almost _anything_ (such as a file read, network connection, etc), - /// the error message is a string. - /// Depending on how this ends up being consumed, - /// we may consider moving this to use the `wasi:io/error` type instead. - /// For simplicity right now in supporting multiple implementations, it is being left as a string. - io(string), - } - - /// Gets a configuration value of type `string` associated with the `key`. - /// - /// The value is returned as an `option`. If the key is not found, - /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. - get: func(key: string) -> result, error>; - - /// Gets a list of configuration key-value pairs of type `string`. - /// - /// If an error occurs, an `Err(error)` is returned. - get-all: func() -> result>, error>; -} - -world imports { - import store; -} diff --git a/wit/deps/wasi-filesystem-0.3.0/package.wit b/wit/deps/wasi-filesystem-0.3.0/package.wit deleted file mode 100644 index e4a778f..0000000 --- a/wit/deps/wasi-filesystem-0.3.0/package.wit +++ /dev/null @@ -1,575 +0,0 @@ -package wasi:filesystem@0.3.0; - -/// 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. -/// -/// Paths are passed as interface-type `string`s, meaning they must consist of -/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain -/// paths which are not accessible by this API. -/// -/// The directory separator in WASI is always the forward-slash (`/`). -/// -/// All paths in WASI are relative paths, and are interpreted relative to a -/// `descriptor` referring to a base directory. If a `path` argument to any WASI -/// function starts with `/`, or if any step of resolving a `path`, including -/// `..` 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`. -/// -/// For more information about WASI path resolution and sandboxing, see -/// [WASI filesystem path resolution]. -/// -/// Though this package presents a portable interface modelled on POSIX, it -/// prioritizes compatibility over portability: allowing users to access their -/// files on their machine is more important than exposing a single semantics -/// across all platforms. Notably, depending on the underlying operating system -/// and file system: -/// * Paths may be case-folded or not. -/// * Deleting (unlinking) a file may fail if there are other file descriptors -/// open. -/// * Durability and atomicity of changes to underlying files when there are -/// concurrent writers. -/// -/// Users that need well-defined, portable semantics should use a key-value -/// store or a database instead. -/// -/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md -@since(version = 0.3.0) -interface types { - @since(version = 0.3.0) - use wasi:clocks/system-clock@0.3.0.{instant}; - - /// File size or length of a region within a file. - @since(version = 0.3.0) - type filesize = u64; - - /// The type of a filesystem object referenced by a descriptor. - /// - /// Note: This was called `filetype` in earlier versions of WASI. - @since(version = 0.3.0) - variant descriptor-type { - /// The descriptor refers to a block device inode. - block-device, - /// The descriptor refers to a character device inode. - character-device, - /// The descriptor refers to a directory inode. - directory, - /// The descriptor refers to a named pipe. - fifo, - /// The file refers to a symbolic link inode. - symbolic-link, - /// The descriptor refers to a regular file inode. - regular-file, - /// The descriptor refers to a socket. - socket, - /// The type of the descriptor or file is different from any of the - /// other types specified. - other(option), - } - - /// Descriptor flags. - /// - /// Note: This was called `fdflags` in earlier versions of WASI. - @since(version = 0.3.0) - flags descriptor-flags { - /// Read mode: Data can be read. - read, - /// Write mode: Data can be written to. - write, - /// Request that writes be performed according to synchronized I/O file - /// integrity completion. The data stored in the file and the file's - /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - file-integrity-sync, - /// Request that writes be performed according to synchronized I/O data - /// integrity completion. Only the data stored in the file is - /// synchronized. This is similar to `O_DSYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - data-integrity-sync, - /// Requests that reads be performed at the same level of integrity - /// requested for writes. This is similar to `O_RSYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - requested-write-sync, - /// Mutating directories mode: Directory contents may be mutated. - /// - /// When this flag is unset on a descriptor, operations using the - /// descriptor which would create, rename, delete, modify the data or - /// metadata of filesystem objects, or obtain another handle which - /// would permit any of those, shall fail with `error-code::read-only` if - /// they would otherwise succeed. - /// - /// This may only be set on directories. - mutate-directory, - } - - /// Flags determining the method of how paths are resolved. - @since(version = 0.3.0) - flags path-flags { - /// As long as the resolved path corresponds to a symbolic link, it is - /// expanded. - symlink-follow, - } - - /// Open flags used by `open-at`. - @since(version = 0.3.0) - flags open-flags { - /// Create file if it does not exist, similar to `O_CREAT` in POSIX. - create, - /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. - directory, - /// Fail if file already exists, similar to `O_EXCL` in POSIX. - exclusive, - /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. - truncate, - } - - /// Number of hard links to an inode. - @since(version = 0.3.0) - type link-count = u64; - - /// File attributes. - /// - /// Note: This was called `filestat` in earlier versions of WASI. - @since(version = 0.3.0) - record descriptor-stat { - /// File type. - %type: descriptor-type, - /// Number of hard links to the file. - link-count: link-count, - /// For regular files, the file size in bytes. For symbolic links, the - /// length in bytes of the pathname contained in the symbolic link. - size: filesize, - /// Last data access timestamp. - /// - /// If the `option` is none, the platform doesn't maintain an access - /// timestamp for this file. - data-access-timestamp: option, - /// Last data modification timestamp. - /// - /// If the `option` is none, the platform doesn't maintain a - /// modification timestamp for this file. - data-modification-timestamp: option, - /// Last file status-change timestamp. - /// - /// If the `option` is none, the platform doesn't maintain a - /// status-change timestamp for this file. - status-change-timestamp: option, - } - - /// When setting a timestamp, this gives the value to set it to. - @since(version = 0.3.0) - variant new-timestamp { - /// Leave the timestamp set to its previous value. - no-change, - /// Set the timestamp to the current time of the system clock associated - /// with the filesystem. - now, - /// Set the timestamp to the given value. - timestamp(instant), - } - - /// A directory entry. - @since(version = 0.3.0) - record directory-entry { - /// The type of the file referred to by this directory entry. - %type: descriptor-type, - /// The name of the object. - name: string, - } - - /// Error codes returned by functions, similar to `errno` in POSIX. - /// Not all of these error codes are returned by the functions provided by this - /// API; some are used in higher-level library layers, and others are provided - /// merely for alignment with POSIX. - @since(version = 0.3.0) - variant error-code { - /// Permission denied, similar to `EACCES` in POSIX. - access, - /// Connection already in progress, similar to `EALREADY` in POSIX. - already, - /// Bad descriptor, similar to `EBADF` in POSIX. - bad-descriptor, - /// Device or resource busy, similar to `EBUSY` in POSIX. - busy, - /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. - deadlock, - /// Storage quota exceeded, similar to `EDQUOT` in POSIX. - quota, - /// File exists, similar to `EEXIST` in POSIX. - exist, - /// File too large, similar to `EFBIG` in POSIX. - file-too-large, - /// Illegal byte sequence, similar to `EILSEQ` in POSIX. - illegal-byte-sequence, - /// Operation in progress, similar to `EINPROGRESS` in POSIX. - in-progress, - /// Interrupted function, similar to `EINTR` in POSIX. - interrupted, - /// Invalid argument, similar to `EINVAL` in POSIX. - invalid, - /// I/O error, similar to `EIO` in POSIX. - io, - /// Is a directory, similar to `EISDIR` in POSIX. - is-directory, - /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. - loop, - /// Too many links, similar to `EMLINK` in POSIX. - too-many-links, - /// Message too large, similar to `EMSGSIZE` in POSIX. - message-size, - /// Filename too long, similar to `ENAMETOOLONG` in POSIX. - name-too-long, - /// No such device, similar to `ENODEV` in POSIX. - no-device, - /// No such file or directory, similar to `ENOENT` in POSIX. - no-entry, - /// No locks available, similar to `ENOLCK` in POSIX. - no-lock, - /// Not enough space, similar to `ENOMEM` in POSIX. - insufficient-memory, - /// No space left on device, similar to `ENOSPC` in POSIX. - insufficient-space, - /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. - not-directory, - /// Directory not empty, similar to `ENOTEMPTY` in POSIX. - not-empty, - /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. - not-recoverable, - /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. - unsupported, - /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. - no-tty, - /// No such device or address, similar to `ENXIO` in POSIX. - no-such-device, - /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. - overflow, - /// Operation not permitted, similar to `EPERM` in POSIX. - not-permitted, - /// Broken pipe, similar to `EPIPE` in POSIX. - pipe, - /// Read-only file system, similar to `EROFS` in POSIX. - read-only, - /// Invalid seek, similar to `ESPIPE` in POSIX. - invalid-seek, - /// Text file busy, similar to `ETXTBSY` in POSIX. - text-file-busy, - /// Cross-device link, similar to `EXDEV` in POSIX. - cross-device, - /// A catch-all for errors not captured by the existing variants. - /// Implementations can use this to extend the error type without - /// breaking existing code. - other(option), - } - - /// File or memory access pattern advisory information. - @since(version = 0.3.0) - enum advice { - /// The application has no advice to give on its behavior with respect - /// to the specified data. - normal, - /// The application expects to access the specified data sequentially - /// from lower offsets to higher offsets. - sequential, - /// The application expects to access the specified data in a random - /// order. - random, - /// The application expects to access the specified data in the near - /// future. - will-need, - /// The application expects that it will not access the specified data - /// in the near future. - dont-need, - /// The application expects to access the specified data once and then - /// not reuse it thereafter. - no-reuse, - } - - /// A 128-bit hash value, split into parts because wasm doesn't have a - /// 128-bit integer type. - @since(version = 0.3.0) - 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, - } - - /// A descriptor is a reference to a filesystem object, which may be a file, - /// directory, named pipe, special file, or other object on which filesystem - /// calls may be made. - @since(version = 0.3.0) - resource descriptor { - /// Return a stream for reading from a file. - /// - /// Multiple read, write, and append streams may be active on the same open - /// file and they do not interfere with each other. - /// - /// This function returns a `stream` which provides the data received from the - /// file, and a `future` providing additional error information in case an - /// error is encountered. - /// - /// If no error is encountered, `stream.read` on the `stream` will return - /// `read-status::closed` with no `error-context` and the future resolves to - /// the value `ok`. If an error is encountered, `stream.read` on the - /// `stream` returns `read-status::closed` with an `error-context` and the future - /// resolves to `err` with an `error-code`. - /// - /// Note: This is similar to `pread` in POSIX. - @since(version = 0.3.0) - read-via-stream: func(offset: filesize) -> tuple, future>>; - /// Return a stream for writing to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be written. - /// - /// It is valid to write past the end of a file; the file is extended to the - /// extent of the write, with bytes between the previous end and the start of - /// the write set to zero. - /// - /// This function returns once either full contents of the stream are - /// written or an error is encountered. - /// - /// Note: This is similar to `pwrite` in POSIX. - @since(version = 0.3.0) - write-via-stream: func(data: stream, offset: filesize) -> future>; - /// Return a stream for appending to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be appended. - /// - /// This function returns once either full contents of the stream are - /// written or an error is encountered. - /// - /// Note: This is similar to `write` with `O_APPEND` in POSIX. - @since(version = 0.3.0) - append-via-stream: func(data: stream) -> future>; - /// Provide file advisory information on a descriptor. - /// - /// This is similar to `posix_fadvise` in POSIX. - @since(version = 0.3.0) - advise: async func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; - /// Synchronize the data of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fdatasync` in POSIX. - @since(version = 0.3.0) - sync-data: async func() -> result<_, error-code>; - /// Get flags associated with a descriptor. - /// - /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. - /// - /// Note: This returns the value that was the `fs_flags` value returned - /// from `fdstat_get` in earlier versions of WASI. - @since(version = 0.3.0) - get-flags: async func() -> result; - /// Get the dynamic type of a descriptor. - /// - /// Note: This returns the same value as the `type` field of the `fd-stat` - /// returned by `stat`, `stat-at` and similar. - /// - /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided - /// by `fstat` in POSIX. - /// - /// Note: This returns the value that was the `fs_filetype` value returned - /// from `fdstat_get` in earlier versions of WASI. - @since(version = 0.3.0) - get-type: async func() -> result; - /// Adjust the size of an open file. If this increases the file's size, the - /// extra bytes are filled with zeros. - /// - /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. - @since(version = 0.3.0) - set-size: async func(size: filesize) -> result<_, error-code>; - /// Adjust the timestamps of an open file or directory. - /// - /// Note: This is similar to `futimens` in POSIX. - /// - /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. - @since(version = 0.3.0) - set-times: async func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - /// Read directory entries from a directory. - /// - /// On filesystems where directories contain entries referring to themselves - /// and their parents, often named `.` and `..` respectively, these entries - /// are omitted. - /// - /// This always returns a new stream which starts at the beginning of the - /// directory. Multiple streams may be active on the same directory, and they - /// do not interfere with each other. - /// - /// This function returns a future, which will resolve to an error code if - /// reading full contents of the directory fails. - @since(version = 0.3.0) - read-directory: func() -> tuple, future>>; - /// Synchronize the data and metadata of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fsync` in POSIX. - @since(version = 0.3.0) - sync: async func() -> result<_, error-code>; - /// Create a directory. - /// - /// Note: This is similar to `mkdirat` in POSIX. - @since(version = 0.3.0) - create-directory-at: async func(path: string) -> result<_, error-code>; - /// Return the attributes of an open file or directory. - /// - /// 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. - @since(version = 0.3.0) - stat: async func() -> result; - /// Return the attributes of a file or directory. - /// - /// 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. - @since(version = 0.3.0) - stat-at: async func(path-flags: path-flags, path: string) -> result; - /// Adjust the timestamps of a file or directory. - /// - /// Note: This is similar to `utimensat` in POSIX. - /// - /// Note: This was called `path_filestat_set_times` in earlier versions of - /// WASI. - @since(version = 0.3.0) - set-times-at: async func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - /// Create a hard link. - /// - /// Fails with `error-code::no-entry` if the old path does not exist, - /// with `error-code::exist` if the new path already exists, and - /// `error-code::not-permitted` if the old path is not a file. - /// - /// Note: This is similar to `linkat` in POSIX. - @since(version = 0.3.0) - link-at: async func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - /// Open a file or directory. - /// - /// If `flags` contains `descriptor-flags::mutate-directory`, and the base - /// descriptor doesn't have `descriptor-flags::mutate-directory` set, - /// `open-at` fails with `error-code::read-only`. - /// - /// If `flags` contains `write` or `mutate-directory`, or `open-flags` - /// contains `truncate` or `create`, and the base descriptor doesn't have - /// `descriptor-flags::mutate-directory` set, `open-at` fails with - /// `error-code::read-only`. - /// - /// Note: This is similar to `openat` in POSIX. - @since(version = 0.3.0) - open-at: async func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; - /// Read the contents of a symbolic link. - /// - /// If the contents contain an absolute or rooted path in the underlying - /// filesystem, this function fails with `error-code::not-permitted`. - /// - /// Note: This is similar to `readlinkat` in POSIX. - @since(version = 0.3.0) - readlink-at: async func(path: string) -> result; - /// Remove a directory. - /// - /// Return `error-code::not-empty` if the directory is not empty. - /// - /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. - @since(version = 0.3.0) - remove-directory-at: async func(path: string) -> result<_, error-code>; - /// Rename a filesystem object. - /// - /// Note: This is similar to `renameat` in POSIX. - @since(version = 0.3.0) - rename-at: async func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - /// Create a symbolic link (also known as a "symlink"). - /// - /// If `old-path` starts with `/`, the function fails with - /// `error-code::not-permitted`. - /// - /// Note: This is similar to `symlinkat` in POSIX. - @since(version = 0.3.0) - symlink-at: async func(old-path: string, new-path: string) -> result<_, error-code>; - /// Unlink a filesystem object that is not a directory. - /// - /// This is similar to `unlinkat(fd, path, 0)` in POSIX. - /// - /// Error returns are as specified by POSIX. - /// - /// If the filesystem object is a directory, `error-code::access` or - /// `error-code::is-directory` may be returned instead of the - /// POSIX-specified `error-code::not-permitted`. - @since(version = 0.3.0) - unlink-file-at: async func(path: string) -> result<_, error-code>; - /// 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. - @since(version = 0.3.0) - is-same-object: async func(other: borrow) -> 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 encouraged 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. - @since(version = 0.3.0) - metadata-hash: async func() -> 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`. - @since(version = 0.3.0) - metadata-hash-at: async func(path-flags: path-flags, path: string) -> result; - } -} - -@since(version = 0.3.0) -interface preopens { - @since(version = 0.3.0) - use types.{descriptor}; - - /// Return the set of preopened directories, and their paths. - @since(version = 0.3.0) - get-directories: func() -> list>; -} - -@since(version = 0.3.0) -world imports { - @since(version = 0.3.0) - import wasi:clocks/types@0.3.0; - @since(version = 0.3.0) - import wasi:clocks/system-clock@0.3.0; - @since(version = 0.3.0) - import types; - @since(version = 0.3.0) - import preopens; -} diff --git a/wit/deps/wasi-logging-0.1.0-draft/package.wit b/wit/deps/wasi-logging-0.1.0-draft/package.wit deleted file mode 100644 index 164cb5b..0000000 --- a/wit/deps/wasi-logging-0.1.0-draft/package.wit +++ /dev/null @@ -1,36 +0,0 @@ -package wasi:logging@0.1.0-draft; - -/// WASI Logging is a logging API intended to let users emit log messages with -/// simple priority levels and context values. -interface logging { - /// A log level, describing a kind of message. - enum level { - /// Describes messages about the values of variables and the flow of - /// control within a program. - trace, - /// Describes messages likely to be of interest to someone debugging a - /// program. - debug, - /// Describes messages likely to be of interest to someone monitoring a - /// program. - info, - /// Describes messages indicating hazardous situations. - warn, - /// Describes messages indicating serious errors. - error, - /// Describes messages indicating fatal errors. - critical, - } - - /// Emit a log message. - /// - /// A log message has a `level` describing what kind of message is being - /// sent, a context, which is an uninterpreted string meant to help - /// consumers group similar messages, and a string containing the message - /// text. - log: func(level: level, context: string, message: string); -} - -world imports { - import logging; -} diff --git a/wit/worlds.wit b/wit/worlds.wit index 6d65fad..8c8701f 100644 --- a/wit/worlds.wit +++ b/wit/worlds.wit @@ -14,23 +14,12 @@ world filesystem-latch { export latch; } -world filesystem-latch2 { - import latch1; - import latch2; +world filesystem-latch-n { export latch; -} - -world filesystem-latch3 { - import latch1; - import latch2; - import latch3; - export latch; -} - -world filesystem-latch4 { import latch1; import latch2; import latch3; import latch4; - export latch; + import wasi:filesystem/preopens@0.2.6; + import wasi:filesystem/types@0.2.6; } From 415b1400256405087e3fbd3fbee830e9cd4424ba Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 18 May 2026 13:57:56 -0400 Subject: [PATCH 03/11] rebuild components on changes in crates dir Signed-off-by: Scott Andrews --- Cargo.lock | 4 ---- components/latch-n2/Cargo.toml | 1 - components/latch-n3/Cargo.toml | 1 - components/latch-n4/Cargo.toml | 1 - components/latch-n5/Cargo.toml | 1 - 5 files changed, 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 667e639..792f52f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,7 +223,6 @@ name = "latch-n2" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] @@ -231,7 +230,6 @@ name = "latch-n3" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] @@ -239,7 +237,6 @@ name = "latch-n4" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] @@ -247,7 +244,6 @@ name = "latch-n5" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] diff --git a/components/latch-n2/Cargo.toml b/components/latch-n2/Cargo.toml index ddd1054..4fbab2a 100644 --- a/components/latch-n2/Cargo.toml +++ b/components/latch-n2/Cargo.toml @@ -9,4 +9,3 @@ crate-type = ["cdylib"] [dependencies] latch-n = { workspace = true } -wit-bindgen = { workspace = true } diff --git a/components/latch-n3/Cargo.toml b/components/latch-n3/Cargo.toml index beb0e00..baf0a07 100644 --- a/components/latch-n3/Cargo.toml +++ b/components/latch-n3/Cargo.toml @@ -9,4 +9,3 @@ crate-type = ["cdylib"] [dependencies] latch-n = { workspace = true } -wit-bindgen = { workspace = true } diff --git a/components/latch-n4/Cargo.toml b/components/latch-n4/Cargo.toml index 456bb99..d6bc670 100644 --- a/components/latch-n4/Cargo.toml +++ b/components/latch-n4/Cargo.toml @@ -9,4 +9,3 @@ crate-type = ["cdylib"] [dependencies] latch-n = { workspace = true } -wit-bindgen = { workspace = true } diff --git a/components/latch-n5/Cargo.toml b/components/latch-n5/Cargo.toml index 0aeb5b3..1140d89 100644 --- a/components/latch-n5/Cargo.toml +++ b/components/latch-n5/Cargo.toml @@ -9,4 +9,3 @@ crate-type = ["cdylib"] [dependencies] latch-n = { workspace = true } -wit-bindgen = { workspace = true } From 32aee1aefb3a74cb6ba010c2d6a14d05b500e44a Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 18 May 2026 15:51:09 -0400 Subject: [PATCH 04/11] Gate check preopens and directory-entry-streams The preopens interface and directory-entry-stream resource are a bit different as we allow the call and check if the returned value should be passed back to the caller. This allows the gate to filter the preopened filesystems, or files in a directory. Signed-off-by: Scott Andrews --- components/gate/src/lib.rs | 167 ++++++++++++++++++--------- components/latch-readonly/src/lib.rs | 2 + crates/latch-n/src/lib.rs | 30 ++++- wit/latch.wit | 12 +- 4 files changed, 154 insertions(+), 57 deletions(-) diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs index bc1b48e..aeeec0f 100644 --- a/components/gate/src/lib.rs +++ b/components/gate/src/lib.rs @@ -1,9 +1,13 @@ #![no_main] +use std::fmt::Display; +use std::path::PathBuf; use std::rc::Rc; use crate::componentized::filesystem::latch::Decision::{Abstain, Allow, Deny}; -use crate::componentized::filesystem::latch::{self, check, DescriptorOperation, Operation}; +use crate::componentized::filesystem::latch::{ + self, check, DescriptorOperation, DirectoryEntryStreamOperation, Operation, PreopensOperation, +}; use crate::exports::wasi::filesystem::preopens::Guest as Preopens; use crate::exports::wasi::filesystem::types::{ Advice, Descriptor, DescriptorBorrow, DescriptorFlags, DescriptorStat, DescriptorType, @@ -14,7 +18,6 @@ use crate::wasi::filesystem::preopens; use crate::wasi::filesystem::types; use crate::wasi::logging::logging::{log, Level}; -#[macro_export] macro_rules! warn { ($dst:expr, $($arg:tt)*) => { log(Level::Warn, "componentized-gate", &format!($dst, $($arg)*)); @@ -24,25 +27,44 @@ macro_rules! warn { }; } +macro_rules! trace { + ($dst:expr, $($arg:tt)*) => { + log(Level::Trace, "componentized-gate", &format!($dst, $($arg)*)); + }; + ($dst:expr) => { + log(Level::Trace, "componentized-gate", &format!($dst)); + }; +} + #[derive(Debug, Clone)] -struct FilesystemGate {} +struct GatedFilesystem {} -impl Preopens for FilesystemGate { +impl Preopens for GatedFilesystem { #[doc = " Return the set of preopened directories, and their path."] fn get_directories() -> Vec<(Descriptor, String)> { preopens::get_directories() .into_iter() + .filter(|(fs, path)| { + match check(&Operation::Preopens(PreopensOperation::GetDirectoriesItem((fs, path.clone())))) { + Allow => true, + Deny(code) => { + trace!("Denied REASON={code} OPERATION=wasi:filesystem/preopens#get-directories PATH={path}"); + false + } + Abstain => panic!("missing latch decision"), + } + }) .map(|(fd, path)| { - let fd = Descriptor::new(GateDescriptor::new(fd)); + let fd = Descriptor::new(GatedFileDescriptor::new(fd, PathBuf::from(path.clone()))); (fd, path) }) .collect() } } -impl Types for FilesystemGate { - type Descriptor = GateDescriptor; - type DirectoryEntryStream = GateDirectoryEntryStream; +impl Types for GatedFilesystem { + type Descriptor = GatedFileDescriptor; + type DirectoryEntryStream = GatedDirectoryEntryStream; #[doc = " Attempts to extract a filesystem-related `error-code` from the stream"] #[doc = " `error` provided."] @@ -60,17 +82,27 @@ impl Types for FilesystemGate { } #[derive(Debug, Clone)] -struct GateDescriptor { +struct GatedFileDescriptor { fd: Rc, + path: PathBuf, +} + +impl GatedFileDescriptor { + fn new(fd: types::Descriptor, path: PathBuf) -> Self { + Self { + fd: Rc::new(fd), + path, + } + } } -impl GateDescriptor { - fn new(fd: types::Descriptor) -> Self { - Self { fd: Rc::new(fd) } +impl Display for GatedFileDescriptor { + fn fmt(&self, d: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + d.write_fmt(format_args!("{}", self.path.display())) } } -impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { +impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " Return a stream for reading from a file, if available."] #[doc = ""] #[doc = " May fail with an error-code describing why the file cannot be read."] @@ -86,7 +118,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.read_via_stream(offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self:?} OFFSET={offset}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -106,7 +138,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.write_via_stream(offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self:?} OFFSET={offset}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -126,7 +158,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.append_via_stream().map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -152,7 +184,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .advise(offset, length, advice) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.advise FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.advise FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -172,7 +204,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.sync_data().map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -196,7 +228,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map(descriptor_flags_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -224,7 +256,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map(descriptor_type_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -242,7 +274,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.set_size(size).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self:?} SIZE={size}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self} SIZE={size}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -274,7 +306,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .set_times(data_access_timestamp, data_modification_timestamp) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self:?} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -299,7 +331,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.read(length, offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read FD={self:?} LENGTH={length} OFFSET={offset}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read FD={self} LENGTH={length} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -329,7 +361,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.write(&buffer, offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write FD={self:?} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write FD={self} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -353,10 +385,10 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { Allow => self .fd .read_directory() - .map(directory_entry_stream_map) + .map(|des| directory_entry_stream_map(des, self.path.clone())) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -376,7 +408,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.sync().map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -395,7 +427,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.create_directory_at(&path).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self:?} PATH={path}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -422,7 +454,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map(descriptor_stat_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -452,7 +484,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map(descriptor_stat_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self:?} PATH={path}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -494,7 +526,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self:?} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -532,7 +564,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map_err(error_code_map), Deny(code) => { warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self:?} OLD-PATH={old_path} NEW-PATH={new_path}", + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } @@ -579,11 +611,11 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self .fd - .open_at(path_flags, &path, open_flags, flags) - .map(descriptor_map) + .open_at(path_flags, &path.clone(), open_flags, flags) + .map(|descriptor| descriptor_map(descriptor, self.path.join(path))) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self:?} PATH={path}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -604,7 +636,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { Allow => self.fd.readlink_at(&path).map_err(error_code_map), Deny(code) => { warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self:?} PATH={path}", + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self} PATH={path}", ); Err(error_code_map(code)) } @@ -626,7 +658,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { ))) { Allow => self.fd.remove_directory_at(&path).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self:?} PATH={path}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -658,7 +690,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { } Deny(code) => { warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self:?} OLD-PATH={old_path} NEW-PATH={new_path}", + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } @@ -686,7 +718,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map_err(error_code_map), Deny(code) => { warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self:?} OLD-PATH={old_path} NEW-PATH={new_path}", + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } @@ -708,7 +740,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { Allow => self.fd.unlink_file_at(&path).map_err(error_code_map), Deny(code) => { warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self:?} PATH={path}", + "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self} PATH={path}", ); Err(error_code_map(code)) } @@ -757,7 +789,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map(metadata_hash_value_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self:?}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -787,7 +819,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { .map(metadata_hash_value_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self:?} PATH={path}"); + warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -796,23 +828,47 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GateDescriptor { } #[derive(Debug, Clone)] -struct GateDirectoryEntryStream { +struct GatedDirectoryEntryStream { des: Rc, + path: PathBuf, } -impl GateDirectoryEntryStream { - fn new(des: types::DirectoryEntryStream) -> Self { - Self { des: Rc::new(des) } +impl GatedDirectoryEntryStream { + fn new(des: types::DirectoryEntryStream, path: PathBuf) -> Self { + Self { + des: Rc::new(des), + path, + } } } -impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GateDirectoryEntryStream { +impl Display for GatedDirectoryEntryStream { + fn fmt(&self, d: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + d.write_fmt(format_args!("{}", self.path.display())) + } +} + +impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GatedDirectoryEntryStream { #[doc = " Read a single directory entry from a `directory-entry-stream`."] fn read_directory_entry(&self) -> Result, ErrorCode> { - self.des - .read_directory_entry() - .map(|de| de.map(directory_entry_map)) - .map_err(error_code_map) + match self.des.read_directory_entry() { + Ok(Some(de)) => { + match check(&Operation::DirectoryEntryStream(( + &self.des, + DirectoryEntryStreamOperation::ReadDirectoryEntry(de.clone()), + ))) { + Allow => Ok(Some(directory_entry_map(de))), + Deny(code) => { + trace!("Denied REASON={code} OPERATION=wasi:filesystem/types#directory-entry-stream.read-directory-entry STREAM={} ENTRY={}", self, de.name); + // continue reading the next entry transparently + self.read_directory_entry() + } + Abstain => panic!("missing latch decision"), + } + } + Ok(None) => Ok(None), + Err(code) => Err(error_code_map(code)), + } } } @@ -827,8 +883,8 @@ fn advice_map_in(advice: Advice) -> types::Advice { } } -fn descriptor_map(descriptor: types::Descriptor) -> Descriptor { - Descriptor::new(GateDescriptor::new(descriptor)) +fn descriptor_map(descriptor: types::Descriptor, path: PathBuf) -> Descriptor { + Descriptor::new(GatedFileDescriptor::new(descriptor, path)) } fn descriptor_flags_map(descriptor_flags: types::DescriptorFlags) -> DescriptorFlags { @@ -868,8 +924,9 @@ fn directory_entry_map(directory_entry: types::DirectoryEntry) -> DirectoryEntry fn directory_entry_stream_map( directory_entry_stream: types::DirectoryEntryStream, + path: PathBuf, ) -> DirectoryEntryStream { - DirectoryEntryStream::new(GateDirectoryEntryStream::new(directory_entry_stream)) + DirectoryEntryStream::new(GatedDirectoryEntryStream::new(directory_entry_stream, path)) } fn error_code_map(error_code: types::ErrorCode) -> ErrorCode { @@ -935,4 +992,4 @@ wit_bindgen::generate!({ generate_all }); -export!(FilesystemGate); +export!(GatedFilesystem); diff --git a/components/latch-readonly/src/lib.rs b/components/latch-readonly/src/lib.rs index 327e264..29e1d94 100644 --- a/components/latch-readonly/src/lib.rs +++ b/components/latch-readonly/src/lib.rs @@ -13,6 +13,7 @@ struct ReadOnlyLatch {} impl Latch for ReadOnlyLatch { fn check(operation: Operation) -> Decision { match operation { + Operation::Preopens(_) => Abstain, Operation::Descriptor((_, descriptor_operation)) => match descriptor_operation { DescriptorOperation::ReadViaStream(_) => Abstain, DescriptorOperation::WriteViaStream(_) => Deny(ReadOnly), @@ -58,6 +59,7 @@ impl Latch for ReadOnlyLatch { DescriptorOperation::MetadataHash => Abstain, DescriptorOperation::MetadataHashAt(_) => Abstain, }, + Operation::DirectoryEntryStream(_) => Abstain, } } } diff --git a/crates/latch-n/src/lib.rs b/crates/latch-n/src/lib.rs index 34cc1b4..234b3e3 100644 --- a/crates/latch-n/src/lib.rs +++ b/crates/latch-n/src/lib.rs @@ -9,7 +9,7 @@ use crate::bindings::{ DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, - DescriptorWriteViaStreamArgs, Operation, + DescriptorWriteViaStreamArgs, DirectoryEntryStreamOperation, Operation, PreopensOperation, }, }; @@ -30,9 +30,27 @@ pub fn check( fn operation_map(operation: Operation) -> latch::Operation { match operation { + Operation::Preopens(preopens_operation) => { + latch::Operation::Preopens(preopens_operation_map(preopens_operation)) + } Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( (descriptor, descriptor_operation_map(descriptor_operation)), ), + Operation::DirectoryEntryStream(( + directory_entry_stream, + directory_entry_stream_operation, + )) => latch::Operation::DirectoryEntryStream(( + directory_entry_stream, + directory_entry_stream_operation_map(directory_entry_stream_operation), + )), + } +} + +fn preopens_operation_map(preopens_operation: PreopensOperation) -> latch::PreopensOperation { + match preopens_operation { + PreopensOperation::GetDirectoriesItem((fd, path)) => { + latch::PreopensOperation::GetDirectoriesItem((fd, path)) + } } } @@ -262,6 +280,16 @@ fn descriptor_metadata_hash_at_args_map( } } +fn directory_entry_stream_operation_map( + directory_entry_stream_operation: DirectoryEntryStreamOperation, +) -> latch::DirectoryEntryStreamOperation { + match directory_entry_stream_operation { + DirectoryEntryStreamOperation::ReadDirectoryEntry(directory_entry) => { + latch::DirectoryEntryStreamOperation::ReadDirectoryEntry(directory_entry) + } + } +} + pub mod bindings { wit_bindgen::generate!({ path: "../../wit", diff --git a/wit/latch.wit b/wit/latch.wit index ac2c49f..12a57b8 100644 --- a/wit/latch.wit +++ b/wit/latch.wit @@ -1,6 +1,6 @@ interface latch { - use wasi:filesystem/types@0.2.6.{advice, descriptor, descriptor-flags, error-code, filesize, open-flags, new-timestamp, path-flags}; + use wasi:filesystem/types@0.2.6.{advice, descriptor, descriptor-flags, directory-entry, directory-entry-stream, error-code, filesize, open-flags, new-timestamp, path-flags}; record descriptor-get-directory-args { path: string, @@ -98,6 +98,10 @@ interface latch { path: string, } + variant preopens-operation { + get-directories-item(tuple, string>), + } + variant descriptor-operation { read-via-stream(descriptor-read-via-stream-args), write-via-stream(descriptor-write-via-stream-args), @@ -127,8 +131,14 @@ interface latch { metadata-hash-at(descriptor-metadata-hash-at-args), } + variant directory-entry-stream-operation { + read-directory-entry(directory-entry), + } + variant operation { + preopens(preopens-operation), descriptor(tuple, descriptor-operation>), + directory-entry-stream(tuple, directory-entry-stream-operation>), } variant decision { From 6b650d6aa76d35bebb58ec63c01dc435a690195b Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Sun, 31 May 2026 23:54:16 -0400 Subject: [PATCH 05/11] Add components that can be used to test th gate Signed-off-by: Scott Andrews --- Cargo.lock | 23 ++++-- Cargo.toml | 1 + Makefile | 66 +++++++++++++++- components/gate/Cargo.toml | 1 + components/gate/src/lib.rs | 131 ++++++++++++++++++++++++------- lib/tests/.gitignore | 2 + tests/filesystem-cli/Cargo.toml | 8 ++ tests/filesystem-cli/src/main.rs | 100 +++++++++++++++++++++++ tests/readonly.wac | 12 +++ 9 files changed, 310 insertions(+), 34 deletions(-) create mode 100644 lib/tests/.gitignore create mode 100644 tests/filesystem-cli/Cargo.toml create mode 100644 tests/filesystem-cli/src/main.rs create mode 100644 tests/readonly.wac diff --git a/Cargo.lock b/Cargo.lock index 792f52f..3cbc9f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,7 @@ dependencies = [ name = "gate" version = "0.1.0" dependencies = [ + "heck", "wit-bindgen", ] @@ -180,6 +181,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.14" @@ -312,18 +319,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -410,7 +417,13 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 69c0a33..b2fa826 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "components/*", "crates/*", + "tests/*", ] [workspace.dependencies] diff --git a/Makefile b/Makefile index 3ac7c7c..855d547 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ export RUST_BACKTRACE ?= 1 export WASMTIME_BACKTRACE_DETAILS ?= 1 COMPONENTS = $(shell ls -1 components) +TEST_COMPONENTS = $(shell ls -1 tests | grep -v '\.wac') .PHONY: all all: components @@ -13,9 +14,11 @@ clean: cargo clean rm -rf lib/*.wasm rm -rf lib/*.wasm.md + rm -rf lib/tests/*.wasm + rm -rf lib/tests/*.wasm.md .PHONY: components -components: $(foreach component,$(COMPONENTS),lib/$(component).wasm $(foreach component,$(COMPONENTS),lib/$(component).debug.wasm)) +components: $(foreach component,$(COMPONENTS),lib/$(component).wasm) $(foreach component,$(COMPONENTS),lib/$(component).debug.wasm) define BUILD_COMPONENT @@ -36,6 +39,67 @@ endef $(foreach component,$(COMPONENTS),$(eval $(call BUILD_COMPONENT,$(component)))) +define TEST_COMPONENT + +lib/tests/$1.wasm: Cargo.toml Cargo.lock wit/deps $(shell find tests/$1 -type f) + cargo component build -p $1 --target wasm32-wasip2 --release + cp target/wasm32-wasip2/release/$1.wasm lib/tests/$1.wasm + +lib/tests/$1.debug.wasm: Cargo.toml Cargo.lock wit/deps $(shell find tests/$1 -type f) + cargo component build -p $1 --target wasm32-wasip2 + cp target/wasm32-wasip2/debug/$1.wasm lib/tests/$1.debug.wasm + +endef + +$(foreach component,$(TEST_COMPONENTS),$(eval $(call TEST_COMPONENT,$(component)))) + +lib/tests/logging-to-stdout.wasm: + wkg oci pull ghcr.io/componentized/logging/to-stdout:v0.2.1 -o "lib/tests/logging-to-stdout.wasm" + +lib/tests/allow.wasm: lib/gate.wasm lib/latch-allow.wasm + wac plug lib/gate.wasm \ + --plug lib/latch-allow.wasm \ + -o lib/tests/allow.wasm + +lib/tests/filesystem-cli-allow.wasm: lib/tests/filesystem-cli.wasm lib/tests/allow.wasm lib/tests/logging-to-stdout.wasm + wac plug lib/tests/filesystem-cli.wasm \ + --plug <( \ + wac plug lib/tests/allow.wasm \ + --plug lib/tests/logging-to-stdout.wasm \ + ) \ + -o lib/tests/filesystem-cli-allow.wasm + +lib/tests/deny.wasm: lib/gate.wasm lib/latch-deny.wasm + wac plug lib/gate.wasm \ + --plug lib/latch-deny.wasm \ + -o lib/tests/deny.wasm + +lib/tests/filesystem-cli-deny.wasm: lib/tests/filesystem-cli.wasm lib/tests/deny.wasm lib/tests/logging-to-stdout.wasm + wac plug lib/tests/filesystem-cli.wasm \ + --plug <( \ + wac plug lib/tests/deny.wasm \ + --plug lib/tests/logging-to-stdout.wasm \ + ) \ + -o lib/tests/filesystem-cli-deny.wasm + +lib/tests/readonly.wasm: tests/readonly.wac lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-allow.wasm + wac compose -o lib/tests/readonly.wasm \ + -d componentized:gate="lib/gate.wasm" \ + -d componentized:latch-n2="lib/latch-n2.wasm" \ + -d componentized:latch-readonly="lib/latch-readonly.wasm" \ + -d componentized:latch-allow="lib/latch-allow.wasm" \ + tests/readonly.wac + +lib/tests/filesystem-cli-readonly.wasm: lib/tests/filesystem-cli.wasm lib/tests/readonly.wasm lib/tests/logging-to-stdout.wasm + wac plug lib/tests/filesystem-cli.wasm \ + --plug <( \ + wac plug lib/tests/readonly.wasm \ + --plug lib/tests/logging-to-stdout.wasm \ + ) \ + -o lib/tests/filesystem-cli-readonly.wasm + +.PHONY: tests +tests: $(foreach component,$(TEST_COMPONENTS),lib/tests/$(component).wasm) lib/tests/filesystem-cli-allow.wasm lib/tests/filesystem-cli-deny.wasm lib/tests/filesystem-cli-readonly.wasm .PHONY: wit wit: wit/deps diff --git a/components/gate/Cargo.toml b/components/gate/Cargo.toml index 51d9cae..c238b21 100644 --- a/components/gate/Cargo.toml +++ b/components/gate/Cargo.toml @@ -9,3 +9,4 @@ crate-type = ["cdylib"] [dependencies] wit-bindgen = { workspace = true } +heck = "0.5" diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs index aeeec0f..d6f11d9 100644 --- a/components/gate/src/lib.rs +++ b/components/gate/src/lib.rs @@ -4,6 +4,8 @@ use std::fmt::Display; use std::path::PathBuf; use std::rc::Rc; +use heck::ToKebabCase; + use crate::componentized::filesystem::latch::Decision::{Abstain, Allow, Deny}; use crate::componentized::filesystem::latch::{ self, check, DescriptorOperation, DirectoryEntryStreamOperation, Operation, PreopensOperation, @@ -48,7 +50,8 @@ impl Preopens for GatedFilesystem { match check(&Operation::Preopens(PreopensOperation::GetDirectoriesItem((fs, path.clone())))) { Allow => true, Deny(code) => { - trace!("Denied REASON={code} OPERATION=wasi:filesystem/preopens#get-directories PATH={path}"); + let reason = error_code_display(code); + trace!("Denied REASON={reason} OPERATION=wasi:filesystem/preopens#get-directories PATH={path}"); false } Abstain => panic!("missing latch decision"), @@ -118,7 +121,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.read_via_stream(offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self} OFFSET={offset}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -138,7 +142,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.write_via_stream(offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self} OFFSET={offset}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -158,7 +163,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.append_via_stream().map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -184,7 +190,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .advise(offset, length, advice) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.advise FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.advise FD={self} OFFSET={offset} LENGTH={length} ADVICE={advice}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -204,7 +211,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.sync_data().map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -228,7 +236,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(descriptor_flags_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -256,7 +265,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(descriptor_type_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -274,7 +284,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.set_size(size).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self} SIZE={size}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self} SIZE={size}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -306,7 +317,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .set_times(data_access_timestamp, data_modification_timestamp) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -331,7 +343,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.read(length, offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read FD={self} LENGTH={length} OFFSET={offset}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read FD={self} LENGTH={length} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -361,7 +374,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.write(&buffer, offset).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.write FD={self} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write FD={self} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -388,7 +402,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(|des| directory_entry_stream_map(des, self.path.clone())) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -408,7 +423,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.sync().map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.sync FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -427,7 +443,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.create_directory_at(&path).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self} PATH={path}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -454,7 +471,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(descriptor_stat_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -484,7 +502,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(descriptor_stat_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self} PATH={path}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self} PATH-FLAGS={path_flags} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -526,7 +545,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH-FLAGS={path_flags} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -563,8 +583,9 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ) .map_err(error_code_map), Deny(code) => { + let reason = error_code_display(code); warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", + "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} OLD-PATH-FLAGS={old_path_flags} NEW-PATH={new_path}", ); Err(error_code_map(code)) } @@ -615,7 +636,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(|descriptor| descriptor_map(descriptor, self.path.join(path))) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self} PATH={path}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self} PATH-FLAGS={path_flags} PATH={path} OPEN-FLAGS={open_flags} FLAGS={flags}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -635,8 +657,9 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.readlink_at(&path).map_err(error_code_map), Deny(code) => { + let reason = error_code_display(code); warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self} PATH={path}", + "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self} PATH={path}", ); Err(error_code_map(code)) } @@ -658,7 +681,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.remove_directory_at(&path).map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self} PATH={path}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -689,8 +713,9 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map_err(error_code_map) } Deny(code) => { + let reason = error_code_display(code); warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", + "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } @@ -717,8 +742,9 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .symlink_at(&old_path, &new_path) .map_err(error_code_map), Deny(code) => { + let reason = error_code_display(code); warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", + "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } @@ -739,8 +765,9 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ))) { Allow => self.fd.unlink_file_at(&path).map_err(error_code_map), Deny(code) => { + let reason = error_code_display(code); warn!( - "Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self} PATH={path}", + "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self} PATH={path}", ); Err(error_code_map(code)) } @@ -789,7 +816,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(metadata_hash_value_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -819,7 +847,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .map(metadata_hash_value_map) .map_err(error_code_map), Deny(code) => { - warn!("Denied REASON={code} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self} PATH={path}"); + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self} PATH={path} PATH-FLAGS={path_flags}"); Err(error_code_map(code)) } Abstain => panic!("missing latch decision"), @@ -859,7 +888,8 @@ impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GatedDirect ))) { Allow => Ok(Some(directory_entry_map(de))), Deny(code) => { - trace!("Denied REASON={code} OPERATION=wasi:filesystem/types#directory-entry-stream.read-directory-entry STREAM={} ENTRY={}", self, de.name); + let reason = error_code_display(code); + trace!("Denied REASON={reason} OPERATION=wasi:filesystem/types#directory-entry-stream.read-directory-entry STREAM={} ENTRY={}", self, de.name); // continue reading the next entry transparently self.read_directory_entry() } @@ -971,6 +1001,15 @@ fn error_code_map(error_code: types::ErrorCode) -> ErrorCode { } } +fn error_code_display(error_code: types::ErrorCode) -> String { + error_code + .to_string() + .splitn(2, ' ') + .next() + .unwrap_or("") + .to_kebab_case() +} + fn metadata_hash_value_map(metadata_hash_value: types::MetadataHashValue) -> MetadataHashValue { MetadataHashValue { lower: metadata_hash_value.lower, @@ -986,6 +1025,42 @@ fn new_timestamp_map_in(timestamp: NewTimestamp) -> types::NewTimestamp { } } +impl Display for types::Advice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string().to_kebab_case()) + } +} + +impl Display for types::DescriptorFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let names: Vec = self + .iter_names() + .map(|(name, _flags)| name.to_kebab_case()) + .collect(); + f.write_fmt(format_args!("({})", &names.join("|"))) + } +} + +impl Display for types::OpenFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let names: Vec = self + .iter_names() + .map(|(name, _flags)| name.to_kebab_case()) + .collect(); + f.write_fmt(format_args!("({})", &names.join("|"))) + } +} + +impl Display for types::PathFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let names: Vec = self + .iter_names() + .map(|(name, _flags)| name.to_kebab_case()) + .collect(); + f.write_fmt(format_args!("({})", &names.join("|"))) + } +} + wit_bindgen::generate!({ path: "../../wit", world: "filesystem", diff --git a/lib/tests/.gitignore b/lib/tests/.gitignore new file mode 100644 index 0000000..902f891 --- /dev/null +++ b/lib/tests/.gitignore @@ -0,0 +1,2 @@ +*.md +*.wasm diff --git a/tests/filesystem-cli/Cargo.toml b/tests/filesystem-cli/Cargo.toml new file mode 100644 index 0000000..6b2ecad --- /dev/null +++ b/tests/filesystem-cli/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "filesystem-cli" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[dependencies] +clap = { version = "4.6.1", features = ["derive"] } diff --git a/tests/filesystem-cli/src/main.rs b/tests/filesystem-cli/src/main.rs new file mode 100644 index 0000000..1eda915 --- /dev/null +++ b/tests/filesystem-cli/src/main.rs @@ -0,0 +1,100 @@ +use std::{ + fs::{self, File, OpenOptions}, + io, + path::PathBuf, +}; + +use clap::{Parser, Subcommand}; + +/// componentized filesystem CLI +#[derive(Debug, Parser)] // requires `derive` feature +#[command(name = "filesystem")] +#[command(about = "componentized filesystem CLI", long_about = None)] +struct FilesystemCli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand, Clone)] +enum Commands { + /// List items in a directory + List { + #[arg()] + path: PathBuf, + }, + + /// Read a file + #[command(arg_required_else_help = true)] + Read { + #[arg()] + path: PathBuf, + }, + + /// Write a file + #[command(arg_required_else_help = true)] + Write { + #[arg()] + path: PathBuf, + }, + + /// Append to a file + #[command(arg_required_else_help = true)] + Append { + #[arg()] + path: PathBuf, + }, + + /// Move a file + #[command(arg_required_else_help = true)] + Move { + #[arg()] + from: PathBuf, + + #[arg()] + to: PathBuf, + }, + + /// Remove a file + #[command(arg_required_else_help = true)] + Remove { + #[arg()] + path: PathBuf, + }, +} + +fn main() -> Result<(), std::io::Error> { + match FilesystemCli::parse().command { + Commands::List { path } => { + for entry in fs::read_dir(path)? { + let entry = entry?; + println!("{:?}", entry.file_name()); + } + Ok(()) + } + Commands::Read { path } => { + let mut from = OpenOptions::new().read(true).open(path)?; + let mut to = io::stdout(); + io::copy(&mut from, &mut to)?; + Ok(()) + } + Commands::Write { path } => { + let mut from = io::stdin(); + let mut to = File::create(path)?; + io::copy(&mut from, &mut to)?; + Ok(()) + } + Commands::Append { path } => { + let mut from = io::stdin(); + let mut to = OpenOptions::new().create(true).append(true).open(path)?; + io::copy(&mut from, &mut to)?; + Ok(()) + } + Commands::Move { from, to } => fs::rename(from, to), + Commands::Remove { path } => { + if path.is_dir() { + return fs::remove_dir_all(path); + } + fs::remove_file(path) + } + } +} diff --git a/tests/readonly.wac b/tests/readonly.wac new file mode 100644 index 0000000..df73c7c --- /dev/null +++ b/tests/readonly.wac @@ -0,0 +1,12 @@ +package componentized:filesystem; + +let readonly = new componentized:gate { + latch: new componentized:latch-n2 { + latch: new componentized:latch-readonly { ... }.latch, + latch1: new componentized:latch-allow { ... }.latch, + ... + }.latch, + ... +}; + +export readonly...; From b90096d14a2399287b95b721d5fabd1878c5e8c9 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 09:52:45 -0400 Subject: [PATCH 06/11] Polish names - allow -> some(permitted) - deny -> some(denied) - abstain -> none - check -> authorize - latch-allow -> latch-permit-all - latch-deny -> latch-deny-all Signed-off-by: Scott Andrews --- Cargo.lock | 7 + Makefile | 22 +- README.md | 10 +- components/gate/src/lib.rs | 229 +++++++++--------- components/latch-allow/README.md | 3 - .../Cargo.toml | 2 +- .../{latch-deny => latch-deny-all}/README.md | 2 +- .../{latch-deny => latch-deny-all}/src/lib.rs | 10 +- components/latch-n2/src/lib.rs | 6 +- components/latch-n3/src/lib.rs | 6 +- components/latch-n4/src/lib.rs | 11 +- components/latch-n5/src/lib.rs | 16 +- .../Cargo.toml | 2 +- components/latch-permit-all/README.md | 3 + .../src/lib.rs | 10 +- components/latch-readonly/src/lib.rs | 62 ++--- crates/latch-n/src/lib.rs | 18 +- tests/readonly.wac | 2 +- wit/latch.wit | 17 +- 19 files changed, 225 insertions(+), 213 deletions(-) delete mode 100644 components/latch-allow/README.md rename components/{latch-allow => latch-deny-all}/Cargo.toml (86%) rename components/{latch-deny => latch-deny-all}/README.md (75%) rename components/{latch-deny => latch-deny-all}/src/lib.rs (59%) rename components/{latch-deny => latch-permit-all}/Cargo.toml (85%) create mode 100644 components/latch-permit-all/README.md rename components/{latch-allow => latch-permit-all}/src/lib.rs (56%) diff --git a/Cargo.lock b/Cargo.lock index 3cbc9f7..3152a6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,13 @@ dependencies = [ "latch-n", ] +[[package]] +name = "latch-permit-all" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "latch-readonly" version = "0.1.0" diff --git a/Makefile b/Makefile index 855d547..cb1e961 100644 --- a/Makefile +++ b/Makefile @@ -56,22 +56,22 @@ $(foreach component,$(TEST_COMPONENTS),$(eval $(call TEST_COMPONENT,$(component) lib/tests/logging-to-stdout.wasm: wkg oci pull ghcr.io/componentized/logging/to-stdout:v0.2.1 -o "lib/tests/logging-to-stdout.wasm" -lib/tests/allow.wasm: lib/gate.wasm lib/latch-allow.wasm +lib/tests/permit.wasm: lib/gate.wasm lib/latch-permit-all.wasm wac plug lib/gate.wasm \ - --plug lib/latch-allow.wasm \ - -o lib/tests/allow.wasm + --plug lib/latch-permit-all.wasm \ + -o lib/tests/permit.wasm -lib/tests/filesystem-cli-allow.wasm: lib/tests/filesystem-cli.wasm lib/tests/allow.wasm lib/tests/logging-to-stdout.wasm +lib/tests/filesystem-cli-permit.wasm: lib/tests/filesystem-cli.wasm lib/tests/permit.wasm lib/tests/logging-to-stdout.wasm wac plug lib/tests/filesystem-cli.wasm \ --plug <( \ - wac plug lib/tests/allow.wasm \ + wac plug lib/tests/permit.wasm \ --plug lib/tests/logging-to-stdout.wasm \ ) \ - -o lib/tests/filesystem-cli-allow.wasm + -o lib/tests/filesystem-cli-permit.wasm -lib/tests/deny.wasm: lib/gate.wasm lib/latch-deny.wasm +lib/tests/deny.wasm: lib/gate.wasm lib/latch-deny-all.wasm wac plug lib/gate.wasm \ - --plug lib/latch-deny.wasm \ + --plug lib/latch-deny-all.wasm \ -o lib/tests/deny.wasm lib/tests/filesystem-cli-deny.wasm: lib/tests/filesystem-cli.wasm lib/tests/deny.wasm lib/tests/logging-to-stdout.wasm @@ -82,12 +82,12 @@ lib/tests/filesystem-cli-deny.wasm: lib/tests/filesystem-cli.wasm lib/tests/deny ) \ -o lib/tests/filesystem-cli-deny.wasm -lib/tests/readonly.wasm: tests/readonly.wac lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-allow.wasm +lib/tests/readonly.wasm: tests/readonly.wac lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-permit-all.wasm wac compose -o lib/tests/readonly.wasm \ -d componentized:gate="lib/gate.wasm" \ -d componentized:latch-n2="lib/latch-n2.wasm" \ -d componentized:latch-readonly="lib/latch-readonly.wasm" \ - -d componentized:latch-allow="lib/latch-allow.wasm" \ + -d componentized:latch-permit="lib/latch-permit-all.wasm" \ tests/readonly.wac lib/tests/filesystem-cli-readonly.wasm: lib/tests/filesystem-cli.wasm lib/tests/readonly.wasm lib/tests/logging-to-stdout.wasm @@ -99,7 +99,7 @@ lib/tests/filesystem-cli-readonly.wasm: lib/tests/filesystem-cli.wasm lib/tests/ -o lib/tests/filesystem-cli-readonly.wasm .PHONY: tests -tests: $(foreach component,$(TEST_COMPONENTS),lib/tests/$(component).wasm) lib/tests/filesystem-cli-allow.wasm lib/tests/filesystem-cli-deny.wasm lib/tests/filesystem-cli-readonly.wasm +tests: $(foreach component,$(TEST_COMPONENTS),lib/tests/$(component).wasm) lib/tests/filesystem-cli-permit.wasm lib/tests/filesystem-cli-deny.wasm lib/tests/filesystem-cli-readonly.wasm .PHONY: wit wit: wit/deps diff --git a/README.md b/README.md index 3d47b73..eccfb36 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ A collection of utility components that remix wasi:filesystem types and interfac - [`chroot`](./components/chroot/) - [`gate`](./components/gate/) -- [`latch-2`](./components/latch-2/) -- [`latch-3`](./components/latch-3/) -- [`latch-4`](./components/latch-4/) -- [`latch-allow`](./components/latch-allow/) -- [`latch-deny`](./components/latch-deny/) +- [`latch-n2`](./components/latch-n2/) +- [`latch-n3`](./components/latch-n3/) +- [`latch-n4`](./components/latch-n4/) +- [`latch-deny-all`](./components/latch-deny-all/) +- [`latch-permit-all`](./components/latch-permit-all/) - [`latch-readonly`](./components/latch-readonly/) - ~~[`readonly`](./components/readonly/)~~ (deprecated, favor gate with readonly latch) - [`tracing`](./components/tracing/) diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs index d6f11d9..c46fc23 100644 --- a/components/gate/src/lib.rs +++ b/components/gate/src/lib.rs @@ -6,9 +6,10 @@ use std::rc::Rc; use heck::ToKebabCase; -use crate::componentized::filesystem::latch::Decision::{Abstain, Allow, Deny}; +use crate::componentized::filesystem::latch::Decision::{Denied, Permitted}; use crate::componentized::filesystem::latch::{ - self, check, DescriptorOperation, DirectoryEntryStreamOperation, Operation, PreopensOperation, + self, authorize, DescriptorOperation, DirectoryEntryStreamOperation, Operation, + PreopensOperation, }; use crate::exports::wasi::filesystem::preopens::Guest as Preopens; use crate::exports::wasi::filesystem::types::{ @@ -47,14 +48,14 @@ impl Preopens for GatedFilesystem { preopens::get_directories() .into_iter() .filter(|(fs, path)| { - match check(&Operation::Preopens(PreopensOperation::GetDirectoriesItem((fs, path.clone())))) { - Allow => true, - Deny(code) => { + match authorize(&Operation::Preopens(PreopensOperation::GetDirectoriesItem((fs, path.clone())))) { + Some(Permitted) => true, + Some(Denied(code)) => { let reason = error_code_display(code); trace!("Denied REASON={reason} OPERATION=wasi:filesystem/preopens#get-directories PATH={path}"); false } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } }) .map(|(fd, path)| { @@ -115,17 +116,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This allows using `read-stream`, which is similar to `read` in POSIX."] fn read_via_stream(&self, offset: Filesize) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::ReadViaStream(latch::DescriptorReadViaStreamArgs { offset }), ))) { - Allow => self.fd.read_via_stream(offset).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.read_via_stream(offset).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -136,17 +137,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " Note: This allows using `write-stream`, which is similar to `write` in"] #[doc = " POSIX."] fn write_via_stream(&self, offset: Filesize) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::WriteViaStream(latch::DescriptorWriteViaStreamArgs { offset }), ))) { - Allow => self.fd.write_via_stream(offset).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.write_via_stream(offset).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -157,17 +158,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " Note: This allows using `write-stream`, which is similar to `write` with"] #[doc = " `O_APPEND` in in POSIX."] fn append_via_stream(&self) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::AppendViaStream, ))) { - Allow => self.fd.append_via_stream().map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.append_via_stream().map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -177,7 +178,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn advise(&self, offset: Filesize, length: Filesize, advice: Advice) -> Result<(), ErrorCode> { let advice = advice_map_in(advice); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::Advise(latch::DescriptorAdviseArgs { offset, @@ -185,16 +186,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { advice, }), ))) { - Allow => self + Some(Permitted) => self .fd .advise(offset, length, advice) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.advise FD={self} OFFSET={offset} LENGTH={length} ADVICE={advice}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -205,17 +206,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `fdatasync` in POSIX."] fn sync_data(&self) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::SyncData, ))) { - Allow => self.fd.sync_data().map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.sync_data().map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -226,21 +227,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " Note: This returns the value that was the `fs_flags` value returned"] #[doc = " from `fdstat_get` in earlier versions of WASI."] fn get_flags(&self) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::GetFlags, ))) { - Allow => self + Some(Permitted) => self .fd .get_flags() .map(descriptor_flags_map) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -255,21 +256,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " Note: This returns the value that was the `fs_filetype` value returned"] #[doc = " from `fdstat_get` in earlier versions of WASI."] fn get_type(&self) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::GetType, ))) { - Allow => self + Some(Permitted) => self .fd .get_type() .map(descriptor_type_map) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -278,17 +279,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This was called `fd_filestat_set_size` in earlier versions of WASI."] fn set_size(&self, size: Filesize) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::SetSize(latch::DescriptorSetSizeArgs { size }), ))) { - Allow => self.fd.set_size(size).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.set_size(size).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self} SIZE={size}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -305,23 +306,23 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let data_access_timestamp = new_timestamp_map_in(data_access_timestamp); let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::SetTimes(latch::DescriptorSetTimesArgs { data_access_timestamp, data_modification_timestamp, }), ))) { - Allow => self + Some(Permitted) => self .fd .set_times(data_access_timestamp, data_modification_timestamp) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -337,17 +338,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `pread` in POSIX."] fn read(&self, length: Filesize, offset: Filesize) -> Result<(Vec, bool), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::Read(latch::DescriptorReadArgs { length, offset }), ))) { - Allow => self.fd.read(length, offset).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.read(length, offset).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read FD={self} LENGTH={length} OFFSET={offset}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -365,20 +366,20 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .len() .try_into() .expect("buffer length 64-bits or less"); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::Write(latch::DescriptorWriteArgs { buffer_length, offset, }), ))) { - Allow => self.fd.write(&buffer, offset).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.write(&buffer, offset).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write FD={self} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -392,21 +393,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " directory. Multiple streams may be active on the same directory, and they"] #[doc = " do not interfere with each other."] fn read_directory(&self) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::ReadDirectory, ))) { - Allow => self + Some(Permitted) => self .fd .read_directory() .map(|des| directory_entry_stream_map(des, self.path.clone())) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -417,17 +418,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `fsync` in POSIX."] fn sync(&self) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::Sync, ))) { - Allow => self.fd.sync().map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.sync().map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -435,19 +436,19 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `mkdirat` in POSIX."] fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::CreateDirectoryAt(latch::DescriptorCreateDirectoryAtArgs { path: path.clone(), }), ))) { - Allow => self.fd.create_directory_at(&path).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.create_directory_at(&path).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -461,21 +462,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This was called `fd_filestat_get` in earlier versions of WASI."] fn stat(&self) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::Stat, ))) { - Allow => self + Some(Permitted) => self .fd .stat() .map(descriptor_stat_map) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -489,24 +490,24 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn stat_at(&self, path_flags: PathFlags, path: String) -> Result { let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::StatAt(latch::DescriptorStatAtArgs { path_flags, path: path.clone(), }), ))) { - Allow => self + Some(Permitted) => self .fd .stat_at(path_flags, &path) .map(descriptor_stat_map) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self} PATH-FLAGS={path_flags} PATH={path}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -526,7 +527,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); let data_access_timestamp = new_timestamp_map_in(data_access_timestamp); let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::SetTimesAt(latch::DescriptorSetTimesAtArgs { path_flags, @@ -535,7 +536,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, }), ))) { - Allow => self + Some(Permitted) => self .fd .set_times_at( path_flags, @@ -544,12 +545,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, ) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH-FLAGS={path_flags} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -564,7 +565,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: String, ) -> Result<(), ErrorCode> { let old_path_flags = types::PathFlags::from_bits(old_path_flags.bits()).unwrap(); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::LinkAt(latch::DescriptorLinkAtArgs { old_path_flags, @@ -573,7 +574,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Allow => self + Some(Permitted) => self .fd .link_at( old_path_flags, @@ -582,14 +583,14 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &new_path, ) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} OLD-PATH-FLAGS={old_path_flags} NEW-PATH={new_path}", ); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -621,7 +622,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); let open_flags = types::OpenFlags::from_bits(open_flags.bits()).unwrap(); let flags = types::DescriptorFlags::from_bits(flags.bits()).unwrap(); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::OpenAt(latch::DescriptorOpenAtArgs { path_flags, @@ -630,17 +631,17 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { flags, }), ))) { - Allow => self + Some(Permitted) => self .fd .open_at(path_flags, &path.clone(), open_flags, flags) .map(|descriptor| descriptor_map(descriptor, self.path.join(path))) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self} PATH-FLAGS={path_flags} PATH={path} OPEN-FLAGS={open_flags} FLAGS={flags}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -651,19 +652,19 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `readlinkat` in POSIX."] fn readlink_at(&self, path: String) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::ReadlinkAt(latch::DescriptorReadlinkAtArgs { path: path.clone() }), ))) { - Allow => self.fd.readlink_at(&path).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.readlink_at(&path).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self} PATH={path}", ); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -673,19 +674,19 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX."] fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::RemoveDirectoryAt(latch::DescriptorRemoveDirectoryAtArgs { path: path.clone(), }), ))) { - Allow => self.fd.remove_directory_at(&path).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.remove_directory_at(&path).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -698,7 +699,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_descriptor: DescriptorBorrow<'_>, new_path: String, ) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::RenameAt(latch::DescriptorRenameAtArgs { old_path: old_path.clone(), @@ -706,20 +707,20 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Allow => { + Some(Permitted) => { let new_descriptor: &Self = new_descriptor.get(); self.fd .rename_at(&old_path, &new_descriptor.fd, &new_path) .map_err(error_code_map) } - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -730,25 +731,25 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " Note: This is similar to `symlinkat` in POSIX."] fn symlink_at(&self, old_path: String, new_path: String) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::SymlinkAt(latch::DescriptorSymlinkAtArgs { old_path: old_path.clone(), new_path: new_path.clone(), }), ))) { - Allow => self + Some(Permitted) => self .fd .symlink_at(&old_path, &new_path) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -757,21 +758,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = " Return `error-code::is-directory` if the path refers to a directory."] #[doc = " Note: This is similar to `unlinkat(fd, path, 0)` in POSIX."] fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::UnlinkFileAt(latch::DescriptorUnlinkFileAtArgs { path: path.clone(), }), ))) { - Allow => self.fd.unlink_file_at(&path).map_err(error_code_map), - Deny(code) => { + Some(Permitted) => self.fd.unlink_file_at(&path).map_err(error_code_map), + Some(Denied(code)) => { let reason = error_code_display(code); warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self} PATH={path}", ); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -806,21 +807,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { #[doc = ""] #[doc = " However, none of these is required."] fn metadata_hash(&self) -> Result { - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::MetadataHash, ))) { - Allow => self + Some(Permitted) => self .fd .metadata_hash() .map(metadata_hash_value_map) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } @@ -834,24 +835,24 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: String, ) -> Result { let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); - match check(&Operation::Descriptor(( + match authorize(&Operation::Descriptor(( &self.fd, DescriptorOperation::MetadataHashAt(latch::DescriptorMetadataHashAtArgs { path_flags, path: path.clone(), }), ))) { - Allow => self + Some(Permitted) => self .fd .metadata_hash_at(path_flags, &path) .map(metadata_hash_value_map) .map_err(error_code_map), - Deny(code) => { + Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self} PATH={path} PATH-FLAGS={path_flags}"); Err(error_code_map(code)) } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } } @@ -882,18 +883,18 @@ impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GatedDirect fn read_directory_entry(&self) -> Result, ErrorCode> { match self.des.read_directory_entry() { Ok(Some(de)) => { - match check(&Operation::DirectoryEntryStream(( + match authorize(&Operation::DirectoryEntryStream(( &self.des, DirectoryEntryStreamOperation::ReadDirectoryEntry(de.clone()), ))) { - Allow => Ok(Some(directory_entry_map(de))), - Deny(code) => { + Some(Permitted) => Ok(Some(directory_entry_map(de))), + Some(Denied(code)) => { let reason = error_code_display(code); trace!("Denied REASON={reason} OPERATION=wasi:filesystem/types#directory-entry-stream.read-directory-entry STREAM={} ENTRY={}", self, de.name); // continue reading the next entry transparently self.read_directory_entry() } - Abstain => panic!("missing latch decision"), + None => panic!("missing required latch decision"), } } Ok(None) => Ok(None), diff --git a/components/latch-allow/README.md b/components/latch-allow/README.md deleted file mode 100644 index 08965e4..0000000 --- a/components/latch-allow/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `latch-allow` - -Filesystem latch that implicitly allows all operations. diff --git a/components/latch-allow/Cargo.toml b/components/latch-deny-all/Cargo.toml similarity index 86% rename from components/latch-allow/Cargo.toml rename to components/latch-deny-all/Cargo.toml index 8127f2a..741f659 100644 --- a/components/latch-allow/Cargo.toml +++ b/components/latch-deny-all/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "latch-allow" +name = "latch-deny-all" version = "0.1.0" edition = "2021" license = "Apache-2.0" diff --git a/components/latch-deny/README.md b/components/latch-deny-all/README.md similarity index 75% rename from components/latch-deny/README.md rename to components/latch-deny-all/README.md index d46f427..c619098 100644 --- a/components/latch-deny/README.md +++ b/components/latch-deny-all/README.md @@ -1,3 +1,3 @@ -# `latch-deny` +# `latch-deny-all` Filesystem latch that implicitly denies all operations. diff --git a/components/latch-deny/src/lib.rs b/components/latch-deny-all/src/lib.rs similarity index 59% rename from components/latch-deny/src/lib.rs rename to components/latch-deny-all/src/lib.rs index 05dd59a..4133be2 100644 --- a/components/latch-deny/src/lib.rs +++ b/components/latch-deny-all/src/lib.rs @@ -5,11 +5,11 @@ use crate::{ wasi::filesystem::types::ErrorCode, }; -struct DenyLatch {} +struct DenyAllLatch {} -impl Latch for DenyLatch { - fn check(_: Operation) -> Decision { - Decision::Deny(ErrorCode::NotPermitted) +impl Latch for DenyAllLatch { + fn authorize(_: Operation) -> Option { + Some(Decision::Denied(ErrorCode::NotPermitted)) } } @@ -19,4 +19,4 @@ wit_bindgen::generate!({ generate_all }); -export!(DenyLatch); +export!(DenyAllLatch); diff --git a/components/latch-n2/src/lib.rs b/components/latch-n2/src/lib.rs index e157ad0..a1366d4 100644 --- a/components/latch-n2/src/lib.rs +++ b/components/latch-n2/src/lib.rs @@ -9,9 +9,9 @@ struct LatchN2 {} impl Latch for LatchN2 { #[allow(async_fn_in_trait)] - fn check(operation: Operation<'_>) -> Decision { - let checks = vec![latch0::check, latch1::check]; - latch_n::check(operation, checks) + fn authorize(operation: Operation<'_>) -> Option { + let authorizers = vec![latch0::authorize, latch1::authorize]; + latch_n::authorize(operation, authorizers) } } diff --git a/components/latch-n3/src/lib.rs b/components/latch-n3/src/lib.rs index 18f89a8..ff3b838 100644 --- a/components/latch-n3/src/lib.rs +++ b/components/latch-n3/src/lib.rs @@ -9,9 +9,9 @@ struct LatchN3 {} impl Latch for LatchN3 { #[allow(async_fn_in_trait)] - fn check(operation: Operation<'_>) -> Decision { - let checks = vec![latch0::check, latch1::check, latch2::check]; - latch_n::check(operation, checks) + fn authorize(operation: Operation<'_>) -> Option { + let authorizers = vec![latch0::authorize, latch1::authorize, latch2::authorize]; + latch_n::authorize(operation, authorizers) } } diff --git a/components/latch-n4/src/lib.rs b/components/latch-n4/src/lib.rs index e733807..5418cc7 100644 --- a/components/latch-n4/src/lib.rs +++ b/components/latch-n4/src/lib.rs @@ -9,9 +9,14 @@ struct LatchN4 {} impl Latch for LatchN4 { #[allow(async_fn_in_trait)] - fn check(operation: Operation<'_>) -> Decision { - let checks = vec![latch0::check, latch1::check, latch2::check, latch3::check]; - latch_n::check(operation, checks) + fn authorize(operation: Operation<'_>) -> Option { + let authorizers = vec![ + latch0::authorize, + latch1::authorize, + latch2::authorize, + latch3::authorize, + ]; + latch_n::authorize(operation, authorizers) } } diff --git a/components/latch-n5/src/lib.rs b/components/latch-n5/src/lib.rs index e76e136..c257021 100644 --- a/components/latch-n5/src/lib.rs +++ b/components/latch-n5/src/lib.rs @@ -11,15 +11,15 @@ struct LatchN5 {} impl Latch for LatchN5 { #[allow(async_fn_in_trait)] - fn check(operation: Operation<'_>) -> Decision { - let checks = vec![ - latch0::check, - latch1::check, - latch2::check, - latch3::check, - latch4::check, + fn authorize(operation: Operation<'_>) -> Option { + let authorizers = vec![ + latch0::authorize, + latch1::authorize, + latch2::authorize, + latch3::authorize, + latch4::authorize, ]; - latch_n::check(operation, checks) + latch_n::authorize(operation, authorizers) } } diff --git a/components/latch-deny/Cargo.toml b/components/latch-permit-all/Cargo.toml similarity index 85% rename from components/latch-deny/Cargo.toml rename to components/latch-permit-all/Cargo.toml index d13196b..bdd1ad1 100644 --- a/components/latch-deny/Cargo.toml +++ b/components/latch-permit-all/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "latch-deny" +name = "latch-permit-all" version = "0.1.0" edition = "2021" license = "Apache-2.0" diff --git a/components/latch-permit-all/README.md b/components/latch-permit-all/README.md new file mode 100644 index 0000000..e893382 --- /dev/null +++ b/components/latch-permit-all/README.md @@ -0,0 +1,3 @@ +# `latch-permit-all` + +Filesystem latch that implicitly permits all operations. diff --git a/components/latch-allow/src/lib.rs b/components/latch-permit-all/src/lib.rs similarity index 56% rename from components/latch-allow/src/lib.rs rename to components/latch-permit-all/src/lib.rs index 2d6b414..a13b76c 100644 --- a/components/latch-allow/src/lib.rs +++ b/components/latch-permit-all/src/lib.rs @@ -2,11 +2,11 @@ use crate::exports::componentized::filesystem::latch::{Decision, Guest as Latch, Operation}; -struct AllowLatch {} +struct PermitAllLatch {} -impl Latch for AllowLatch { - fn check(_: Operation) -> Decision { - Decision::Allow +impl Latch for PermitAllLatch { + fn authorize(_: Operation) -> Option { + Some(Decision::Permitted) } } @@ -16,4 +16,4 @@ wit_bindgen::generate!({ generate_all }); -export!(AllowLatch); +export!(PermitAllLatch); diff --git a/components/latch-readonly/src/lib.rs b/components/latch-readonly/src/lib.rs index 29e1d94..947331e 100644 --- a/components/latch-readonly/src/lib.rs +++ b/components/latch-readonly/src/lib.rs @@ -2,7 +2,7 @@ use crate::{ exports::componentized::filesystem::latch::{ - Decision::{self, Abstain, Deny}, + Decision::{self, Denied}, DescriptorOpenAtArgs, DescriptorOperation, Guest as Latch, Operation, }, wasi::filesystem::types::{DescriptorFlags, ErrorCode::ReadOnly, OpenFlags}, @@ -11,28 +11,28 @@ use crate::{ struct ReadOnlyLatch {} impl Latch for ReadOnlyLatch { - fn check(operation: Operation) -> Decision { + fn authorize(operation: Operation) -> Option { match operation { - Operation::Preopens(_) => Abstain, + Operation::Preopens(_) => None, Operation::Descriptor((_, descriptor_operation)) => match descriptor_operation { - DescriptorOperation::ReadViaStream(_) => Abstain, - DescriptorOperation::WriteViaStream(_) => Deny(ReadOnly), - DescriptorOperation::AppendViaStream => Deny(ReadOnly), - DescriptorOperation::Advise(_) => Abstain, - DescriptorOperation::SyncData => Deny(ReadOnly), - DescriptorOperation::GetFlags => Abstain, - DescriptorOperation::GetType => Abstain, - DescriptorOperation::SetSize(_) => Deny(ReadOnly), - DescriptorOperation::SetTimes(_) => Deny(ReadOnly), - DescriptorOperation::Read(_) => Abstain, - DescriptorOperation::Write(_) => Deny(ReadOnly), - DescriptorOperation::ReadDirectory => Abstain, - DescriptorOperation::Sync => Deny(ReadOnly), - DescriptorOperation::CreateDirectoryAt(_) => Deny(ReadOnly), - DescriptorOperation::Stat => Abstain, - DescriptorOperation::StatAt(_) => Abstain, - DescriptorOperation::SetTimesAt(_) => Deny(ReadOnly), - DescriptorOperation::LinkAt(_) => Deny(ReadOnly), + DescriptorOperation::ReadViaStream(_) => None, + DescriptorOperation::WriteViaStream(_) => Some(Denied(ReadOnly)), + DescriptorOperation::AppendViaStream => Some(Denied(ReadOnly)), + DescriptorOperation::Advise(_) => None, + DescriptorOperation::SyncData => Some(Denied(ReadOnly)), + DescriptorOperation::GetFlags => None, + DescriptorOperation::GetType => None, + DescriptorOperation::SetSize(_) => Some(Denied(ReadOnly)), + DescriptorOperation::SetTimes(_) => Some(Denied(ReadOnly)), + DescriptorOperation::Read(_) => None, + DescriptorOperation::Write(_) => Some(Denied(ReadOnly)), + DescriptorOperation::ReadDirectory => None, + DescriptorOperation::Sync => Some(Denied(ReadOnly)), + DescriptorOperation::CreateDirectoryAt(_) => Some(Denied(ReadOnly)), + DescriptorOperation::Stat => None, + DescriptorOperation::StatAt(_) => None, + DescriptorOperation::SetTimesAt(_) => Some(Denied(ReadOnly)), + DescriptorOperation::LinkAt(_) => Some(Denied(ReadOnly)), DescriptorOperation::OpenAt(DescriptorOpenAtArgs { open_flags, flags, .. }) => { @@ -46,20 +46,20 @@ impl Latch for ReadOnlyLatch { .union(DescriptorFlags::DATA_INTEGRITY_SYNC) .union(DescriptorFlags::REQUESTED_WRITE_SYNC), ) { - Deny(ReadOnly) + Some(Denied(ReadOnly)) } else { - Abstain + None } } - DescriptorOperation::ReadlinkAt(_) => Abstain, - DescriptorOperation::RemoveDirectoryAt(_) => Deny(ReadOnly), - DescriptorOperation::RenameAt(_) => Deny(ReadOnly), - DescriptorOperation::SymlinkAt(_) => Deny(ReadOnly), - DescriptorOperation::UnlinkFileAt(_) => Deny(ReadOnly), - DescriptorOperation::MetadataHash => Abstain, - DescriptorOperation::MetadataHashAt(_) => Abstain, + DescriptorOperation::ReadlinkAt(_) => None, + DescriptorOperation::RemoveDirectoryAt(_) => Some(Denied(ReadOnly)), + DescriptorOperation::RenameAt(_) => Some(Denied(ReadOnly)), + DescriptorOperation::SymlinkAt(_) => Some(Denied(ReadOnly)), + DescriptorOperation::UnlinkFileAt(_) => Some(Denied(ReadOnly)), + DescriptorOperation::MetadataHash => None, + DescriptorOperation::MetadataHashAt(_) => None, }, - Operation::DirectoryEntryStream(_) => Abstain, + Operation::DirectoryEntryStream(_) => None, } } } diff --git a/crates/latch-n/src/lib.rs b/crates/latch-n/src/lib.rs index 234b3e3..eb0c542 100644 --- a/crates/latch-n/src/lib.rs +++ b/crates/latch-n/src/lib.rs @@ -13,19 +13,19 @@ use crate::bindings::{ }, }; -pub fn check( +pub fn authorize( operation: Operation, - checks: Vec) -> latch::Decision>, -) -> Decision { + authorizers: Vec) -> Option>, +) -> Option { let operation = operation_map(operation); - for check in checks { - match check(&operation) { - latch::Decision::Abstain => {} - latch::Decision::Allow => return Decision::Allow, - latch::Decision::Deny(error_code) => return Decision::Deny(error_code), + for authorize in authorizers { + match authorize(&operation) { + None => {} + Some(latch::Decision::Permitted) => return Some(Decision::Permitted), + Some(latch::Decision::Denied(error_code)) => return Some(Decision::Denied(error_code)), } } - Decision::Abstain + None } fn operation_map(operation: Operation) -> latch::Operation { diff --git a/tests/readonly.wac b/tests/readonly.wac index df73c7c..908403a 100644 --- a/tests/readonly.wac +++ b/tests/readonly.wac @@ -3,7 +3,7 @@ package componentized:filesystem; let readonly = new componentized:gate { latch: new componentized:latch-n2 { latch: new componentized:latch-readonly { ... }.latch, - latch1: new componentized:latch-allow { ... }.latch, + latch1: new componentized:latch-permit { ... }.latch, ... }.latch, ... diff --git a/wit/latch.wit b/wit/latch.wit index 12a57b8..daf0182 100644 --- a/wit/latch.wit +++ b/wit/latch.wit @@ -35,7 +35,7 @@ interface latch { } record descriptor-write-args { - buffer-length: u64, + buffer-length: u64, // derived from buffer: list offset: filesize, } @@ -142,34 +142,33 @@ interface latch { } variant decision { - abstain, - allow, - deny(error-code), + permitted, + denied(error-code), } - check: func(operation: operation) -> decision; + authorize: func(operation: operation) -> option; } interface latch1 { use latch.{operation, decision}; - check: func(operation: operation) -> decision; + authorize: func(operation: operation) -> option; } interface latch2 { use latch.{operation, decision}; - check: func(operation: operation) -> decision; + authorize: func(operation: operation) -> option; } interface latch3 { use latch.{operation, decision}; - check: func(operation: operation) -> decision; + authorize: func(operation: operation) -> option; } interface latch4 { use latch.{operation, decision}; - check: func(operation: operation) -> decision; + authorize: func(operation: operation) -> option; } From a3de39494ceccdd7b48b2ff1ead32cc097f71996 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 09:58:46 -0400 Subject: [PATCH 07/11] Build test components in ci Signed-off-by: Scott Andrews --- .github/workflows/ci.yaml | 2 ++ README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ce44fbd..d1e9910 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,6 +48,8 @@ jobs: wasm-tools component wit "${component}" echo "::endgroup::" done + - name: Build test components + run: make tests publish: if: github.event_name == 'push' && ( startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' ) diff --git a/README.md b/README.md index eccfb36..2aef4a1 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ A collection of utility components that remix wasi:filesystem types and interfac Prereqs: - a rust toolchain - [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) +- [`wac`](https://github.com/bytecodealliance/wac) - [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools) ```sh From 47dd09e8048f363229f481b8b546b29a68c38ff4 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 12:48:55 -0400 Subject: [PATCH 08/11] Run operation unless denied The gate now treats `Some(Permitted)` and `None` authorization decisions as allowing the operation. This dramatically simplifies use of a single latch as it no longer needs to be aggregated with allow-all. Not needing an aggregation also means `wac plug` can manage the composition, while an aggregation requires a wac script or custom embedding to manage the interface names. Signed-off-by: Scott Andrews --- Makefile | 13 +-- components/gate/src/lib.rs | 202 ++++++++++++++++--------------------- tests/readonly.wac | 12 --- 3 files changed, 92 insertions(+), 135 deletions(-) delete mode 100644 tests/readonly.wac diff --git a/Makefile b/Makefile index cb1e961..69c37c0 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ export RUST_BACKTRACE ?= 1 export WASMTIME_BACKTRACE_DETAILS ?= 1 COMPONENTS = $(shell ls -1 components) -TEST_COMPONENTS = $(shell ls -1 tests | grep -v '\.wac') +TEST_COMPONENTS = $(shell ls -1 tests) .PHONY: all all: components @@ -82,13 +82,10 @@ lib/tests/filesystem-cli-deny.wasm: lib/tests/filesystem-cli.wasm lib/tests/deny ) \ -o lib/tests/filesystem-cli-deny.wasm -lib/tests/readonly.wasm: tests/readonly.wac lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-permit-all.wasm - wac compose -o lib/tests/readonly.wasm \ - -d componentized:gate="lib/gate.wasm" \ - -d componentized:latch-n2="lib/latch-n2.wasm" \ - -d componentized:latch-readonly="lib/latch-readonly.wasm" \ - -d componentized:latch-permit="lib/latch-permit-all.wasm" \ - tests/readonly.wac +lib/tests/readonly.wasm: lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-permit-all.wasm + wac plug lib/gate.wasm \ + --plug lib/latch-readonly.wasm \ + -o lib/tests/readonly.wasm lib/tests/filesystem-cli-readonly.wasm: lib/tests/filesystem-cli.wasm lib/tests/readonly.wasm lib/tests/logging-to-stdout.wasm wac plug lib/tests/filesystem-cli.wasm \ diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs index c46fc23..c0ad121 100644 --- a/components/gate/src/lib.rs +++ b/components/gate/src/lib.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use heck::ToKebabCase; -use crate::componentized::filesystem::latch::Decision::{Denied, Permitted}; +use crate::componentized::filesystem::latch::Decision::Denied; use crate::componentized::filesystem::latch::{ self, authorize, DescriptorOperation, DirectoryEntryStreamOperation, Operation, PreopensOperation, @@ -49,13 +49,12 @@ impl Preopens for GatedFilesystem { .into_iter() .filter(|(fs, path)| { match authorize(&Operation::Preopens(PreopensOperation::GetDirectoriesItem((fs, path.clone())))) { - Some(Permitted) => true, Some(Denied(code)) => { let reason = error_code_display(code); trace!("Denied REASON={reason} OPERATION=wasi:filesystem/preopens#get-directories PATH={path}"); false } - None => panic!("missing required latch decision"), + _ => true, } }) .map(|(fd, path)| { @@ -120,13 +119,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::ReadViaStream(latch::DescriptorReadViaStreamArgs { offset }), ))) { - Some(Permitted) => self.fd.read_via_stream(offset).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.read_via_stream(offset).map_err(error_code_map), } } @@ -141,13 +139,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::WriteViaStream(latch::DescriptorWriteViaStreamArgs { offset }), ))) { - Some(Permitted) => self.fd.write_via_stream(offset).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self} OFFSET={offset}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.write_via_stream(offset).map_err(error_code_map), } } @@ -162,13 +159,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::AppendViaStream, ))) { - Some(Permitted) => self.fd.append_via_stream().map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.append_via_stream().map_err(error_code_map), } } @@ -186,16 +182,15 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { advice, }), ))) { - Some(Permitted) => self - .fd - .advise(offset, length, advice) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.advise FD={self} OFFSET={offset} LENGTH={length} ADVICE={advice}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .advise(offset, length, advice) + .map_err(error_code_map), } } @@ -210,13 +205,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::SyncData, ))) { - Some(Permitted) => self.fd.sync_data().map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.sync_data().map_err(error_code_map), } } @@ -231,17 +225,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::GetFlags, ))) { - Some(Permitted) => self - .fd - .get_flags() - .map(descriptor_flags_map) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .get_flags() + .map(descriptor_flags_map) + .map_err(error_code_map), } } @@ -260,17 +253,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::GetType, ))) { - Some(Permitted) => self - .fd - .get_type() - .map(descriptor_type_map) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .get_type() + .map(descriptor_type_map) + .map_err(error_code_map), } } @@ -283,13 +275,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::SetSize(latch::DescriptorSetSizeArgs { size }), ))) { - Some(Permitted) => self.fd.set_size(size).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self} SIZE={size}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.set_size(size).map_err(error_code_map), } } @@ -313,16 +304,15 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, }), ))) { - Some(Permitted) => self - .fd - .set_times(data_access_timestamp, data_modification_timestamp) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .set_times(data_access_timestamp, data_modification_timestamp) + .map_err(error_code_map), } } @@ -342,13 +332,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::Read(latch::DescriptorReadArgs { length, offset }), ))) { - Some(Permitted) => self.fd.read(length, offset).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read FD={self} LENGTH={length} OFFSET={offset}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.read(length, offset).map_err(error_code_map), } } @@ -373,13 +362,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { offset, }), ))) { - Some(Permitted) => self.fd.write(&buffer, offset).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write FD={self} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.write(&buffer, offset).map_err(error_code_map), } } @@ -397,17 +385,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::ReadDirectory, ))) { - Some(Permitted) => self - .fd - .read_directory() - .map(|des| directory_entry_stream_map(des, self.path.clone())) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .read_directory() + .map(|des| directory_entry_stream_map(des, self.path.clone())) + .map_err(error_code_map), } } @@ -422,13 +409,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::Sync, ))) { - Some(Permitted) => self.fd.sync().map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.sync().map_err(error_code_map), } } @@ -442,13 +428,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Permitted) => self.fd.create_directory_at(&path).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.create_directory_at(&path).map_err(error_code_map), } } @@ -466,17 +451,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::Stat, ))) { - Some(Permitted) => self - .fd - .stat() - .map(descriptor_stat_map) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .stat() + .map(descriptor_stat_map) + .map_err(error_code_map), } } @@ -497,17 +481,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Permitted) => self - .fd - .stat_at(path_flags, &path) - .map(descriptor_stat_map) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self} PATH-FLAGS={path_flags} PATH={path}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .stat_at(path_flags, &path) + .map(descriptor_stat_map) + .map_err(error_code_map), } } @@ -536,7 +519,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, }), ))) { - Some(Permitted) => self + Some(Denied(code)) => { + let reason = error_code_display(code); + warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH-FLAGS={path_flags} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); + Err(error_code_map(code)) + } + _ => self .fd .set_times_at( path_flags, @@ -545,12 +533,6 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, ) .map_err(error_code_map), - Some(Denied(code)) => { - let reason = error_code_display(code); - warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH-FLAGS={path_flags} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); - Err(error_code_map(code)) - } - None => panic!("missing required latch decision"), } } @@ -574,7 +556,14 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Some(Permitted) => self + Some(Denied(code)) => { + let reason = error_code_display(code); + warn!( + "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} OLD-PATH-FLAGS={old_path_flags} NEW-PATH={new_path}", + ); + Err(error_code_map(code)) + } + _ => self .fd .link_at( old_path_flags, @@ -583,14 +572,6 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &new_path, ) .map_err(error_code_map), - Some(Denied(code)) => { - let reason = error_code_display(code); - warn!( - "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} OLD-PATH-FLAGS={old_path_flags} NEW-PATH={new_path}", - ); - Err(error_code_map(code)) - } - None => panic!("missing required latch decision"), } } @@ -631,17 +612,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { flags, }), ))) { - Some(Permitted) => self - .fd - .open_at(path_flags, &path.clone(), open_flags, flags) - .map(|descriptor| descriptor_map(descriptor, self.path.join(path))) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self} PATH-FLAGS={path_flags} PATH={path} OPEN-FLAGS={open_flags} FLAGS={flags}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .open_at(path_flags, &path.clone(), open_flags, flags) + .map(|descriptor| descriptor_map(descriptor, self.path.join(path))) + .map_err(error_code_map), } } @@ -656,7 +636,6 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::ReadlinkAt(latch::DescriptorReadlinkAtArgs { path: path.clone() }), ))) { - Some(Permitted) => self.fd.readlink_at(&path).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!( @@ -664,7 +643,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.readlink_at(&path).map_err(error_code_map), } } @@ -680,13 +659,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Permitted) => self.fd.remove_directory_at(&path).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self} PATH={path}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.remove_directory_at(&path).map_err(error_code_map), } } @@ -707,12 +685,6 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Some(Permitted) => { - let new_descriptor: &Self = new_descriptor.get(); - self.fd - .rename_at(&old_path, &new_descriptor.fd, &new_path) - .map_err(error_code_map) - } Some(Denied(code)) => { let reason = error_code_display(code); warn!( @@ -720,7 +692,12 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => { + let new_descriptor: &Self = new_descriptor.get(); + self.fd + .rename_at(&old_path, &new_descriptor.fd, &new_path) + .map_err(error_code_map) + } } } @@ -738,10 +715,6 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Some(Permitted) => self - .fd - .symlink_at(&old_path, &new_path) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!( @@ -749,7 +722,10 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .symlink_at(&old_path, &new_path) + .map_err(error_code_map), } } @@ -764,7 +740,6 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Permitted) => self.fd.unlink_file_at(&path).map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!( @@ -772,7 +747,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self.fd.unlink_file_at(&path).map_err(error_code_map), } } @@ -811,17 +786,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { &self.fd, DescriptorOperation::MetadataHash, ))) { - Some(Permitted) => self - .fd - .metadata_hash() - .map(metadata_hash_value_map) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .metadata_hash() + .map(metadata_hash_value_map) + .map_err(error_code_map), } } @@ -842,17 +816,16 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Permitted) => self - .fd - .metadata_hash_at(path_flags, &path) - .map(metadata_hash_value_map) - .map_err(error_code_map), Some(Denied(code)) => { let reason = error_code_display(code); warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self} PATH={path} PATH-FLAGS={path_flags}"); Err(error_code_map(code)) } - None => panic!("missing required latch decision"), + _ => self + .fd + .metadata_hash_at(path_flags, &path) + .map(metadata_hash_value_map) + .map_err(error_code_map), } } } @@ -887,14 +860,13 @@ impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GatedDirect &self.des, DirectoryEntryStreamOperation::ReadDirectoryEntry(de.clone()), ))) { - Some(Permitted) => Ok(Some(directory_entry_map(de))), Some(Denied(code)) => { let reason = error_code_display(code); trace!("Denied REASON={reason} OPERATION=wasi:filesystem/types#directory-entry-stream.read-directory-entry STREAM={} ENTRY={}", self, de.name); // continue reading the next entry transparently self.read_directory_entry() } - None => panic!("missing required latch decision"), + _ => Ok(Some(directory_entry_map(de))), } } Ok(None) => Ok(None), diff --git a/tests/readonly.wac b/tests/readonly.wac deleted file mode 100644 index 908403a..0000000 --- a/tests/readonly.wac +++ /dev/null @@ -1,12 +0,0 @@ -package componentized:filesystem; - -let readonly = new componentized:gate { - latch: new componentized:latch-n2 { - latch: new componentized:latch-readonly { ... }.latch, - latch1: new componentized:latch-permit { ... }.latch, - ... - }.latch, - ... -}; - -export readonly...; From d1747ebee1d93b5e69ee8699ab0539734a312b94 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 16:31:24 -0400 Subject: [PATCH 09/11] Include path as part of the operation The effective path is now passed as part of the operation. This enables a latch to consider the path of an operation. `latch-glob` uses glob pattern matching on the path to permit or deny an operation. Signed-off-by: Scott Andrews --- Cargo.lock | 23 +++ README.md | 1 + components/gate/src/lib.rs | 27 ++++ components/latch-glob/Cargo.toml | 12 ++ components/latch-glob/README.md | 9 ++ components/latch-glob/src/lib.rs | 209 +++++++++++++++++++++++++++ components/latch-readonly/src/lib.rs | 2 +- crates/latch-n/src/lib.rs | 12 +- wit/latch.wit | 4 +- wit/worlds.wit | 1 + 10 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 components/latch-glob/Cargo.toml create mode 100644 components/latch-glob/README.md create mode 100644 components/latch-glob/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3152a6e..e77b478 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,12 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hashbrown" version = "0.17.0" @@ -218,6 +224,14 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "latch-glob" +version = "0.1.0" +dependencies = [ + "path-matchers", + "wit-bindgen", +] + [[package]] name = "latch-n" version = "0.1.0" @@ -314,6 +328,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "path-matchers" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b" +dependencies = [ + "glob", +] + [[package]] name = "prettyplease" version = "0.2.29" diff --git a/README.md b/README.md index 2aef4a1..f60d1a1 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ A collection of utility components that remix wasi:filesystem types and interfac - [`latch-n4`](./components/latch-n4/) - [`latch-deny-all`](./components/latch-deny-all/) - [`latch-permit-all`](./components/latch-permit-all/) +- [`latch-glob`](./components/latch-glob/) - [`latch-readonly`](./components/latch-readonly/) - ~~[`readonly`](./components/readonly/)~~ (deprecated, favor gate with readonly latch) - [`tracing`](./components/tracing/) diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs index c0ad121..406305e 100644 --- a/components/gate/src/lib.rs +++ b/components/gate/src/lib.rs @@ -117,6 +117,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn read_via_stream(&self, offset: Filesize) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::ReadViaStream(latch::DescriptorReadViaStreamArgs { offset }), ))) { Some(Denied(code)) => { @@ -137,6 +138,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn write_via_stream(&self, offset: Filesize) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::WriteViaStream(latch::DescriptorWriteViaStreamArgs { offset }), ))) { Some(Denied(code)) => { @@ -157,6 +159,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn append_via_stream(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::AppendViaStream, ))) { Some(Denied(code)) => { @@ -176,6 +179,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::Advise(latch::DescriptorAdviseArgs { offset, length, @@ -203,6 +207,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn sync_data(&self) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::SyncData, ))) { Some(Denied(code)) => { @@ -223,6 +228,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn get_flags(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::GetFlags, ))) { Some(Denied(code)) => { @@ -251,6 +257,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn get_type(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::GetType, ))) { Some(Denied(code)) => { @@ -273,6 +280,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn set_size(&self, size: Filesize) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::SetSize(latch::DescriptorSetSizeArgs { size }), ))) { Some(Denied(code)) => { @@ -299,6 +307,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::SetTimes(latch::DescriptorSetTimesArgs { data_access_timestamp, data_modification_timestamp, @@ -330,6 +339,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn read(&self, length: Filesize, offset: Filesize) -> Result<(Vec, bool), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::Read(latch::DescriptorReadArgs { length, offset }), ))) { Some(Denied(code)) => { @@ -357,6 +367,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { .expect("buffer length 64-bits or less"); match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::Write(latch::DescriptorWriteArgs { buffer_length, offset, @@ -383,6 +394,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn read_directory(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::ReadDirectory, ))) { Some(Denied(code)) => { @@ -407,6 +419,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn sync(&self) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::Sync, ))) { Some(Denied(code)) => { @@ -424,6 +437,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::CreateDirectoryAt(latch::DescriptorCreateDirectoryAtArgs { path: path.clone(), }), @@ -449,6 +463,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn stat(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::Stat, ))) { Some(Denied(code)) => { @@ -476,6 +491,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::StatAt(latch::DescriptorStatAtArgs { path_flags, path: path.clone(), @@ -512,6 +528,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::SetTimesAt(latch::DescriptorSetTimesAtArgs { path_flags, path: path.clone(), @@ -549,6 +566,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let old_path_flags = types::PathFlags::from_bits(old_path_flags.bits()).unwrap(); match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::LinkAt(latch::DescriptorLinkAtArgs { old_path_flags, old_path: old_path.clone(), @@ -605,6 +623,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let flags = types::DescriptorFlags::from_bits(flags.bits()).unwrap(); match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::OpenAt(latch::DescriptorOpenAtArgs { path_flags, path: path.clone(), @@ -634,6 +653,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn readlink_at(&self, path: String) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::ReadlinkAt(latch::DescriptorReadlinkAtArgs { path: path.clone() }), ))) { Some(Denied(code)) => { @@ -655,6 +675,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::RemoveDirectoryAt(latch::DescriptorRemoveDirectoryAtArgs { path: path.clone(), }), @@ -679,6 +700,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { ) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::RenameAt(latch::DescriptorRenameAtArgs { old_path: old_path.clone(), new_descriptor: &new_descriptor.get::().fd, @@ -710,6 +732,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn symlink_at(&self, old_path: String, new_path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::SymlinkAt(latch::DescriptorSymlinkAtArgs { old_path: old_path.clone(), new_path: new_path.clone(), @@ -736,6 +759,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::UnlinkFileAt(latch::DescriptorUnlinkFileAtArgs { path: path.clone(), }), @@ -784,6 +808,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { fn metadata_hash(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::MetadataHash, ))) { Some(Denied(code)) => { @@ -811,6 +836,7 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); match authorize(&Operation::Descriptor(( &self.fd, + self.path.to_string_lossy().into_owned(), DescriptorOperation::MetadataHashAt(latch::DescriptorMetadataHashAtArgs { path_flags, path: path.clone(), @@ -858,6 +884,7 @@ impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GatedDirect Ok(Some(de)) => { match authorize(&Operation::DirectoryEntryStream(( &self.des, + self.path.to_string_lossy().into_owned(), DirectoryEntryStreamOperation::ReadDirectoryEntry(de.clone()), ))) { Some(Denied(code)) => { diff --git a/components/latch-glob/Cargo.toml b/components/latch-glob/Cargo.toml new file mode 100644 index 0000000..862df7b --- /dev/null +++ b/components/latch-glob/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "latch-glob" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } +path-matchers = "1.0" diff --git a/components/latch-glob/README.md b/components/latch-glob/README.md new file mode 100644 index 0000000..e033cc2 --- /dev/null +++ b/components/latch-glob/README.md @@ -0,0 +1,9 @@ +# `latch-glob` + +Filesystem latch that uses glob patterns to permit or deny operations on a path. + +The patterns are defined in a wasi:config/store. Keys starting with `deny` are parsed as globs with matching path operations being denied. Multiple patterns are allowed by defining unique config keys (e.g. `deny-1`, `deny-2`, etc). Keys starting with `permit` are parsed as globs with matching path operations being permitted. + +Operations are evaluated against the pattern with the base path for the operation joined with a relative path, if any. For example, the file descriptor open-at operation will join the descriptor's path with the argument's path, the resulting path is matched to the patterns. + +For operations with multiple paths, each path is evaluated individually. Any path matching a deny pattern will result in a denial. diff --git a/components/latch-glob/src/lib.rs b/components/latch-glob/src/lib.rs new file mode 100644 index 0000000..a5313bd --- /dev/null +++ b/components/latch-glob/src/lib.rs @@ -0,0 +1,209 @@ +#![no_main] + +use std::path::Path; + +use path_matchers::{glob, PathMatcher}; + +use crate::{ + exports::componentized::filesystem::latch::{ + Decision, DescriptorOperation, DirectoryEntryStreamOperation, Guest as Latch, Operation, + PreopensOperation, + }, + wasi::filesystem::types::ErrorCode, +}; + +struct GlobLatch {} + +struct Patterns { + initialized: bool, + denies: Option>>, + permits: Option>>, + deny_reason: Option, +} + +impl Patterns { + fn initialize() { + if unsafe { STATE.initialized } { + return; + } + + let mut denies: Vec> = vec![]; + let mut permits: Vec> = vec![]; + let mut reason = ErrorCode::NotPermitted; + + for (key, value) in wasi::config::store::get_all().expect("config must be available") { + if key.starts_with("deny") { + denies.push(Box::new( + glob(&value).expect("config value must parse as a glob"), + )); + } else if key.starts_with("permit") { + permits.push(Box::new( + glob(&value).expect("config value must parse as a glob"), + )); + } else if key == "reason" { + reason = get_error_code(value).unwrap_or(ErrorCode::NotPermitted); + } + } + + unsafe { + STATE.denies = Some(denies); + STATE.permits = Some(permits); + STATE.deny_reason = Some(reason); + STATE.initialized = true; + }; + } + + #[allow(static_mut_refs)] + fn authorize(path: String, paths: Vec) -> Option { + if paths.len() == 0 { + let path = Path::new(&path); + return unsafe { STATE.authorize_path(path) }; + } + let mut decision = None; + for p in paths { + let path = Path::new(&path).join(p); + match unsafe { STATE.authorize_path(&path) } { + // return denies immediately, buffer permits + Some(Decision::Denied(reason)) => return Some(Decision::Denied(reason)), + Some(Decision::Permitted) => decision = Some(Decision::Permitted), + None => {} + } + } + decision + } + + fn authorize_path(&self, path: &Path) -> Option { + for deny in self.denies.as_ref().unwrap() { + if deny.matches(path) { + return Some(Decision::Denied(self.deny_reason.unwrap())); + } + } + for permit in self.permits.as_ref().unwrap() { + if permit.matches(path) { + return Some(Decision::Permitted); + } + } + None + } +} + +static mut STATE: Patterns = Patterns { + initialized: false, + denies: None, + permits: None, + deny_reason: None, +}; + +impl Latch for GlobLatch { + fn authorize(operation: Operation) -> Option { + Patterns::initialize(); + + match operation { + Operation::Preopens(preopens_operation) => match preopens_operation { + PreopensOperation::GetDirectoriesItem((_, path)) => { + Patterns::authorize(path, vec![]) + } + }, + Operation::Descriptor((_, path, descriptor_operation)) => match descriptor_operation { + DescriptorOperation::ReadViaStream(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::WriteViaStream(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::AppendViaStream => Patterns::authorize(path, vec![]), + DescriptorOperation::Advise(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::SyncData => Patterns::authorize(path, vec![]), + DescriptorOperation::GetFlags => Patterns::authorize(path, vec![]), + DescriptorOperation::GetType => Patterns::authorize(path, vec![]), + DescriptorOperation::SetSize(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::SetTimes(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::Read(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::Write(_) => Patterns::authorize(path, vec![]), + DescriptorOperation::ReadDirectory => Patterns::authorize(path, vec![]), + DescriptorOperation::Sync => Patterns::authorize(path, vec![]), + DescriptorOperation::CreateDirectoryAt(args) => { + Patterns::authorize(path, vec![args.path]) + } + DescriptorOperation::Stat => Patterns::authorize(path, vec![]), + DescriptorOperation::StatAt(args) => Patterns::authorize(path, vec![args.path]), + DescriptorOperation::SetTimesAt(args) => Patterns::authorize(path, vec![args.path]), + DescriptorOperation::LinkAt(args) => { + Patterns::authorize(path, vec![args.old_path, args.new_path]) + } + DescriptorOperation::OpenAt(args) => Patterns::authorize(path, vec![args.path]), + DescriptorOperation::ReadlinkAt(args) => Patterns::authorize(path, vec![args.path]), + DescriptorOperation::RemoveDirectoryAt(args) => { + Patterns::authorize(path, vec![args.path]) + } + DescriptorOperation::RenameAt(args) => { + Patterns::authorize(path, vec![args.old_path, args.new_path]) + } + DescriptorOperation::SymlinkAt(args) => { + Patterns::authorize(path, vec![args.old_path, args.new_path]) + } + DescriptorOperation::UnlinkFileAt(args) => { + Patterns::authorize(path, vec![args.path]) + } + DescriptorOperation::MetadataHash => Patterns::authorize(path, vec![]), + DescriptorOperation::MetadataHashAt(args) => { + Patterns::authorize(path, vec![args.path]) + } + }, + Operation::DirectoryEntryStream((_, path, directory_entry_stream_operation)) => { + match directory_entry_stream_operation { + DirectoryEntryStreamOperation::ReadDirectoryEntry(directory_entry) => { + Patterns::authorize(path, vec![directory_entry.name]) + } + } + } + } + } +} + +fn get_error_code(value: String) -> Option { + match value.as_str() { + "access" => Some(ErrorCode::Access), + "wouldblock" => Some(ErrorCode::WouldBlock), + "already" => Some(ErrorCode::Already), + "bad-descriptor" => Some(ErrorCode::BadDescriptor), + "busy" => Some(ErrorCode::Busy), + "deadlock" => Some(ErrorCode::Deadlock), + "quota" => Some(ErrorCode::Quota), + "exist" => Some(ErrorCode::Exist), + "file-too-large" => Some(ErrorCode::FileTooLarge), + "illegal-byte-sequence" => Some(ErrorCode::IllegalByteSequence), + "in-progress" => Some(ErrorCode::InProgress), + "interrupted" => Some(ErrorCode::Interrupted), + "invalid" => Some(ErrorCode::Invalid), + "io" => Some(ErrorCode::Io), + "is-directory" => Some(ErrorCode::IsDirectory), + "loop" => Some(ErrorCode::Loop), + "too-many-links" => Some(ErrorCode::TooManyLinks), + "message-size" => Some(ErrorCode::MessageSize), + "name-too-long" => Some(ErrorCode::NameTooLong), + "no-device" => Some(ErrorCode::NoDevice), + "no-entry" => Some(ErrorCode::NoEntry), + "no-lock" => Some(ErrorCode::NoLock), + "insufficient-memory" => Some(ErrorCode::InsufficientMemory), + "insufficient-space" => Some(ErrorCode::InsufficientSpace), + "not-directory" => Some(ErrorCode::NotDirectory), + "not-empty" => Some(ErrorCode::NotEmpty), + "not-recoverable" => Some(ErrorCode::NotRecoverable), + "unsupported" => Some(ErrorCode::Unsupported), + "no-tty" => Some(ErrorCode::NoTty), + "no-such-device" => Some(ErrorCode::NoSuchDevice), + "overflow" => Some(ErrorCode::Overflow), + "not-permitted" => Some(ErrorCode::NotPermitted), + "pipe" => Some(ErrorCode::Pipe), + "read-only" => Some(ErrorCode::ReadOnly), + "invalid-seek" => Some(ErrorCode::InvalidSeek), + "text-file-busy" => Some(ErrorCode::TextFileBusy), + "cross-device" => Some(ErrorCode::CrossDevice), + _ => None, + } +} + +wit_bindgen::generate!({ + path: "../../wit", + world: "filesystem-latch", + generate_all +}); + +export!(GlobLatch); diff --git a/components/latch-readonly/src/lib.rs b/components/latch-readonly/src/lib.rs index 947331e..7665788 100644 --- a/components/latch-readonly/src/lib.rs +++ b/components/latch-readonly/src/lib.rs @@ -14,7 +14,7 @@ impl Latch for ReadOnlyLatch { fn authorize(operation: Operation) -> Option { match operation { Operation::Preopens(_) => None, - Operation::Descriptor((_, descriptor_operation)) => match descriptor_operation { + Operation::Descriptor((_, _, descriptor_operation)) => match descriptor_operation { DescriptorOperation::ReadViaStream(_) => None, DescriptorOperation::WriteViaStream(_) => Some(Denied(ReadOnly)), DescriptorOperation::AppendViaStream => Some(Denied(ReadOnly)), diff --git a/crates/latch-n/src/lib.rs b/crates/latch-n/src/lib.rs index eb0c542..4075fde 100644 --- a/crates/latch-n/src/lib.rs +++ b/crates/latch-n/src/lib.rs @@ -33,14 +33,20 @@ fn operation_map(operation: Operation) -> latch::Operation { Operation::Preopens(preopens_operation) => { latch::Operation::Preopens(preopens_operation_map(preopens_operation)) } - Operation::Descriptor((descriptor, descriptor_operation)) => latch::Operation::Descriptor( - (descriptor, descriptor_operation_map(descriptor_operation)), - ), + Operation::Descriptor((descriptor, path, descriptor_operation)) => { + latch::Operation::Descriptor(( + descriptor, + path, + descriptor_operation_map(descriptor_operation), + )) + } Operation::DirectoryEntryStream(( directory_entry_stream, + path, directory_entry_stream_operation, )) => latch::Operation::DirectoryEntryStream(( directory_entry_stream, + path, directory_entry_stream_operation_map(directory_entry_stream_operation), )), } diff --git a/wit/latch.wit b/wit/latch.wit index daf0182..bad62a0 100644 --- a/wit/latch.wit +++ b/wit/latch.wit @@ -137,8 +137,8 @@ interface latch { variant operation { preopens(preopens-operation), - descriptor(tuple, descriptor-operation>), - directory-entry-stream(tuple, directory-entry-stream-operation>), + descriptor(tuple, string, descriptor-operation>), + directory-entry-stream(tuple, string, directory-entry-stream-operation>), } variant decision { diff --git a/wit/worlds.wit b/wit/worlds.wit index 8c8701f..f6e3142 100644 --- a/wit/worlds.wit +++ b/wit/worlds.wit @@ -11,6 +11,7 @@ world filesystem { } world filesystem-latch { + import wasi:config/store@0.2.0-rc.1; export latch; } From 167a7f89c3a55218071cb286b54c84a8a224d3be Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Thu, 11 Jun 2026 15:45:34 -0400 Subject: [PATCH 10/11] permit -> grant Signed-off-by: Scott Andrews --- Cargo.lock | 14 ++++++------ Makefile | 16 +++++++------- README.md | 2 +- components/latch-glob/README.md | 4 ++-- components/latch-glob/src/lib.rs | 22 +++++++++---------- .../Cargo.toml | 2 +- components/latch-grant-all/README.md | 3 +++ .../src/lib.rs | 8 +++---- components/latch-permit-all/README.md | 3 --- crates/latch-n/src/lib.rs | 2 +- wit/latch.wit | 2 +- 11 files changed, 39 insertions(+), 39 deletions(-) rename components/{latch-permit-all => latch-grant-all}/Cargo.toml (85%) create mode 100644 components/latch-grant-all/README.md rename components/{latch-permit-all => latch-grant-all}/src/lib.rs (70%) delete mode 100644 components/latch-permit-all/README.md diff --git a/Cargo.lock b/Cargo.lock index e77b478..8cb03cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,13 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "latch-grant-all" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "latch-n" version = "0.1.0" @@ -267,13 +274,6 @@ dependencies = [ "latch-n", ] -[[package]] -name = "latch-permit-all" -version = "0.1.0" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "latch-readonly" version = "0.1.0" diff --git a/Makefile b/Makefile index 69c37c0..8fb14c7 100644 --- a/Makefile +++ b/Makefile @@ -56,18 +56,18 @@ $(foreach component,$(TEST_COMPONENTS),$(eval $(call TEST_COMPONENT,$(component) lib/tests/logging-to-stdout.wasm: wkg oci pull ghcr.io/componentized/logging/to-stdout:v0.2.1 -o "lib/tests/logging-to-stdout.wasm" -lib/tests/permit.wasm: lib/gate.wasm lib/latch-permit-all.wasm +lib/tests/grant.wasm: lib/gate.wasm lib/latch-grant-all.wasm wac plug lib/gate.wasm \ - --plug lib/latch-permit-all.wasm \ - -o lib/tests/permit.wasm + --plug lib/latch-grant-all.wasm \ + -o lib/tests/grant.wasm -lib/tests/filesystem-cli-permit.wasm: lib/tests/filesystem-cli.wasm lib/tests/permit.wasm lib/tests/logging-to-stdout.wasm +lib/tests/filesystem-cli-grant.wasm: lib/tests/filesystem-cli.wasm lib/tests/grant.wasm lib/tests/logging-to-stdout.wasm wac plug lib/tests/filesystem-cli.wasm \ --plug <( \ - wac plug lib/tests/permit.wasm \ + wac plug lib/tests/grant.wasm \ --plug lib/tests/logging-to-stdout.wasm \ ) \ - -o lib/tests/filesystem-cli-permit.wasm + -o lib/tests/filesystem-cli-grant.wasm lib/tests/deny.wasm: lib/gate.wasm lib/latch-deny-all.wasm wac plug lib/gate.wasm \ @@ -82,7 +82,7 @@ lib/tests/filesystem-cli-deny.wasm: lib/tests/filesystem-cli.wasm lib/tests/deny ) \ -o lib/tests/filesystem-cli-deny.wasm -lib/tests/readonly.wasm: lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-permit-all.wasm +lib/tests/readonly.wasm: lib/gate.wasm lib/latch-n2.wasm lib/latch-readonly.wasm lib/latch-grant-all.wasm wac plug lib/gate.wasm \ --plug lib/latch-readonly.wasm \ -o lib/tests/readonly.wasm @@ -96,7 +96,7 @@ lib/tests/filesystem-cli-readonly.wasm: lib/tests/filesystem-cli.wasm lib/tests/ -o lib/tests/filesystem-cli-readonly.wasm .PHONY: tests -tests: $(foreach component,$(TEST_COMPONENTS),lib/tests/$(component).wasm) lib/tests/filesystem-cli-permit.wasm lib/tests/filesystem-cli-deny.wasm lib/tests/filesystem-cli-readonly.wasm +tests: $(foreach component,$(TEST_COMPONENTS),lib/tests/$(component).wasm) lib/tests/filesystem-cli-grant.wasm lib/tests/filesystem-cli-deny.wasm lib/tests/filesystem-cli-readonly.wasm .PHONY: wit wit: wit/deps diff --git a/README.md b/README.md index f60d1a1..ce797f7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A collection of utility components that remix wasi:filesystem types and interfac - [`latch-n3`](./components/latch-n3/) - [`latch-n4`](./components/latch-n4/) - [`latch-deny-all`](./components/latch-deny-all/) -- [`latch-permit-all`](./components/latch-permit-all/) +- [`latch-grant-all`](./components/latch-grant-all/) - [`latch-glob`](./components/latch-glob/) - [`latch-readonly`](./components/latch-readonly/) - ~~[`readonly`](./components/readonly/)~~ (deprecated, favor gate with readonly latch) diff --git a/components/latch-glob/README.md b/components/latch-glob/README.md index e033cc2..e8cc14b 100644 --- a/components/latch-glob/README.md +++ b/components/latch-glob/README.md @@ -1,8 +1,8 @@ # `latch-glob` -Filesystem latch that uses glob patterns to permit or deny operations on a path. +Filesystem latch that uses glob patterns to grant or deny operations on a path. -The patterns are defined in a wasi:config/store. Keys starting with `deny` are parsed as globs with matching path operations being denied. Multiple patterns are allowed by defining unique config keys (e.g. `deny-1`, `deny-2`, etc). Keys starting with `permit` are parsed as globs with matching path operations being permitted. +The patterns are defined in a wasi:config/store. Keys starting with `deny` are parsed as globs with matching path operations being denied. Multiple patterns are allowed by defining unique config keys (e.g. `deny-1`, `deny-2`, etc). Keys starting with `grant` are parsed as globs with matching path operations being granted. Operations are evaluated against the pattern with the base path for the operation joined with a relative path, if any. For example, the file descriptor open-at operation will join the descriptor's path with the argument's path, the resulting path is matched to the patterns. diff --git a/components/latch-glob/src/lib.rs b/components/latch-glob/src/lib.rs index a5313bd..9b012c0 100644 --- a/components/latch-glob/src/lib.rs +++ b/components/latch-glob/src/lib.rs @@ -17,7 +17,7 @@ struct GlobLatch {} struct Patterns { initialized: bool, denies: Option>>, - permits: Option>>, + grants: Option>>, deny_reason: Option, } @@ -28,7 +28,7 @@ impl Patterns { } let mut denies: Vec> = vec![]; - let mut permits: Vec> = vec![]; + let mut grants: Vec> = vec![]; let mut reason = ErrorCode::NotPermitted; for (key, value) in wasi::config::store::get_all().expect("config must be available") { @@ -36,8 +36,8 @@ impl Patterns { denies.push(Box::new( glob(&value).expect("config value must parse as a glob"), )); - } else if key.starts_with("permit") { - permits.push(Box::new( + } else if key.starts_with("grant") { + grants.push(Box::new( glob(&value).expect("config value must parse as a glob"), )); } else if key == "reason" { @@ -47,7 +47,7 @@ impl Patterns { unsafe { STATE.denies = Some(denies); - STATE.permits = Some(permits); + STATE.grants = Some(grants); STATE.deny_reason = Some(reason); STATE.initialized = true; }; @@ -63,9 +63,9 @@ impl Patterns { for p in paths { let path = Path::new(&path).join(p); match unsafe { STATE.authorize_path(&path) } { - // return denies immediately, buffer permits + // return denies immediately, buffer grants Some(Decision::Denied(reason)) => return Some(Decision::Denied(reason)), - Some(Decision::Permitted) => decision = Some(Decision::Permitted), + Some(Decision::Granted) => decision = Some(Decision::Granted), None => {} } } @@ -78,9 +78,9 @@ impl Patterns { return Some(Decision::Denied(self.deny_reason.unwrap())); } } - for permit in self.permits.as_ref().unwrap() { - if permit.matches(path) { - return Some(Decision::Permitted); + for grant in self.grants.as_ref().unwrap() { + if grant.matches(path) { + return Some(Decision::Granted); } } None @@ -90,7 +90,7 @@ impl Patterns { static mut STATE: Patterns = Patterns { initialized: false, denies: None, - permits: None, + grants: None, deny_reason: None, }; diff --git a/components/latch-permit-all/Cargo.toml b/components/latch-grant-all/Cargo.toml similarity index 85% rename from components/latch-permit-all/Cargo.toml rename to components/latch-grant-all/Cargo.toml index bdd1ad1..e37c55f 100644 --- a/components/latch-permit-all/Cargo.toml +++ b/components/latch-grant-all/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "latch-permit-all" +name = "latch-grant-all" version = "0.1.0" edition = "2021" license = "Apache-2.0" diff --git a/components/latch-grant-all/README.md b/components/latch-grant-all/README.md new file mode 100644 index 0000000..21165c4 --- /dev/null +++ b/components/latch-grant-all/README.md @@ -0,0 +1,3 @@ +# `latch-grant-all` + +Filesystem latch that implicitly grants all operations. diff --git a/components/latch-permit-all/src/lib.rs b/components/latch-grant-all/src/lib.rs similarity index 70% rename from components/latch-permit-all/src/lib.rs rename to components/latch-grant-all/src/lib.rs index a13b76c..09f7107 100644 --- a/components/latch-permit-all/src/lib.rs +++ b/components/latch-grant-all/src/lib.rs @@ -2,11 +2,11 @@ use crate::exports::componentized::filesystem::latch::{Decision, Guest as Latch, Operation}; -struct PermitAllLatch {} +struct GrantAllLatch {} -impl Latch for PermitAllLatch { +impl Latch for GrantAllLatch { fn authorize(_: Operation) -> Option { - Some(Decision::Permitted) + Some(Decision::Granted) } } @@ -16,4 +16,4 @@ wit_bindgen::generate!({ generate_all }); -export!(PermitAllLatch); +export!(GrantAllLatch); diff --git a/components/latch-permit-all/README.md b/components/latch-permit-all/README.md deleted file mode 100644 index e893382..0000000 --- a/components/latch-permit-all/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `latch-permit-all` - -Filesystem latch that implicitly permits all operations. diff --git a/crates/latch-n/src/lib.rs b/crates/latch-n/src/lib.rs index 4075fde..6349284 100644 --- a/crates/latch-n/src/lib.rs +++ b/crates/latch-n/src/lib.rs @@ -21,7 +21,7 @@ pub fn authorize( for authorize in authorizers { match authorize(&operation) { None => {} - Some(latch::Decision::Permitted) => return Some(Decision::Permitted), + Some(latch::Decision::Granted) => return Some(Decision::Granted), Some(latch::Decision::Denied(error_code)) => return Some(Decision::Denied(error_code)), } } diff --git a/wit/latch.wit b/wit/latch.wit index bad62a0..07b5099 100644 --- a/wit/latch.wit +++ b/wit/latch.wit @@ -142,7 +142,7 @@ interface latch { } variant decision { - permitted, + granted, denied(error-code), } From b436b28833bf114bb5475954cd3d734ad6df89b1 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Sun, 14 Jun 2026 10:27:25 -0400 Subject: [PATCH 11/11] migrate to wasi 0.3.0 Signed-off-by: Scott Andrews --- Cargo.lock | 182 ++- Cargo.toml | 2 +- components/gate/Cargo.toml | 3 +- components/gate/src/lib.rs | 1044 +++++++---------- components/latch-deny-all/src/lib.rs | 1 + components/latch-glob/src/lib.rs | 101 +- components/latch-grant-all/src/lib.rs | 1 + components/latch-readonly/src/lib.rs | 4 +- components/tracing/src/lib.rs | 9 +- crates/latch-n/src/lib.rs | 289 +---- wit/deps/wasi-clocks-0.3.0/package.wit | 19 + wit/deps/wasi-config-0.2.0-rc.1/package.wit | 33 + wit/deps/wasi-filesystem-0.3.0/package.wit | 575 +++++++++ wit/deps/wasi-logging-0.1.0-draft/package.wit | 36 + wit/latch.wit | 26 +- wit/worlds.wit | 5 +- 16 files changed, 1326 insertions(+), 1004 deletions(-) create mode 100644 wit/deps/wasi-clocks-0.3.0/package.wit create mode 100644 wit/deps/wasi-config-0.2.0-rc.1/package.wit create mode 100644 wit/deps/wasi-filesystem-0.3.0/package.wit create mode 100644 wit/deps/wasi-logging-0.1.0-draft/package.wit diff --git a/Cargo.lock b/Cargo.lock index 8cb03cc..6eeaba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -68,6 +118,52 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -80,6 +176,13 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "filesystem-cli" +version = "0.1.0" +dependencies = [ + "clap", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -92,12 +195,48 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.32" @@ -111,6 +250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", + "futures-sink", "futures-task", "pin-project-lite", "slab", @@ -120,6 +260,7 @@ dependencies = [ name = "gate" version = "0.1.0" dependencies = [ + "chrono", "heck", "wit-bindgen", ] @@ -211,14 +352,7 @@ dependencies = [ ] [[package]] -name = "latch-allow" -version = "0.1.0" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "latch-deny" +name = "latch-deny-all" version = "0.1.0" dependencies = [ "wit-bindgen", @@ -323,10 +457,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "path-matchers" @@ -337,6 +471,12 @@ dependencies = [ "glob", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "prettyplease" version = "0.2.29" @@ -444,8 +584,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "syn" -version = "2.0.98" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" @@ -481,6 +621,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasm-bindgen" version = "0.2.123" @@ -619,6 +765,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -689,6 +844,7 @@ version = "0.58.0" source = "git+https://github.com/bytecodealliance/wit-bindgen.git?branch=main#646d3b4be2b0b02cfdace6fa0614c504ed53c801" dependencies = [ "bitflags", + "futures", "wit-bindgen-rust-macro", ] diff --git a/Cargo.toml b/Cargo.toml index b2fa826..510a676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,4 @@ members = [ chrono = { git = "https://github.com/chronotope/chrono.git", branch = "0.5.x" } heck = "0.5" latch-n = { path = "./crates/latch-n" } -wit-bindgen = { version = "0.58.0", git = "https://github.com/bytecodealliance/wit-bindgen.git", branch = "main" } +wit-bindgen = { version = "0.58.0", features = ["async-spawn"], git = "https://github.com/bytecodealliance/wit-bindgen.git", branch = "main" } diff --git a/components/gate/Cargo.toml b/components/gate/Cargo.toml index c238b21..fe14ecf 100644 --- a/components/gate/Cargo.toml +++ b/components/gate/Cargo.toml @@ -8,5 +8,6 @@ license = "Apache-2.0" crate-type = ["cdylib"] [dependencies] +chrono = { workspace = true } +heck = { workspace = true } wit-bindgen = { workspace = true } -heck = "0.5" diff --git a/components/gate/src/lib.rs b/components/gate/src/lib.rs index 406305e..a5f9e1a 100644 --- a/components/gate/src/lib.rs +++ b/components/gate/src/lib.rs @@ -2,20 +2,19 @@ use std::fmt::Display; use std::path::PathBuf; -use std::rc::Rc; +use chrono::DateTime; use heck::ToKebabCase; use crate::componentized::filesystem::latch::Decision::Denied; use crate::componentized::filesystem::latch::{ - self, authorize, DescriptorOperation, DirectoryEntryStreamOperation, Operation, - PreopensOperation, + self, authorize, DescriptorOperation, Operation, PreopensOperation, }; use crate::exports::wasi::filesystem::preopens::Guest as Preopens; use crate::exports::wasi::filesystem::types::{ Advice, Descriptor, DescriptorBorrow, DescriptorFlags, DescriptorStat, DescriptorType, - DirectoryEntry, DirectoryEntryStream, Error, ErrorCode, Filesize, Guest as Types, InputStream, - MetadataHashValue, NewTimestamp, OpenFlags, OutputStream, PathFlags, + DirectoryEntry, ErrorCode, Filesize, Guest as Types, MetadataHashValue, NewTimestamp, + OpenFlags, PathFlags, }; use crate::wasi::filesystem::preopens; use crate::wasi::filesystem::types; @@ -39,18 +38,17 @@ macro_rules! trace { }; } -#[derive(Debug, Clone)] struct GatedFilesystem {} impl Preopens for GatedFilesystem { - #[doc = " Return the set of preopened directories, and their path."] + #[doc = "/ Return the set of preopened directories, and their paths."] + #[allow(async_fn_in_trait)] fn get_directories() -> Vec<(Descriptor, String)> { preopens::get_directories() .into_iter() .filter(|(fs, path)| { match authorize(&Operation::Preopens(PreopensOperation::GetDirectoriesItem((fs, path.clone())))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { trace!("Denied REASON={reason} OPERATION=wasi:filesystem/preopens#get-directories PATH={path}"); false } @@ -67,116 +65,134 @@ impl Preopens for GatedFilesystem { impl Types for GatedFilesystem { type Descriptor = GatedFileDescriptor; - type DirectoryEntryStream = GatedDirectoryEntryStream; - - #[doc = " Attempts to extract a filesystem-related `error-code` from the stream"] - #[doc = " `error` provided."] - #[doc = ""] - #[doc = " Stream operations which return `stream-error::last-operation-failed`"] - #[doc = " have a payload with more information about the operation that failed."] - #[doc = " This payload can be passed through to this function to see if there\'s"] - #[doc = " filesystem-related information about the error to return."] - #[doc = ""] - #[doc = " Note that this function is fallible because not all stream-related"] - #[doc = " errors are filesystem-related errors."] - fn filesystem_error_code(err: &Error) -> Option { - types::filesystem_error_code(err).map(error_code_map) - } } -#[derive(Debug, Clone)] struct GatedFileDescriptor { - fd: Rc, + fd: types::Descriptor, path: PathBuf, } impl GatedFileDescriptor { fn new(fd: types::Descriptor, path: PathBuf) -> Self { - Self { - fd: Rc::new(fd), - path, - } - } -} - -impl Display for GatedFileDescriptor { - fn fmt(&self, d: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - d.write_fmt(format_args!("{}", self.path.display())) + Self { fd, path } } } impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { - #[doc = " Return a stream for reading from a file, if available."] - #[doc = ""] - #[doc = " May fail with an error-code describing why the file cannot be read."] - #[doc = ""] - #[doc = " Multiple read, write, and append streams may be active on the same open"] - #[doc = " file and they do not interfere with each other."] - #[doc = ""] - #[doc = " Note: This allows using `read-stream`, which is similar to `read` in POSIX."] - fn read_via_stream(&self, offset: Filesize) -> Result { + #[doc = "/ Return a stream for reading from a file."] + #[doc = "/"] + #[doc = "/ Multiple read, write, and append streams may be active on the same open"] + #[doc = "/ file and they do not interfere with each other."] + #[doc = "/"] + #[doc = "/ This function returns a `stream` which provides the data received from the"] + #[doc = "/ file, and a `future` providing additional error information in case an"] + #[doc = "/ error is encountered."] + #[doc = "/"] + #[doc = "/ If no error is encountered, `stream.read` on the `stream` will return"] + #[doc = "/ `read-status::closed` with no `error-context` and the future resolves to"] + #[doc = "/ the value `ok`. If an error is encountered, `stream.read` on the"] + #[doc = "/ `stream` returns `read-status::closed` with an `error-context` and the future"] + #[doc = "/ resolves to `err` with an `error-code`."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `pread` in POSIX."] + #[allow(async_fn_in_trait)] + fn read_via_stream( + &self, + offset: Filesize, + ) -> ( + wit_bindgen::StreamReader, + wit_bindgen::FutureReader>, + ) { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::ReadViaStream(latch::DescriptorReadViaStreamArgs { offset }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-via-stream FD={self} OFFSET={offset}"); - Err(error_code_map(code)) + let (_, data_reader) = wit_stream::new(); + let (result_writer, result_reader) = + wit_future::new(|| Err(ErrorCode::Other(None))); + result_writer.write(Err(reason)); + (data_reader, result_reader) } - _ => self.fd.read_via_stream(offset).map_err(error_code_map), + _ => self.fd.read_via_stream(offset), } } - #[doc = " Return a stream for writing to a file, if available."] - #[doc = ""] - #[doc = " May fail with an error-code describing why the file cannot be written."] - #[doc = ""] - #[doc = " Note: This allows using `write-stream`, which is similar to `write` in"] - #[doc = " POSIX."] - fn write_via_stream(&self, offset: Filesize) -> Result { + #[doc = "/ Return a stream for writing to a file, if available."] + #[doc = "/"] + #[doc = "/ May fail with an error-code describing why the file cannot be written."] + #[doc = "/"] + #[doc = "/ It is valid to write past the end of a file; the file is extended to the"] + #[doc = "/ extent of the write, with bytes between the previous end and the start of"] + #[doc = "/ the write set to zero."] + #[doc = "/"] + #[doc = "/ This function returns once either full contents of the stream are"] + #[doc = "/ written or an error is encountered."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `pwrite` in POSIX."] + #[allow(async_fn_in_trait)] + fn write_via_stream( + &self, + data: wit_bindgen::StreamReader, + offset: Filesize, + ) -> wit_bindgen::FutureReader> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::WriteViaStream(latch::DescriptorWriteViaStreamArgs { offset }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write-via-stream FD={self} OFFSET={offset}"); - Err(error_code_map(code)) + let (result_writer, result_reader) = + wit_future::new(|| Err(ErrorCode::Other(None))); + result_writer.write(Err(reason)); + result_reader } - _ => self.fd.write_via_stream(offset).map_err(error_code_map), + _ => self.fd.write_via_stream(data, offset), } } - #[doc = " Return a stream for appending to a file, if available."] - #[doc = ""] - #[doc = " May fail with an error-code describing why the file cannot be appended."] - #[doc = ""] - #[doc = " Note: This allows using `write-stream`, which is similar to `write` with"] - #[doc = " `O_APPEND` in in POSIX."] - fn append_via_stream(&self) -> Result { + #[doc = "/ Return a stream for appending to a file, if available."] + #[doc = "/"] + #[doc = "/ May fail with an error-code describing why the file cannot be appended."] + #[doc = "/"] + #[doc = "/ This function returns once either full contents of the stream are"] + #[doc = "/ written or an error is encountered."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `write` with `O_APPEND` in POSIX."] + #[allow(async_fn_in_trait)] + fn append_via_stream( + &self, + data: wit_bindgen::StreamReader, + ) -> wit_bindgen::FutureReader> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::AppendViaStream, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.append-via-stream FD={self}"); - Err(error_code_map(code)) + let (result_writer, result_reader) = + wit_future::new(|| Err(ErrorCode::Other(None))); + result_writer.write(Err(reason)); + result_reader } - _ => self.fd.append_via_stream().map_err(error_code_map), + _ => self.fd.append_via_stream(data), } } - #[doc = " Provide file advisory information on a descriptor."] - #[doc = ""] - #[doc = " This is similar to `posix_fadvise` in POSIX."] - fn advise(&self, offset: Filesize, length: Filesize, advice: Advice) -> Result<(), ErrorCode> { - let advice = advice_map_in(advice); - + #[doc = "/ Provide file advisory information on a descriptor."] + #[doc = "/"] + #[doc = "/ This is similar to `posix_fadvise` in POSIX."] + #[allow(async_fn_in_trait)] + async fn advise( + &self, + offset: Filesize, + length: Filesize, + advice: Advice, + ) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -186,125 +202,111 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { advice, }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.advise FD={self} OFFSET={offset} LENGTH={length} ADVICE={advice}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .advise(offset, length, advice) - .map_err(error_code_map), + _ => self.fd.advise(offset, length, advice).await, } } - #[doc = " Synchronize the data of a file to disk."] - #[doc = ""] - #[doc = " This function succeeds with no effect if the file descriptor is not"] - #[doc = " opened for writing."] - #[doc = ""] - #[doc = " Note: This is similar to `fdatasync` in POSIX."] - fn sync_data(&self) -> Result<(), ErrorCode> { + #[doc = "/ Synchronize the data of a file to disk."] + #[doc = "/"] + #[doc = "/ This function succeeds with no effect if the file descriptor is not"] + #[doc = "/ opened for writing."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `fdatasync` in POSIX."] + #[allow(async_fn_in_trait)] + async fn sync_data(&self) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::SyncData, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync-data FD={self}"); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.sync_data().map_err(error_code_map), + _ => self.fd.sync_data().await, } } - #[doc = " Get flags associated with a descriptor."] - #[doc = ""] - #[doc = " Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX."] - #[doc = ""] - #[doc = " Note: This returns the value that was the `fs_flags` value returned"] - #[doc = " from `fdstat_get` in earlier versions of WASI."] - fn get_flags(&self) -> Result { + #[doc = "/ Get flags associated with a descriptor."] + #[doc = "/"] + #[doc = "/ Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX."] + #[doc = "/"] + #[doc = "/ Note: This returns the value that was the `fs_flags` value returned"] + #[doc = "/ from `fdstat_get` in earlier versions of WASI."] + #[allow(async_fn_in_trait)] + async fn get_flags(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::GetFlags, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-flags FD={self}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .get_flags() - .map(descriptor_flags_map) - .map_err(error_code_map), + _ => self.fd.get_flags().await, } } - #[doc = " Get the dynamic type of a descriptor."] - #[doc = ""] - #[doc = " Note: This returns the same value as the `type` field of the `fd-stat`"] - #[doc = " returned by `stat`, `stat-at` and similar."] - #[doc = ""] - #[doc = " Note: This returns similar flags to the `st_mode & S_IFMT` value provided"] - #[doc = " by `fstat` in POSIX."] - #[doc = ""] - #[doc = " Note: This returns the value that was the `fs_filetype` value returned"] - #[doc = " from `fdstat_get` in earlier versions of WASI."] - fn get_type(&self) -> Result { + #[doc = "/ Get the dynamic type of a descriptor."] + #[doc = "/"] + #[doc = "/ Note: This returns the same value as the `type` field of the `fd-stat`"] + #[doc = "/ returned by `stat`, `stat-at` and similar."] + #[doc = "/"] + #[doc = "/ Note: This returns similar flags to the `st_mode & S_IFMT` value provided"] + #[doc = "/ by `fstat` in POSIX."] + #[doc = "/"] + #[doc = "/ Note: This returns the value that was the `fs_filetype` value returned"] + #[doc = "/ from `fdstat_get` in earlier versions of WASI."] + #[allow(async_fn_in_trait)] + async fn get_type(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::GetType, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.get-type FD={self}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .get_type() - .map(descriptor_type_map) - .map_err(error_code_map), + _ => self.fd.get_type().await, } } - #[doc = " Adjust the size of an open file. If this increases the file\'s size, the"] - #[doc = " extra bytes are filled with zeros."] - #[doc = ""] - #[doc = " Note: This was called `fd_filestat_set_size` in earlier versions of WASI."] - fn set_size(&self, size: Filesize) -> Result<(), ErrorCode> { + #[doc = "/ Adjust the size of an open file. If this increases the file\'s size, the"] + #[doc = "/ extra bytes are filled with zeros."] + #[doc = "/"] + #[doc = "/ Note: This was called `fd_filestat_set_size` in earlier versions of WASI."] + #[allow(async_fn_in_trait)] + async fn set_size(&self, size: Filesize) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::SetSize(latch::DescriptorSetSizeArgs { size }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-size FD={self} SIZE={size}"); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.set_size(size).map_err(error_code_map), + _ => self.fd.set_size(size).await, } } - #[doc = " Adjust the timestamps of an open file or directory."] - #[doc = ""] - #[doc = " Note: This is similar to `futimens` in POSIX."] - #[doc = ""] - #[doc = " Note: This was called `fd_filestat_set_times` in earlier versions of WASI."] - fn set_times( + #[doc = "/ Adjust the timestamps of an open file or directory."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `futimens` in POSIX."] + #[doc = "/"] + #[doc = "/ Note: This was called `fd_filestat_set_times` in earlier versions of WASI."] + #[allow(async_fn_in_trait)] + async fn set_times( &self, data_access_timestamp: NewTimestamp, data_modification_timestamp: NewTimestamp, ) -> Result<(), ErrorCode> { - let data_access_timestamp = new_timestamp_map_in(data_access_timestamp); - let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); - match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -313,128 +315,83 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times FD={self} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .set_times(data_access_timestamp, data_modification_timestamp) - .map_err(error_code_map), - } - } - - #[doc = " Read from a descriptor, without using and updating the descriptor\'s offset."] - #[doc = ""] - #[doc = " This function returns a list of bytes containing the data that was"] - #[doc = " read, along with a bool which, when true, indicates that the end of the"] - #[doc = " file was reached. The returned list will contain up to `length` bytes; it"] - #[doc = " may return fewer than requested, if the end of the file is reached or"] - #[doc = " if the I/O operation is interrupted."] - #[doc = ""] - #[doc = " In the future, this may change to return a `stream`."] - #[doc = ""] - #[doc = " Note: This is similar to `pread` in POSIX."] - fn read(&self, length: Filesize, offset: Filesize) -> Result<(Vec, bool), ErrorCode> { - match authorize(&Operation::Descriptor(( - &self.fd, - self.path.to_string_lossy().into_owned(), - DescriptorOperation::Read(latch::DescriptorReadArgs { length, offset }), - ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); - warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read FD={self} LENGTH={length} OFFSET={offset}"); - Err(error_code_map(code)) - } - _ => self.fd.read(length, offset).map_err(error_code_map), - } - } - - #[doc = " Write to a descriptor, without using and updating the descriptor\'s offset."] - #[doc = ""] - #[doc = " It is valid to write past the end of a file; the file is extended to the"] - #[doc = " extent of the write, with bytes between the previous end and the start of"] - #[doc = " the write set to zero."] - #[doc = ""] - #[doc = " In the future, this may change to take a `stream`."] - #[doc = ""] - #[doc = " Note: This is similar to `pwrite` in POSIX."] - fn write(&self, buffer: Vec, offset: Filesize) -> Result { - let buffer_length: u64 = buffer - .len() - .try_into() - .expect("buffer length 64-bits or less"); - match authorize(&Operation::Descriptor(( - &self.fd, - self.path.to_string_lossy().into_owned(), - DescriptorOperation::Write(latch::DescriptorWriteArgs { - buffer_length, - offset, - }), - ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); - warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.write FD={self} BUFFER-LENGTH={buffer_length} OFFSET={offset}"); - Err(error_code_map(code)) + _ => { + self.fd + .set_times(data_access_timestamp, data_modification_timestamp) + .await } - _ => self.fd.write(&buffer, offset).map_err(error_code_map), } } - #[doc = " Read directory entries from a directory."] - #[doc = ""] - #[doc = " On filesystems where directories contain entries referring to themselves"] - #[doc = " and their parents, often named `.` and `..` respectively, these entries"] - #[doc = " are omitted."] - #[doc = ""] - #[doc = " This always returns a new stream which starts at the beginning of the"] - #[doc = " directory. Multiple streams may be active on the same directory, and they"] - #[doc = " do not interfere with each other."] - fn read_directory(&self) -> Result { + #[doc = "/ Read directory entries from a directory."] + #[doc = "/"] + #[doc = "/ On filesystems where directories contain entries referring to themselves"] + #[doc = "/ and their parents, often named `.` and `..` respectively, these entries"] + #[doc = "/ are omitted."] + #[doc = "/"] + #[doc = "/ This always returns a new stream which starts at the beginning of the"] + #[doc = "/ directory. Multiple streams may be active on the same directory, and they"] + #[doc = "/ do not interfere with each other."] + #[doc = "/"] + #[doc = "/ This function returns a future, which will resolve to an error code if"] + #[doc = "/ reading full contents of the directory fails."] + #[allow(async_fn_in_trait)] + fn read_directory( + &self, + ) -> ( + wit_bindgen::StreamReader, + wit_bindgen::FutureReader>, + ) { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::ReadDirectory, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.read-directory FD={self}"); - Err(error_code_map(code)) + let (_, dir_reader) = wit_stream::new(); + let (result_writer, result_reader) = + wit_future::new(|| Err(ErrorCode::Other(None))); + result_writer.write(Err(reason)); + (dir_reader, result_reader) + } + _ => { + // TODO authorize individual directory entries + self.fd.read_directory() } - _ => self - .fd - .read_directory() - .map(|des| directory_entry_stream_map(des, self.path.clone())) - .map_err(error_code_map), } } - #[doc = " Synchronize the data and metadata of a file to disk."] - #[doc = ""] - #[doc = " This function succeeds with no effect if the file descriptor is not"] - #[doc = " opened for writing."] - #[doc = ""] - #[doc = " Note: This is similar to `fsync` in POSIX."] - fn sync(&self) -> Result<(), ErrorCode> { + #[doc = "/ Synchronize the data and metadata of a file to disk."] + #[doc = "/"] + #[doc = "/ This function succeeds with no effect if the file descriptor is not"] + #[doc = "/ opened for writing."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `fsync` in POSIX."] + #[allow(async_fn_in_trait)] + async fn sync(&self) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::Sync, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.sync FD={self}"); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.sync().map_err(error_code_map), + _ => self.fd.sync().await, } } - #[doc = " Create a directory."] - #[doc = ""] - #[doc = " Note: This is similar to `mkdirat` in POSIX."] - fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> { + #[doc = "/ Create a directory."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `mkdirat` in POSIX."] + #[allow(async_fn_in_trait)] + async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -442,53 +399,51 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.create-directory-at FD={self} PATH={path}"); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.create_directory_at(&path).map_err(error_code_map), + _ => self.fd.create_directory_at(path).await, } } - #[doc = " Return the attributes of an open file or directory."] - #[doc = ""] - #[doc = " Note: This is similar to `fstat` in POSIX, except that it does not return"] - #[doc = " device and inode information. For testing whether two descriptors refer to"] - #[doc = " the same underlying filesystem object, use `is-same-object`. To obtain"] - #[doc = " additional data that can be used do determine whether a file has been"] - #[doc = " modified, use `metadata-hash`."] - #[doc = ""] - #[doc = " Note: This was called `fd_filestat_get` in earlier versions of WASI."] - fn stat(&self) -> Result { + #[doc = "/ Return the attributes of an open file or directory."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `fstat` in POSIX, except that it does not return"] + #[doc = "/ device and inode information. For testing whether two descriptors refer to"] + #[doc = "/ the same underlying filesystem object, use `is-same-object`. To obtain"] + #[doc = "/ additional data that can be used do determine whether a file has been"] + #[doc = "/ modified, use `metadata-hash`."] + #[doc = "/"] + #[doc = "/ Note: This was called `fd_filestat_get` in earlier versions of WASI."] + #[allow(async_fn_in_trait)] + async fn stat(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::Stat, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat FD={self}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .stat() - .map(descriptor_stat_map) - .map_err(error_code_map), + _ => self.fd.stat().await, } } - #[doc = " Return the attributes of a file or directory."] - #[doc = ""] - #[doc = " Note: This is similar to `fstatat` in POSIX, except that it does not"] - #[doc = " return device and inode information. See the `stat` description for a"] - #[doc = " discussion of alternatives."] - #[doc = ""] - #[doc = " Note: This was called `path_filestat_get` in earlier versions of WASI."] - fn stat_at(&self, path_flags: PathFlags, path: String) -> Result { - let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); - + #[doc = "/ Return the attributes of a file or directory."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `fstatat` in POSIX, except that it does not"] + #[doc = "/ return device and inode information. See the `stat` description for a"] + #[doc = "/ discussion of alternatives."] + #[doc = "/"] + #[doc = "/ Note: This was called `path_filestat_get` in earlier versions of WASI."] + #[allow(async_fn_in_trait)] + async fn stat_at( + &self, + path_flags: PathFlags, + path: String, + ) -> Result { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -497,35 +452,28 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.stat-at FD={self} PATH-FLAGS={path_flags} PATH={path}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .stat_at(path_flags, &path) - .map(descriptor_stat_map) - .map_err(error_code_map), + _ => self.fd.stat_at(path_flags, path).await, } } - #[doc = " Adjust the timestamps of a file or directory."] - #[doc = ""] - #[doc = " Note: This is similar to `utimensat` in POSIX."] - #[doc = ""] - #[doc = " Note: This was called `path_filestat_set_times` in earlier versions of"] - #[doc = " WASI."] - fn set_times_at( + #[doc = "/ Adjust the timestamps of a file or directory."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `utimensat` in POSIX."] + #[doc = "/"] + #[doc = "/ Note: This was called `path_filestat_set_times` in earlier versions of"] + #[doc = "/ WASI."] + #[allow(async_fn_in_trait)] + async fn set_times_at( &self, path_flags: PathFlags, path: String, data_access_timestamp: NewTimestamp, data_modification_timestamp: NewTimestamp, ) -> Result<(), ErrorCode> { - let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); - let data_access_timestamp = new_timestamp_map_in(data_access_timestamp); - let data_modification_timestamp = new_timestamp_map_in(data_modification_timestamp); match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -536,91 +484,83 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { data_modification_timestamp, }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.set-times-at FD={self} PATH-FLAGS={path_flags} PATH={path} ACCESS-TIME={data_access_timestamp:?} MODIFIED-TIME={data_modification_timestamp:?}"); - Err(error_code_map(code)) + Err(reason) + } + _ => { + self.fd + .set_times_at( + path_flags, + path, + data_access_timestamp, + data_modification_timestamp, + ) + .await } - _ => self - .fd - .set_times_at( - path_flags, - &path, - data_access_timestamp, - data_modification_timestamp, - ) - .map_err(error_code_map), } } - #[doc = " Create a hard link."] - #[doc = ""] - #[doc = " Note: This is similar to `linkat` in POSIX."] - fn link_at( + #[doc = "/ Create a hard link."] + #[doc = "/"] + #[doc = "/ Fails with `error-code::no-entry` if the old path does not exist,"] + #[doc = "/ with `error-code::exist` if the new path already exists, and"] + #[doc = "/ `error-code::not-permitted` if the old path is not a file."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `linkat` in POSIX."] + #[allow(async_fn_in_trait)] + async fn link_at( &self, old_path_flags: PathFlags, old_path: String, new_descriptor: DescriptorBorrow<'_>, new_path: String, ) -> Result<(), ErrorCode> { - let old_path_flags = types::PathFlags::from_bits(old_path_flags.bits()).unwrap(); + let new_descriptor: &Self = new_descriptor.get(); match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::LinkAt(latch::DescriptorLinkAtArgs { old_path_flags, old_path: old_path.clone(), - new_descriptor: &new_descriptor.get::().fd, + new_descriptor: &new_descriptor.fd, new_path: new_path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.link-at FD={self} OLD-PATH={old_path} OLD-PATH-FLAGS={old_path_flags} NEW-PATH={new_path}", ); - Err(error_code_map(code)) + Err(reason) + } + _ => { + self.fd + .link_at(old_path_flags, old_path, &new_descriptor.fd, new_path) + .await } - _ => self - .fd - .link_at( - old_path_flags, - &old_path, - &new_descriptor.get::().fd, - &new_path, - ) - .map_err(error_code_map), } } - #[doc = " Open a file or directory."] - #[doc = ""] - #[doc = " The returned descriptor is not guaranteed to be the lowest-numbered"] - #[doc = " descriptor not currently open/ it is randomized to prevent applications"] - #[doc = " from depending on making assumptions about indexes, since this is"] - #[doc = " error-prone in multi-threaded contexts. The returned descriptor is"] - #[doc = " guaranteed to be less than 2**31."] - #[doc = ""] - #[doc = " If `flags` contains `descriptor-flags::mutate-directory`, and the base"] - #[doc = " descriptor doesn\'t have `descriptor-flags::mutate-directory` set,"] - #[doc = " `open-at` fails with `error-code::read-only`."] - #[doc = ""] - #[doc = " If `flags` contains `write` or `mutate-directory`, or `open-flags`"] - #[doc = " contains `truncate` or `create`, and the base descriptor doesn\'t have"] - #[doc = " `descriptor-flags::mutate-directory` set, `open-at` fails with"] - #[doc = " `error-code::read-only`."] - #[doc = ""] - #[doc = " Note: This is similar to `openat` in POSIX."] - fn open_at( + #[doc = "/ Open a file or directory."] + #[doc = "/"] + #[doc = "/ If `flags` contains `descriptor-flags::mutate-directory`, and the base"] + #[doc = "/ descriptor doesn\'t have `descriptor-flags::mutate-directory` set,"] + #[doc = "/ `open-at` fails with `error-code::read-only`."] + #[doc = "/"] + #[doc = "/ If `flags` contains `write` or `mutate-directory`, or `open-flags`"] + #[doc = "/ contains `truncate` or `create`, and the base descriptor doesn\'t have"] + #[doc = "/ `descriptor-flags::mutate-directory` set, `open-at` fails with"] + #[doc = "/ `error-code::read-only`."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `openat` in POSIX."] + #[allow(async_fn_in_trait)] + async fn open_at( &self, path_flags: PathFlags, path: String, open_flags: OpenFlags, flags: DescriptorFlags, ) -> Result { - let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); - let open_flags = types::OpenFlags::from_bits(open_flags.bits()).unwrap(); - let flags = types::DescriptorFlags::from_bits(flags.bits()).unwrap(); match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -631,48 +571,48 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { flags, }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.open-at FD={self} PATH-FLAGS={path_flags} PATH={path} OPEN-FLAGS={open_flags} FLAGS={flags}"); - Err(error_code_map(code)) + Err(reason) } _ => self .fd - .open_at(path_flags, &path.clone(), open_flags, flags) - .map(|descriptor| descriptor_map(descriptor, self.path.join(path))) - .map_err(error_code_map), + .open_at(path_flags, path.clone(), open_flags, flags) + .await + .map(|fd| Descriptor::new(GatedFileDescriptor::new(fd, self.path.join(path)))), } } - #[doc = " Read the contents of a symbolic link."] - #[doc = ""] - #[doc = " If the contents contain an absolute or rooted path in the underlying"] - #[doc = " filesystem, this function fails with `error-code::not-permitted`."] - #[doc = ""] - #[doc = " Note: This is similar to `readlinkat` in POSIX."] - fn readlink_at(&self, path: String) -> Result { + #[doc = "/ Read the contents of a symbolic link."] + #[doc = "/"] + #[doc = "/ If the contents contain an absolute or rooted path in the underlying"] + #[doc = "/ filesystem, this function fails with `error-code::not-permitted`."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `readlinkat` in POSIX."] + #[allow(async_fn_in_trait)] + async fn readlink_at(&self, path: String) -> Result { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::ReadlinkAt(latch::DescriptorReadlinkAtArgs { path: path.clone() }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.readlink-at FD={self} PATH={path}", ); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.readlink_at(&path).map_err(error_code_map), + _ => self.fd.readlink_at(path).await, } } - #[doc = " Remove a directory."] - #[doc = ""] - #[doc = " Return `error-code::not-empty` if the directory is not empty."] - #[doc = ""] - #[doc = " Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX."] - fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> { + #[doc = "/ Remove a directory."] + #[doc = "/"] + #[doc = "/ Return `error-code::not-empty` if the directory is not empty."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX."] + #[allow(async_fn_in_trait)] + async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -680,19 +620,19 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.remove-directory-at FD={self} PATH={path}"); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.remove_directory_at(&path).map_err(error_code_map), + _ => self.fd.remove_directory_at(path).await, } } - #[doc = " Rename a filesystem object."] - #[doc = ""] - #[doc = " Note: This is similar to `renameat` in POSIX."] - fn rename_at( + #[doc = "/ Rename a filesystem object."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `renameat` in POSIX."] + #[allow(async_fn_in_trait)] + async fn rename_at( &self, old_path: String, new_descriptor: DescriptorBorrow<'_>, @@ -707,29 +647,29 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.rename-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); - Err(error_code_map(code)) + Err(reason) } _ => { let new_descriptor: &Self = new_descriptor.get(); self.fd - .rename_at(&old_path, &new_descriptor.fd, &new_path) - .map_err(error_code_map) + .rename_at(old_path, &new_descriptor.fd, new_path) + .await } } } - #[doc = " Create a symbolic link (also known as a \"symlink\")."] - #[doc = ""] - #[doc = " If `old-path` starts with `/`, the function fails with"] - #[doc = " `error-code::not-permitted`."] - #[doc = ""] - #[doc = " Note: This is similar to `symlinkat` in POSIX."] - fn symlink_at(&self, old_path: String, new_path: String) -> Result<(), ErrorCode> { + #[doc = "/ Create a symbolic link (also known as a \"symlink\")."] + #[doc = "/"] + #[doc = "/ If `old-path` starts with `/`, the function fails with"] + #[doc = "/ `error-code::not-permitted`."] + #[doc = "/"] + #[doc = "/ Note: This is similar to `symlinkat` in POSIX."] + #[allow(async_fn_in_trait)] + async fn symlink_at(&self, old_path: String, new_path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -738,25 +678,27 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { new_path: new_path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.symlink-at FD={self} OLD-PATH={old_path} NEW-PATH={new_path}", ); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .symlink_at(&old_path, &new_path) - .map_err(error_code_map), + _ => self.fd.symlink_at(old_path, new_path).await, } } - #[doc = " Unlink a filesystem object that is not a directory."] - #[doc = ""] - #[doc = " Return `error-code::is-directory` if the path refers to a directory."] - #[doc = " Note: This is similar to `unlinkat(fd, path, 0)` in POSIX."] - fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { + #[doc = "/ Unlink a filesystem object that is not a directory."] + #[doc = "/"] + #[doc = "/ This is similar to `unlinkat(fd, path, 0)` in POSIX."] + #[doc = "/"] + #[doc = "/ Error returns are as specified by POSIX."] + #[doc = "/"] + #[doc = "/ If the filesystem object is a directory, `error-code::access` or"] + #[doc = "/ `error-code::is-directory` may be returned instead of the"] + #[doc = "/ POSIX-specified `error-code::not-permitted`."] + #[allow(async_fn_in_trait)] + async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -764,76 +706,72 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!( "Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.unlink-file-at FD={self} PATH={path}", ); - Err(error_code_map(code)) + Err(reason) } - _ => self.fd.unlink_file_at(&path).map_err(error_code_map), + _ => self.fd.unlink_file_at(path).await, } } - #[doc = " Test whether two descriptors refer to the same filesystem object."] - #[doc = ""] - #[doc = " In POSIX, this corresponds to testing whether the two descriptors have the"] - #[doc = " same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers."] - #[doc = " wasi-filesystem does not expose device and inode numbers, so this function"] - #[doc = " may be used instead."] - fn is_same_object(&self, other: DescriptorBorrow<'_>) -> bool { + #[doc = "/ Test whether two descriptors refer to the same filesystem object."] + #[doc = "/"] + #[doc = "/ In POSIX, this corresponds to testing whether the two descriptors have the"] + #[doc = "/ same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers."] + #[doc = "/ wasi-filesystem does not expose device and inode numbers, so this function"] + #[doc = "/ may be used instead."] + #[allow(async_fn_in_trait)] + async fn is_same_object(&self, other: DescriptorBorrow<'_>) -> bool { let other: &Self = other.get(); - self.fd.is_same_object(&other.fd) - } - - #[doc = " Return a hash of the metadata associated with a filesystem object referred"] - #[doc = " to by a descriptor."] - #[doc = ""] - #[doc = " This returns a hash of the last-modification timestamp and file size, and"] - #[doc = " may also include the inode number, device number, birth timestamp, and"] - #[doc = " other metadata fields that may change when the file is modified or"] - #[doc = " replaced. It may also include a secret value chosen by the"] - #[doc = " implementation and not otherwise exposed."] - #[doc = ""] - #[doc = " Implementations are encourated to provide the following properties:"] - #[doc = ""] - #[doc = " - If the file is not modified or replaced, the computed hash value should"] - #[doc = " usually not change."] - #[doc = " - If the object is modified or replaced, the computed hash value should"] - #[doc = " usually change."] - #[doc = " - The inputs to the hash should not be easily computable from the"] - #[doc = " computed hash."] - #[doc = ""] - #[doc = " However, none of these is required."] - fn metadata_hash(&self) -> Result { + self.fd.is_same_object(&other.fd).await + } + + #[doc = "/ Return a hash of the metadata associated with a filesystem object referred"] + #[doc = "/ to by a descriptor."] + #[doc = "/"] + #[doc = "/ This returns a hash of the last-modification timestamp and file size, and"] + #[doc = "/ may also include the inode number, device number, birth timestamp, and"] + #[doc = "/ other metadata fields that may change when the file is modified or"] + #[doc = "/ replaced. It may also include a secret value chosen by the"] + #[doc = "/ implementation and not otherwise exposed."] + #[doc = "/"] + #[doc = "/ Implementations are encouraged to provide the following properties:"] + #[doc = "/"] + #[doc = "/ - If the file is not modified or replaced, the computed hash value should"] + #[doc = "/ usually not change."] + #[doc = "/ - If the object is modified or replaced, the computed hash value should"] + #[doc = "/ usually change."] + #[doc = "/ - The inputs to the hash should not be easily computable from the"] + #[doc = "/ computed hash."] + #[doc = "/"] + #[doc = "/ However, none of these is required."] + #[allow(async_fn_in_trait)] + async fn metadata_hash(&self) -> Result { match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), DescriptorOperation::MetadataHash, ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash FD={self}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .metadata_hash() - .map(metadata_hash_value_map) - .map_err(error_code_map), + _ => self.fd.metadata_hash().await, } } - #[doc = " Return a hash of the metadata associated with a filesystem object referred"] - #[doc = " to by a directory descriptor and a relative path."] - #[doc = ""] - #[doc = " This performs the same hash computation as `metadata-hash`."] - fn metadata_hash_at( + #[doc = "/ Return a hash of the metadata associated with a filesystem object referred"] + #[doc = "/ to by a directory descriptor and a relative path."] + #[doc = "/"] + #[doc = "/ This performs the same hash computation as `metadata-hash`."] + #[allow(async_fn_in_trait)] + async fn metadata_hash_at( &self, path_flags: PathFlags, path: String, ) -> Result { - let path_flags = types::PathFlags::from_bits(path_flags.bits()).unwrap(); match authorize(&Operation::Descriptor(( &self.fd, self.path.to_string_lossy().into_owned(), @@ -842,189 +780,21 @@ impl exports::wasi::filesystem::types::GuestDescriptor for GatedFileDescriptor { path: path.clone(), }), ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); + Some(Denied(reason)) => { warn!("Denied REASON={reason} OPERATION=wasi:filesystem/types#descriptor.metadata-hash-at FD={self} PATH={path} PATH-FLAGS={path_flags}"); - Err(error_code_map(code)) + Err(reason) } - _ => self - .fd - .metadata_hash_at(path_flags, &path) - .map(metadata_hash_value_map) - .map_err(error_code_map), + _ => self.fd.metadata_hash_at(path_flags, path).await, } } } -#[derive(Debug, Clone)] -struct GatedDirectoryEntryStream { - des: Rc, - path: PathBuf, -} - -impl GatedDirectoryEntryStream { - fn new(des: types::DirectoryEntryStream, path: PathBuf) -> Self { - Self { - des: Rc::new(des), - path, - } - } -} - -impl Display for GatedDirectoryEntryStream { +impl Display for GatedFileDescriptor { fn fmt(&self, d: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { d.write_fmt(format_args!("{}", self.path.display())) } } -impl exports::wasi::filesystem::types::GuestDirectoryEntryStream for GatedDirectoryEntryStream { - #[doc = " Read a single directory entry from a `directory-entry-stream`."] - fn read_directory_entry(&self) -> Result, ErrorCode> { - match self.des.read_directory_entry() { - Ok(Some(de)) => { - match authorize(&Operation::DirectoryEntryStream(( - &self.des, - self.path.to_string_lossy().into_owned(), - DirectoryEntryStreamOperation::ReadDirectoryEntry(de.clone()), - ))) { - Some(Denied(code)) => { - let reason = error_code_display(code); - trace!("Denied REASON={reason} OPERATION=wasi:filesystem/types#directory-entry-stream.read-directory-entry STREAM={} ENTRY={}", self, de.name); - // continue reading the next entry transparently - self.read_directory_entry() - } - _ => Ok(Some(directory_entry_map(de))), - } - } - Ok(None) => Ok(None), - Err(code) => Err(error_code_map(code)), - } - } -} - -fn advice_map_in(advice: Advice) -> types::Advice { - match advice { - Advice::Normal => types::Advice::Normal, - Advice::Sequential => types::Advice::Sequential, - Advice::Random => types::Advice::Random, - Advice::WillNeed => types::Advice::WillNeed, - Advice::DontNeed => types::Advice::DontNeed, - Advice::NoReuse => types::Advice::NoReuse, - } -} - -fn descriptor_map(descriptor: types::Descriptor, path: PathBuf) -> Descriptor { - Descriptor::new(GatedFileDescriptor::new(descriptor, path)) -} - -fn descriptor_flags_map(descriptor_flags: types::DescriptorFlags) -> DescriptorFlags { - DescriptorFlags::from_bits(descriptor_flags.bits()).unwrap() -} - -fn descriptor_stat_map(descriptor_stat: types::DescriptorStat) -> DescriptorStat { - DescriptorStat { - type_: descriptor_type_map(descriptor_stat.type_), - link_count: descriptor_stat.link_count, - size: descriptor_stat.size, - data_access_timestamp: descriptor_stat.data_access_timestamp, - data_modification_timestamp: descriptor_stat.data_modification_timestamp, - status_change_timestamp: descriptor_stat.status_change_timestamp, - } -} - -fn descriptor_type_map(descriptor_type: types::DescriptorType) -> DescriptorType { - match descriptor_type { - types::DescriptorType::Unknown => DescriptorType::Unknown, - types::DescriptorType::BlockDevice => DescriptorType::BlockDevice, - types::DescriptorType::CharacterDevice => DescriptorType::CharacterDevice, - types::DescriptorType::Directory => DescriptorType::Directory, - types::DescriptorType::Fifo => DescriptorType::Fifo, - types::DescriptorType::SymbolicLink => DescriptorType::SymbolicLink, - types::DescriptorType::RegularFile => DescriptorType::RegularFile, - types::DescriptorType::Socket => DescriptorType::Socket, - } -} - -fn directory_entry_map(directory_entry: types::DirectoryEntry) -> DirectoryEntry { - DirectoryEntry { - name: directory_entry.name, - type_: descriptor_type_map(directory_entry.type_), - } -} - -fn directory_entry_stream_map( - directory_entry_stream: types::DirectoryEntryStream, - path: PathBuf, -) -> DirectoryEntryStream { - DirectoryEntryStream::new(GatedDirectoryEntryStream::new(directory_entry_stream, path)) -} - -fn error_code_map(error_code: types::ErrorCode) -> ErrorCode { - match error_code { - types::ErrorCode::Access => ErrorCode::Access, - types::ErrorCode::WouldBlock => ErrorCode::WouldBlock, - types::ErrorCode::Already => ErrorCode::Already, - types::ErrorCode::BadDescriptor => ErrorCode::BadDescriptor, - types::ErrorCode::Busy => ErrorCode::Busy, - types::ErrorCode::Deadlock => ErrorCode::Deadlock, - types::ErrorCode::Quota => ErrorCode::Quota, - types::ErrorCode::Exist => ErrorCode::Exist, - types::ErrorCode::FileTooLarge => ErrorCode::FileTooLarge, - types::ErrorCode::IllegalByteSequence => ErrorCode::IllegalByteSequence, - types::ErrorCode::InProgress => ErrorCode::InProgress, - types::ErrorCode::Interrupted => ErrorCode::Interrupted, - types::ErrorCode::Invalid => ErrorCode::Invalid, - types::ErrorCode::Io => ErrorCode::Io, - types::ErrorCode::IsDirectory => ErrorCode::IsDirectory, - types::ErrorCode::Loop => ErrorCode::Loop, - types::ErrorCode::TooManyLinks => ErrorCode::TooManyLinks, - types::ErrorCode::MessageSize => ErrorCode::MessageSize, - types::ErrorCode::NameTooLong => ErrorCode::NameTooLong, - types::ErrorCode::NoDevice => ErrorCode::NoDevice, - types::ErrorCode::NoEntry => ErrorCode::NoEntry, - types::ErrorCode::NoLock => ErrorCode::NoLock, - types::ErrorCode::InsufficientMemory => ErrorCode::InsufficientMemory, - types::ErrorCode::InsufficientSpace => ErrorCode::InsufficientSpace, - types::ErrorCode::NotDirectory => ErrorCode::NotDirectory, - types::ErrorCode::NotEmpty => ErrorCode::NotEmpty, - types::ErrorCode::NotRecoverable => ErrorCode::NotRecoverable, - types::ErrorCode::Unsupported => ErrorCode::Unsupported, - types::ErrorCode::NoTty => ErrorCode::NoTty, - types::ErrorCode::NoSuchDevice => ErrorCode::NoSuchDevice, - types::ErrorCode::Overflow => ErrorCode::Overflow, - types::ErrorCode::NotPermitted => ErrorCode::NotPermitted, - types::ErrorCode::Pipe => ErrorCode::Pipe, - types::ErrorCode::ReadOnly => ErrorCode::ReadOnly, - types::ErrorCode::InvalidSeek => ErrorCode::InvalidSeek, - types::ErrorCode::TextFileBusy => ErrorCode::TextFileBusy, - types::ErrorCode::CrossDevice => ErrorCode::CrossDevice, - } -} - -fn error_code_display(error_code: types::ErrorCode) -> String { - error_code - .to_string() - .splitn(2, ' ') - .next() - .unwrap_or("") - .to_kebab_case() -} - -fn metadata_hash_value_map(metadata_hash_value: types::MetadataHashValue) -> MetadataHashValue { - MetadataHashValue { - lower: metadata_hash_value.lower, - upper: metadata_hash_value.upper, - } -} - -fn new_timestamp_map_in(timestamp: NewTimestamp) -> types::NewTimestamp { - match timestamp { - NewTimestamp::NoChange => types::NewTimestamp::NoChange, - NewTimestamp::Now => types::NewTimestamp::Now, - NewTimestamp::Timestamp(dt) => types::NewTimestamp::Timestamp(dt), - } -} - impl Display for types::Advice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.to_string().to_kebab_case()) @@ -1061,9 +831,43 @@ impl Display for types::PathFlags { } } +impl Display for types::NewTimestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + types::NewTimestamp::NoChange => f.write_str("no-change"), + types::NewTimestamp::Now => f.write_str("now"), + types::NewTimestamp::Timestamp(instant) => { + f.write_fmt(format_args!("timestamp<{instant}>")) + } + } + } +} + +impl Display for types::Instant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let date = DateTime::from_timestamp(self.seconds as i64, self.nanoseconds) + .unwrap() + .format("%Y-%m-%d %H:%M:%S.%3fZ"); + + f.write_fmt(format_args!("{date}")) + } +} + +impl Display for types::DescriptorType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + types::DescriptorType::Other(Some(type_)) => { + f.write_fmt(format_args!("other<{type_}>")) + } + _ => f.write_str(&self.to_string().to_kebab_case()), + } + } +} + wit_bindgen::generate!({ path: "../../wit", world: "filesystem", + merge_structurally_equal_types: true, generate_all }); diff --git a/components/latch-deny-all/src/lib.rs b/components/latch-deny-all/src/lib.rs index 4133be2..3f2baba 100644 --- a/components/latch-deny-all/src/lib.rs +++ b/components/latch-deny-all/src/lib.rs @@ -16,6 +16,7 @@ impl Latch for DenyAllLatch { wit_bindgen::generate!({ path: "../../wit", world: "filesystem-latch", + merge_structurally_equal_types: true, generate_all }); diff --git a/components/latch-glob/src/lib.rs b/components/latch-glob/src/lib.rs index 9b012c0..6917d98 100644 --- a/components/latch-glob/src/lib.rs +++ b/components/latch-glob/src/lib.rs @@ -6,8 +6,7 @@ use path_matchers::{glob, PathMatcher}; use crate::{ exports::componentized::filesystem::latch::{ - Decision, DescriptorOperation, DirectoryEntryStreamOperation, Guest as Latch, Operation, - PreopensOperation, + Decision, DescriptorOperation, Guest as Latch, Operation, PreopensOperation, }, wasi::filesystem::types::ErrorCode, }; @@ -18,7 +17,7 @@ struct Patterns { initialized: bool, denies: Option>>, grants: Option>>, - deny_reason: Option, + deny_reason: ErrorCode, } impl Patterns { @@ -41,14 +40,14 @@ impl Patterns { glob(&value).expect("config value must parse as a glob"), )); } else if key == "reason" { - reason = get_error_code(value).unwrap_or(ErrorCode::NotPermitted); + reason = get_error_code(value); } } unsafe { STATE.denies = Some(denies); STATE.grants = Some(grants); - STATE.deny_reason = Some(reason); + STATE.deny_reason = reason; STATE.initialized = true; }; } @@ -75,7 +74,7 @@ impl Patterns { fn authorize_path(&self, path: &Path) -> Option { for deny in self.denies.as_ref().unwrap() { if deny.matches(path) { - return Some(Decision::Denied(self.deny_reason.unwrap())); + return Some(Decision::Denied(self.deny_reason.clone())); } } for grant in self.grants.as_ref().unwrap() { @@ -91,7 +90,7 @@ static mut STATE: Patterns = Patterns { initialized: false, denies: None, grants: None, - deny_reason: None, + deny_reason: ErrorCode::NotPermitted, }; impl Latch for GlobLatch { @@ -114,8 +113,6 @@ impl Latch for GlobLatch { DescriptorOperation::GetType => Patterns::authorize(path, vec![]), DescriptorOperation::SetSize(_) => Patterns::authorize(path, vec![]), DescriptorOperation::SetTimes(_) => Patterns::authorize(path, vec![]), - DescriptorOperation::Read(_) => Patterns::authorize(path, vec![]), - DescriptorOperation::Write(_) => Patterns::authorize(path, vec![]), DescriptorOperation::ReadDirectory => Patterns::authorize(path, vec![]), DescriptorOperation::Sync => Patterns::authorize(path, vec![]), DescriptorOperation::CreateDirectoryAt(args) => { @@ -146,63 +143,57 @@ impl Latch for GlobLatch { Patterns::authorize(path, vec![args.path]) } }, - Operation::DirectoryEntryStream((_, path, directory_entry_stream_operation)) => { - match directory_entry_stream_operation { - DirectoryEntryStreamOperation::ReadDirectoryEntry(directory_entry) => { - Patterns::authorize(path, vec![directory_entry.name]) - } - } - } } } } -fn get_error_code(value: String) -> Option { +fn get_error_code(value: String) -> ErrorCode { match value.as_str() { - "access" => Some(ErrorCode::Access), - "wouldblock" => Some(ErrorCode::WouldBlock), - "already" => Some(ErrorCode::Already), - "bad-descriptor" => Some(ErrorCode::BadDescriptor), - "busy" => Some(ErrorCode::Busy), - "deadlock" => Some(ErrorCode::Deadlock), - "quota" => Some(ErrorCode::Quota), - "exist" => Some(ErrorCode::Exist), - "file-too-large" => Some(ErrorCode::FileTooLarge), - "illegal-byte-sequence" => Some(ErrorCode::IllegalByteSequence), - "in-progress" => Some(ErrorCode::InProgress), - "interrupted" => Some(ErrorCode::Interrupted), - "invalid" => Some(ErrorCode::Invalid), - "io" => Some(ErrorCode::Io), - "is-directory" => Some(ErrorCode::IsDirectory), - "loop" => Some(ErrorCode::Loop), - "too-many-links" => Some(ErrorCode::TooManyLinks), - "message-size" => Some(ErrorCode::MessageSize), - "name-too-long" => Some(ErrorCode::NameTooLong), - "no-device" => Some(ErrorCode::NoDevice), - "no-entry" => Some(ErrorCode::NoEntry), - "no-lock" => Some(ErrorCode::NoLock), - "insufficient-memory" => Some(ErrorCode::InsufficientMemory), - "insufficient-space" => Some(ErrorCode::InsufficientSpace), - "not-directory" => Some(ErrorCode::NotDirectory), - "not-empty" => Some(ErrorCode::NotEmpty), - "not-recoverable" => Some(ErrorCode::NotRecoverable), - "unsupported" => Some(ErrorCode::Unsupported), - "no-tty" => Some(ErrorCode::NoTty), - "no-such-device" => Some(ErrorCode::NoSuchDevice), - "overflow" => Some(ErrorCode::Overflow), - "not-permitted" => Some(ErrorCode::NotPermitted), - "pipe" => Some(ErrorCode::Pipe), - "read-only" => Some(ErrorCode::ReadOnly), - "invalid-seek" => Some(ErrorCode::InvalidSeek), - "text-file-busy" => Some(ErrorCode::TextFileBusy), - "cross-device" => Some(ErrorCode::CrossDevice), - _ => None, + "access" => ErrorCode::Access, + "already" => ErrorCode::Already, + "bad-descriptor" => ErrorCode::BadDescriptor, + "busy" => ErrorCode::Busy, + "deadlock" => ErrorCode::Deadlock, + "quota" => ErrorCode::Quota, + "exist" => ErrorCode::Exist, + "file-too-large" => ErrorCode::FileTooLarge, + "illegal-byte-sequence" => ErrorCode::IllegalByteSequence, + "in-progress" => ErrorCode::InProgress, + "interrupted" => ErrorCode::Interrupted, + "invalid" => ErrorCode::Invalid, + "io" => ErrorCode::Io, + "is-directory" => ErrorCode::IsDirectory, + "loop" => ErrorCode::Loop, + "too-many-links" => ErrorCode::TooManyLinks, + "message-size" => ErrorCode::MessageSize, + "name-too-long" => ErrorCode::NameTooLong, + "no-device" => ErrorCode::NoDevice, + "no-entry" => ErrorCode::NoEntry, + "no-lock" => ErrorCode::NoLock, + "insufficient-memory" => ErrorCode::InsufficientMemory, + "insufficient-space" => ErrorCode::InsufficientSpace, + "not-directory" => ErrorCode::NotDirectory, + "not-empty" => ErrorCode::NotEmpty, + "not-recoverable" => ErrorCode::NotRecoverable, + "unsupported" => ErrorCode::Unsupported, + "no-tty" => ErrorCode::NoTty, + "no-such-device" => ErrorCode::NoSuchDevice, + "overflow" => ErrorCode::Overflow, + "not-permitted" => ErrorCode::NotPermitted, + "pipe" => ErrorCode::Pipe, + "read-only" => ErrorCode::ReadOnly, + "invalid-seek" => ErrorCode::InvalidSeek, + "text-file-busy" => ErrorCode::TextFileBusy, + "cross-device" => ErrorCode::CrossDevice, + "other" => ErrorCode::Other(None), + _ => ErrorCode::Other(Some(value)), } } wit_bindgen::generate!({ path: "../../wit", world: "filesystem-latch", + merge_structurally_equal_types: true, generate_all }); diff --git a/components/latch-grant-all/src/lib.rs b/components/latch-grant-all/src/lib.rs index 09f7107..318e16c 100644 --- a/components/latch-grant-all/src/lib.rs +++ b/components/latch-grant-all/src/lib.rs @@ -13,6 +13,7 @@ impl Latch for GrantAllLatch { wit_bindgen::generate!({ path: "../../wit", world: "filesystem-latch", + merge_structurally_equal_types: true, generate_all }); diff --git a/components/latch-readonly/src/lib.rs b/components/latch-readonly/src/lib.rs index 7665788..aef5a85 100644 --- a/components/latch-readonly/src/lib.rs +++ b/components/latch-readonly/src/lib.rs @@ -24,8 +24,6 @@ impl Latch for ReadOnlyLatch { DescriptorOperation::GetType => None, DescriptorOperation::SetSize(_) => Some(Denied(ReadOnly)), DescriptorOperation::SetTimes(_) => Some(Denied(ReadOnly)), - DescriptorOperation::Read(_) => None, - DescriptorOperation::Write(_) => Some(Denied(ReadOnly)), DescriptorOperation::ReadDirectory => None, DescriptorOperation::Sync => Some(Denied(ReadOnly)), DescriptorOperation::CreateDirectoryAt(_) => Some(Denied(ReadOnly)), @@ -59,7 +57,6 @@ impl Latch for ReadOnlyLatch { DescriptorOperation::MetadataHash => None, DescriptorOperation::MetadataHashAt(_) => None, }, - Operation::DirectoryEntryStream(_) => None, } } } @@ -67,6 +64,7 @@ impl Latch for ReadOnlyLatch { wit_bindgen::generate!({ path: "../../wit", world: "filesystem-latch", + merge_structurally_equal_types: true, generate_all }); diff --git a/components/tracing/src/lib.rs b/components/tracing/src/lib.rs index 56d4aef..dd9b714 100644 --- a/components/tracing/src/lib.rs +++ b/components/tracing/src/lib.rs @@ -507,13 +507,8 @@ impl exports::wasi::filesystem::types::GuestDescriptor for TracingDescriptor { } impl Display for TracingDescriptor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - &self - .path - .to_str() - .expect("path contains invalid unicode characters"), - ) + fn fmt(&self, d: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + d.write_fmt(format_args!("{}", self.path.display())) } } diff --git a/crates/latch-n/src/lib.rs b/crates/latch-n/src/lib.rs index 6349284..547abcd 100644 --- a/crates/latch-n/src/lib.rs +++ b/crates/latch-n/src/lib.rs @@ -1,306 +1,27 @@ #![no_main] -use crate::bindings::{ - componentized::filesystem::latch, - exports::componentized::filesystem::latch::{ - Decision, DescriptorAdviseArgs, DescriptorCreateDirectoryAtArgs, DescriptorLinkAtArgs, - DescriptorMetadataHashAtArgs, DescriptorOpenAtArgs, DescriptorOperation, - DescriptorReadArgs, DescriptorReadViaStreamArgs, DescriptorReadlinkAtArgs, - DescriptorRemoveDirectoryAtArgs, DescriptorRenameAtArgs, DescriptorSetSizeArgs, - DescriptorSetTimesArgs, DescriptorSetTimesAtArgs, DescriptorStatAtArgs, - DescriptorSymlinkAtArgs, DescriptorUnlinkFileAtArgs, DescriptorWriteArgs, - DescriptorWriteViaStreamArgs, DirectoryEntryStreamOperation, Operation, PreopensOperation, - }, -}; +use crate::bindings::exports::componentized::filesystem::latch::{Decision, Operation}; pub fn authorize( operation: Operation, - authorizers: Vec) -> Option>, + authorizers: Vec) -> Option>, ) -> Option { - let operation = operation_map(operation); for authorize in authorizers { match authorize(&operation) { None => {} - Some(latch::Decision::Granted) => return Some(Decision::Granted), - Some(latch::Decision::Denied(error_code)) => return Some(Decision::Denied(error_code)), + Some(Decision::Granted) => return Some(Decision::Granted), + Some(Decision::Denied(error_code)) => return Some(Decision::Denied(error_code)), } } None } -fn operation_map(operation: Operation) -> latch::Operation { - match operation { - Operation::Preopens(preopens_operation) => { - latch::Operation::Preopens(preopens_operation_map(preopens_operation)) - } - Operation::Descriptor((descriptor, path, descriptor_operation)) => { - latch::Operation::Descriptor(( - descriptor, - path, - descriptor_operation_map(descriptor_operation), - )) - } - Operation::DirectoryEntryStream(( - directory_entry_stream, - path, - directory_entry_stream_operation, - )) => latch::Operation::DirectoryEntryStream(( - directory_entry_stream, - path, - directory_entry_stream_operation_map(directory_entry_stream_operation), - )), - } -} - -fn preopens_operation_map(preopens_operation: PreopensOperation) -> latch::PreopensOperation { - match preopens_operation { - PreopensOperation::GetDirectoriesItem((fd, path)) => { - latch::PreopensOperation::GetDirectoriesItem((fd, path)) - } - } -} - -fn descriptor_operation_map( - descriptor_operation: DescriptorOperation, -) -> latch::DescriptorOperation { - match descriptor_operation { - DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args) => { - latch::DescriptorOperation::ReadViaStream(descriptor_read_via_stream_args_map( - descriptor_read_via_stream_args, - )) - } - DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args) => { - latch::DescriptorOperation::WriteViaStream(descriptor_write_via_stream_args_map( - descriptor_write_via_stream_args, - )) - } - DescriptorOperation::AppendViaStream => latch::DescriptorOperation::AppendViaStream, - DescriptorOperation::Advise(descriptor_advise_args) => { - latch::DescriptorOperation::Advise(descriptor_advise_args_map(descriptor_advise_args)) - } - DescriptorOperation::SyncData => latch::DescriptorOperation::SyncData, - DescriptorOperation::GetFlags => latch::DescriptorOperation::GetFlags, - DescriptorOperation::GetType => latch::DescriptorOperation::GetType, - DescriptorOperation::SetSize(descriptor_set_size_args) => { - latch::DescriptorOperation::SetSize(descriptor_set_size_args_map( - descriptor_set_size_args, - )) - } - DescriptorOperation::SetTimes(descriptor_set_times_args) => { - latch::DescriptorOperation::SetTimes(descriptor_set_times_args_map( - descriptor_set_times_args, - )) - } - DescriptorOperation::Read(descriptor_read_args) => { - latch::DescriptorOperation::Read(descriptor_read_args_map(descriptor_read_args)) - } - DescriptorOperation::Write(descriptor_write_args) => { - latch::DescriptorOperation::Write(descriptor_write_args_map(descriptor_write_args)) - } - DescriptorOperation::ReadDirectory => latch::DescriptorOperation::ReadDirectory, - DescriptorOperation::Sync => latch::DescriptorOperation::Sync, - DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args) => { - latch::DescriptorOperation::CreateDirectoryAt(descriptor_create_directory_at_args_map( - descriptor_create_directory_at_args, - )) - } - DescriptorOperation::Stat => latch::DescriptorOperation::Stat, - DescriptorOperation::StatAt(descriptor_stat_at_args) => { - latch::DescriptorOperation::StatAt(descriptor_stat_at_args_map(descriptor_stat_at_args)) - } - DescriptorOperation::SetTimesAt(descriptor_set_times_at_args) => { - latch::DescriptorOperation::SetTimesAt(descriptor_set_times_at_args_map( - descriptor_set_times_at_args, - )) - } - DescriptorOperation::LinkAt(descriptor_link_at_args) => { - latch::DescriptorOperation::LinkAt(descriptor_link_at_args_map(descriptor_link_at_args)) - } - DescriptorOperation::OpenAt(descriptor_open_at_args) => { - latch::DescriptorOperation::OpenAt(descriptor_open_at_args_map(descriptor_open_at_args)) - } - DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args) => { - latch::DescriptorOperation::ReadlinkAt(descriptor_readlink_at_args_map( - descriptor_readlink_at_args, - )) - } - DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args) => { - latch::DescriptorOperation::RemoveDirectoryAt(descriptor_remove_directory_at_args_map( - descriptor_remove_directory_at_args, - )) - } - DescriptorOperation::RenameAt(descriptor_rename_at_args) => { - latch::DescriptorOperation::RenameAt(descriptor_rename_at_args_map( - descriptor_rename_at_args, - )) - } - DescriptorOperation::SymlinkAt(descriptor_symlink_at_args) => { - latch::DescriptorOperation::SymlinkAt(descriptor_symlink_at_args_map( - descriptor_symlink_at_args, - )) - } - DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args) => { - latch::DescriptorOperation::UnlinkFileAt(descriptor_unlink_file_at_args_map( - descriptor_unlink_file_at_args, - )) - } - DescriptorOperation::MetadataHash => latch::DescriptorOperation::MetadataHash, - DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args) => { - latch::DescriptorOperation::MetadataHashAt(descriptor_metadata_hash_at_args_map( - descriptor_metadata_hash_at_args, - )) - } - } -} - -fn descriptor_read_via_stream_args_map( - args: DescriptorReadViaStreamArgs, -) -> latch::DescriptorReadViaStreamArgs { - latch::DescriptorReadViaStreamArgs { - offset: args.offset, - } -} - -fn descriptor_write_via_stream_args_map( - args: DescriptorWriteViaStreamArgs, -) -> latch::DescriptorWriteViaStreamArgs { - latch::DescriptorWriteViaStreamArgs { - offset: args.offset, - } -} - -fn descriptor_advise_args_map(args: DescriptorAdviseArgs) -> latch::DescriptorAdviseArgs { - latch::DescriptorAdviseArgs { - offset: args.offset, - length: args.length, - advice: args.advice, - } -} - -fn descriptor_set_size_args_map(args: DescriptorSetSizeArgs) -> latch::DescriptorSetSizeArgs { - latch::DescriptorSetSizeArgs { size: args.size } -} - -fn descriptor_set_times_args_map(args: DescriptorSetTimesArgs) -> latch::DescriptorSetTimesArgs { - latch::DescriptorSetTimesArgs { - data_access_timestamp: args.data_access_timestamp, - data_modification_timestamp: args.data_modification_timestamp, - } -} - -fn descriptor_read_args_map(args: DescriptorReadArgs) -> latch::DescriptorReadArgs { - latch::DescriptorReadArgs { - length: args.length, - offset: args.offset, - } -} - -fn descriptor_write_args_map(args: DescriptorWriteArgs) -> latch::DescriptorWriteArgs { - latch::DescriptorWriteArgs { - buffer_length: args.buffer_length, - offset: args.offset, - } -} - -fn descriptor_create_directory_at_args_map( - args: DescriptorCreateDirectoryAtArgs, -) -> latch::DescriptorCreateDirectoryAtArgs { - latch::DescriptorCreateDirectoryAtArgs { path: args.path } -} - -fn descriptor_stat_at_args_map(args: DescriptorStatAtArgs) -> latch::DescriptorStatAtArgs { - latch::DescriptorStatAtArgs { - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_set_times_at_args_map( - args: DescriptorSetTimesAtArgs, -) -> latch::DescriptorSetTimesAtArgs { - latch::DescriptorSetTimesAtArgs { - data_access_timestamp: args.data_access_timestamp, - data_modification_timestamp: args.data_modification_timestamp, - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_link_at_args_map(args: DescriptorLinkAtArgs) -> latch::DescriptorLinkAtArgs { - latch::DescriptorLinkAtArgs { - new_descriptor: args.new_descriptor, - new_path: args.new_path, - old_path: args.old_path, - old_path_flags: args.old_path_flags, - } -} - -fn descriptor_open_at_args_map(args: DescriptorOpenAtArgs) -> latch::DescriptorOpenAtArgs { - latch::DescriptorOpenAtArgs { - flags: args.flags, - open_flags: args.open_flags, - path: args.path, - path_flags: args.path_flags, - } -} - -fn descriptor_readlink_at_args_map( - args: DescriptorReadlinkAtArgs, -) -> latch::DescriptorReadlinkAtArgs { - latch::DescriptorReadlinkAtArgs { path: args.path } -} - -fn descriptor_remove_directory_at_args_map( - args: DescriptorRemoveDirectoryAtArgs, -) -> latch::DescriptorRemoveDirectoryAtArgs { - latch::DescriptorRemoveDirectoryAtArgs { path: args.path } -} - -fn descriptor_rename_at_args_map(args: DescriptorRenameAtArgs) -> latch::DescriptorRenameAtArgs { - latch::DescriptorRenameAtArgs { - new_descriptor: args.new_descriptor, - new_path: args.new_path, - old_path: args.old_path, - } -} - -fn descriptor_symlink_at_args_map(args: DescriptorSymlinkAtArgs) -> latch::DescriptorSymlinkAtArgs { - latch::DescriptorSymlinkAtArgs { - new_path: args.new_path, - old_path: args.old_path, - } -} - -fn descriptor_unlink_file_at_args_map( - args: DescriptorUnlinkFileAtArgs, -) -> latch::DescriptorUnlinkFileAtArgs { - latch::DescriptorUnlinkFileAtArgs { path: args.path } -} - -fn descriptor_metadata_hash_at_args_map( - args: DescriptorMetadataHashAtArgs, -) -> latch::DescriptorMetadataHashAtArgs { - latch::DescriptorMetadataHashAtArgs { - path: args.path, - path_flags: args.path_flags, - } -} - -fn directory_entry_stream_operation_map( - directory_entry_stream_operation: DirectoryEntryStreamOperation, -) -> latch::DirectoryEntryStreamOperation { - match directory_entry_stream_operation { - DirectoryEntryStreamOperation::ReadDirectoryEntry(directory_entry) => { - latch::DirectoryEntryStreamOperation::ReadDirectoryEntry(directory_entry) - } - } -} - pub mod bindings { wit_bindgen::generate!({ path: "../../wit", world: "filesystem-latch-n", pub_export_macro: true, + merge_structurally_equal_types: true, generate_all }); } diff --git a/wit/deps/wasi-clocks-0.3.0/package.wit b/wit/deps/wasi-clocks-0.3.0/package.wit new file mode 100644 index 0000000..2594e02 --- /dev/null +++ b/wit/deps/wasi-clocks-0.3.0/package.wit @@ -0,0 +1,19 @@ +package wasi:clocks@0.3.0; + +interface types { + type duration = u64; +} + +interface system-clock { + use types.{duration}; + + record instant { + seconds: s64, + nanoseconds: u32, + } + + now: func() -> instant; + + get-resolution: func() -> duration; +} + diff --git a/wit/deps/wasi-config-0.2.0-rc.1/package.wit b/wit/deps/wasi-config-0.2.0-rc.1/package.wit new file mode 100644 index 0000000..d8950ee --- /dev/null +++ b/wit/deps/wasi-config-0.2.0-rc.1/package.wit @@ -0,0 +1,33 @@ +package wasi:config@0.2.0-rc.1; + +interface store { + /// An error type that encapsulates the different errors that can occur fetching configuration values. + variant error { + /// This indicates an error from an "upstream" config source. + /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), + /// the error message is a string. + upstream(string), + /// This indicates an error from an I/O operation. + /// As this could be almost _anything_ (such as a file read, network connection, etc), + /// the error message is a string. + /// Depending on how this ends up being consumed, + /// we may consider moving this to use the `wasi:io/error` type instead. + /// For simplicity right now in supporting multiple implementations, it is being left as a string. + io(string), + } + + /// Gets a configuration value of type `string` associated with the `key`. + /// + /// The value is returned as an `option`. If the key is not found, + /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. + get: func(key: string) -> result, error>; + + /// Gets a list of configuration key-value pairs of type `string`. + /// + /// If an error occurs, an `Err(error)` is returned. + get-all: func() -> result>, error>; +} + +world imports { + import store; +} diff --git a/wit/deps/wasi-filesystem-0.3.0/package.wit b/wit/deps/wasi-filesystem-0.3.0/package.wit new file mode 100644 index 0000000..e4a778f --- /dev/null +++ b/wit/deps/wasi-filesystem-0.3.0/package.wit @@ -0,0 +1,575 @@ +package wasi:filesystem@0.3.0; + +/// 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. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` 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`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// Though this package presents a portable interface modelled on POSIX, it +/// prioritizes compatibility over portability: allowing users to access their +/// files on their machine is more important than exposing a single semantics +/// across all platforms. Notably, depending on the underlying operating system +/// and file system: +/// * Paths may be case-folded or not. +/// * Deleting (unlinking) a file may fail if there are other file descriptors +/// open. +/// * Durability and atomicity of changes to underlying files when there are +/// concurrent writers. +/// +/// Users that need well-defined, portable semantics should use a key-value +/// store or a database instead. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +@since(version = 0.3.0) +interface types { + @since(version = 0.3.0) + use wasi:clocks/system-clock@0.3.0.{instant}; + + /// File size or length of a region within a file. + @since(version = 0.3.0) + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + @since(version = 0.3.0) + variant descriptor-type { + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + /// The type of the descriptor or file is different from any of the + /// other types specified. + other(option), + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + @since(version = 0.3.0) + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrity + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// Flags determining the method of how paths are resolved. + @since(version = 0.3.0) + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + @since(version = 0.3.0) + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + @since(version = 0.3.0) + type link-count = u64; + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + @since(version = 0.3.0) + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// When setting a timestamp, this gives the value to set it to. + @since(version = 0.3.0) + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(instant), + } + + /// A directory entry. + @since(version = 0.3.0) + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + @since(version = 0.3.0) + variant error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + /// A catch-all for errors not captured by the existing variants. + /// Implementations can use this to extend the error type without + /// breaking existing code. + other(option), + } + + /// File or memory access pattern advisory information. + @since(version = 0.3.0) + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + @since(version = 0.3.0) + 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, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + @since(version = 0.3.0) + resource descriptor { + /// Return a stream for reading from a file. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// This function returns a `stream` which provides the data received from the + /// file, and a `future` providing additional error information in case an + /// error is encountered. + /// + /// If no error is encountered, `stream.read` on the `stream` will return + /// `read-status::closed` with no `error-context` and the future resolves to + /// the value `ok`. If an error is encountered, `stream.read` on the + /// `stream` returns `read-status::closed` with an `error-context` and the future + /// resolves to `err` with an `error-code`. + /// + /// Note: This is similar to `pread` in POSIX. + @since(version = 0.3.0) + read-via-stream: func(offset: filesize) -> tuple, future>>; + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// This function returns once either full contents of the stream are + /// written or an error is encountered. + /// + /// Note: This is similar to `pwrite` in POSIX. + @since(version = 0.3.0) + write-via-stream: func(data: stream, offset: filesize) -> future>; + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// This function returns once either full contents of the stream are + /// written or an error is encountered. + /// + /// Note: This is similar to `write` with `O_APPEND` in POSIX. + @since(version = 0.3.0) + append-via-stream: func(data: stream) -> future>; + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + @since(version = 0.3.0) + advise: async func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + @since(version = 0.3.0) + sync-data: async func() -> result<_, error-code>; + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.3.0) + get-flags: async func() -> result; + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.3.0) + get-type: async func() -> result; + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + @since(version = 0.3.0) + set-size: async func(size: filesize) -> result<_, error-code>; + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + @since(version = 0.3.0) + set-times: async func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + /// + /// This function returns a future, which will resolve to an error code if + /// reading full contents of the directory fails. + @since(version = 0.3.0) + read-directory: func() -> tuple, future>>; + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + @since(version = 0.3.0) + sync: async func() -> result<_, error-code>; + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + @since(version = 0.3.0) + create-directory-at: async func(path: string) -> result<_, error-code>; + /// Return the attributes of an open file or directory. + /// + /// 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. + @since(version = 0.3.0) + stat: async func() -> result; + /// Return the attributes of a file or directory. + /// + /// 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. + @since(version = 0.3.0) + stat-at: async func(path-flags: path-flags, path: string) -> result; + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + @since(version = 0.3.0) + set-times-at: async func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + /// Create a hard link. + /// + /// Fails with `error-code::no-entry` if the old path does not exist, + /// with `error-code::exist` if the new path already exists, and + /// `error-code::not-permitted` if the old path is not a file. + /// + /// Note: This is similar to `linkat` in POSIX. + @since(version = 0.3.0) + link-at: async func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + /// Open a file or directory. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + @since(version = 0.3.0) + open-at: async func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + @since(version = 0.3.0) + readlink-at: async func(path: string) -> result; + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + @since(version = 0.3.0) + remove-directory-at: async func(path: string) -> result<_, error-code>; + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + @since(version = 0.3.0) + rename-at: async func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + @since(version = 0.3.0) + symlink-at: async func(old-path: string, new-path: string) -> result<_, error-code>; + /// Unlink a filesystem object that is not a directory. + /// + /// This is similar to `unlinkat(fd, path, 0)` in POSIX. + /// + /// Error returns are as specified by POSIX. + /// + /// If the filesystem object is a directory, `error-code::access` or + /// `error-code::is-directory` may be returned instead of the + /// POSIX-specified `error-code::not-permitted`. + @since(version = 0.3.0) + unlink-file-at: async func(path: string) -> result<_, error-code>; + /// 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. + @since(version = 0.3.0) + is-same-object: async func(other: borrow) -> 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 encouraged 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. + @since(version = 0.3.0) + metadata-hash: async func() -> 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`. + @since(version = 0.3.0) + metadata-hash-at: async func(path-flags: path-flags, path: string) -> result; + } +} + +@since(version = 0.3.0) +interface preopens { + @since(version = 0.3.0) + use types.{descriptor}; + + /// Return the set of preopened directories, and their paths. + @since(version = 0.3.0) + get-directories: func() -> list>; +} + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import wasi:clocks/types@0.3.0; + @since(version = 0.3.0) + import wasi:clocks/system-clock@0.3.0; + @since(version = 0.3.0) + import types; + @since(version = 0.3.0) + import preopens; +} diff --git a/wit/deps/wasi-logging-0.1.0-draft/package.wit b/wit/deps/wasi-logging-0.1.0-draft/package.wit new file mode 100644 index 0000000..164cb5b --- /dev/null +++ b/wit/deps/wasi-logging-0.1.0-draft/package.wit @@ -0,0 +1,36 @@ +package wasi:logging@0.1.0-draft; + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface logging { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + /// Describes messages indicating hazardous situations. + warn, + /// Describes messages indicating serious errors. + error, + /// Describes messages indicating fatal errors. + critical, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string); +} + +world imports { + import logging; +} diff --git a/wit/latch.wit b/wit/latch.wit index 07b5099..ac56f9c 100644 --- a/wit/latch.wit +++ b/wit/latch.wit @@ -1,6 +1,6 @@ interface latch { - use wasi:filesystem/types@0.2.6.{advice, descriptor, descriptor-flags, directory-entry, directory-entry-stream, error-code, filesize, open-flags, new-timestamp, path-flags}; + use wasi:filesystem/types@0.3.0.{advice, descriptor, descriptor-flags, error-code, filesize, open-flags, new-timestamp, path-flags}; record descriptor-get-directory-args { path: string, @@ -29,16 +29,6 @@ interface latch { data-modification-timestamp: new-timestamp, } - record descriptor-read-args { - length: filesize, - offset: filesize, - } - - record descriptor-write-args { - buffer-length: u64, // derived from buffer: list - offset: filesize, - } - record descriptor-create-directory-at-args { path: string, } @@ -112,8 +102,6 @@ interface latch { get-type, set-size(descriptor-set-size-args), set-times(descriptor-set-times-args), - read(descriptor-read-args), - write(descriptor-write-args), read-directory, sync, create-directory-at(descriptor-create-directory-at-args), @@ -131,14 +119,9 @@ interface latch { metadata-hash-at(descriptor-metadata-hash-at-args), } - variant directory-entry-stream-operation { - read-directory-entry(directory-entry), - } - variant operation { preopens(preopens-operation), descriptor(tuple, string, descriptor-operation>), - directory-entry-stream(tuple, string, directory-entry-stream-operation>), } variant decision { @@ -149,6 +132,13 @@ interface latch { authorize: func(operation: operation) -> option; } +interface latch0 { + use latch.{operation, decision}; + + authorize: func(operation: operation) -> option; +} + + interface latch1 { use latch.{operation, decision}; diff --git a/wit/worlds.wit b/wit/worlds.wit index f6e3142..5d6ea15 100644 --- a/wit/worlds.wit +++ b/wit/worlds.wit @@ -17,10 +17,11 @@ world filesystem-latch { world filesystem-latch-n { export latch; + import latch; import latch1; import latch2; import latch3; import latch4; - import wasi:filesystem/preopens@0.2.6; - import wasi:filesystem/types@0.2.6; + import wasi:filesystem/preopens@0.3.0; + import wasi:filesystem/types@0.3.0; }