From 83c5beefd3c2a924f5dc0feb5746ef4ecac992e1 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 15 May 2026 00:22:52 -0400 Subject: [PATCH 1/9] 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 1c127f2..06048e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,13 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "gate" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "hashbrown" version = "0.17.0" @@ -72,6 +79,48 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[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 b9b80f0..255e085 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 bad82d8..1b231f5 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.2.6; @@ -8,3 +9,28 @@ world filesystem { import wasi:filesystem/types@0.2.6; export wasi:filesystem/types@0.2.6; } + +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 4d833522848c68cdaa8ca528587f6dd69ec59ac3 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 18 May 2026 13:51:16 -0400 Subject: [PATCH 2/9] 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/worlds.wit | 17 +- 19 files changed, 168 insertions(+), 593 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%) diff --git a/Cargo.lock b/Cargo.lock index 06048e8..88a0af2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,37 +80,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[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 b724f13..7a53530 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,9 @@ resolver = "2" members = [ "components/*", + "crates/*", ] [workspace.dependencies] +latch-n = { path = "./crates/latch-n" } wit-bindgen = "0.57.1" 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/worlds.wit b/wit/worlds.wit index 1b231f5..c04b7fc 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 6ac8b02f227d966d6af606b1b82cfa47a5ce0022 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 18 May 2026 13:57:56 -0400 Subject: [PATCH 3/9] rebuild components on changes in crates dir Signed-off-by: Scott Andrews --- Cargo.lock | 4 ---- Makefile | 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 - 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88a0af2..ae38b67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,6 @@ name = "latch-n2" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] @@ -113,7 +112,6 @@ name = "latch-n3" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] @@ -121,7 +119,6 @@ name = "latch-n4" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] @@ -129,7 +126,6 @@ name = "latch-n5" version = "0.1.0" dependencies = [ "latch-n", - "wit-bindgen", ] [[package]] diff --git a/Makefile b/Makefile index c9eb511..c15b9f3 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,12 @@ components: $(foreach component,$(COMPONENTS),lib/$(component).wasm $(foreach co define BUILD_COMPONENT -lib/$1.wasm: Cargo.toml Cargo.lock wit/deps $(shell find components/$1 -type f) +lib/$1.wasm: Cargo.toml Cargo.lock wit/deps $(shell find components/$1 -type f) $(shell find crates -type f) cargo component build -p $1 --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/$(subst -,_,$1).wasm lib/$1.wasm cp components/$1/README.md lib/$1.wasm.md -lib/$1.debug.wasm: Cargo.toml Cargo.lock wit/deps $(shell find components/$1 -type f) +lib/$1.debug.wasm: Cargo.toml Cargo.lock wit/deps $(shell find components/$1 -type f) $(shell find crates -type f) cargo component build -p $1 --target wasm32-wasip2 cp target/wasm32-wasip2/debug/$(subst -,_,$1).wasm lib/$1.debug.wasm cp components/$1/README.md lib/$1.debug.wasm.md 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 d07f7d098d46485c64abaaec5ab04e72d8584850 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 18 May 2026 15:51:09 -0400 Subject: [PATCH 4/9] 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 5cae82559548b51a9379763a0217c7f623f9706d Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Sun, 31 May 2026 23:54:16 -0400 Subject: [PATCH 5/9] Add components that can be used to test th gate Signed-off-by: Scott Andrews --- Cargo.lock | 155 +++++++++++++++++++++++++++++-- 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, 441 insertions(+), 35 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 ae38b67..40f726f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,56 @@ # It is not intended for manual editing. version = 4 +[[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" @@ -21,12 +71,65 @@ 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 = "equivalent" 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 = "foldhash" version = "0.2.0" @@ -37,6 +140,7 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" name = "gate" version = "0.1.0" dependencies = [ + "heck", "wit-bindgen", ] @@ -73,6 +177,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" @@ -164,6 +274,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "prettyplease" version = "0.2.29" @@ -176,18 +292,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", ] @@ -252,11 +368,17 @@ dependencies = [ "serde", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" -version = "2.0.98" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -282,6 +404,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-encoder" version = "0.247.0" @@ -316,6 +444,21 @@ dependencies = [ "semver", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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 = "wit-bindgen" version = "0.57.1" diff --git a/Cargo.toml b/Cargo.toml index 7a53530..0a47f39 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 c15b9f3..6c246c1 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 @@ -33,6 +36,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 5d7874af82891f1ebd55ca02faedf6d5b4acd234 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 09:52:45 -0400 Subject: [PATCH 6/9] 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 | 16 +- 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, 226 insertions(+), 221 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 40f726f..258a5fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,14 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[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", @@ -238,6 +231,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 6c246c1..345e3f1 100644 --- a/Makefile +++ b/Makefile @@ -53,22 +53,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 @@ -79,12 +79,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 @@ -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-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 255e085..ed8ec78 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 e9e495174f726014160f7dad6dc129c021cea72f Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 09:58:46 -0400 Subject: [PATCH 7/9] Build test components in ci Signed-off-by: Scott Andrews --- .github/workflows/ci.yaml | 4 ++++ README.md | 1 + 2 files changed, 5 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a53d0e8..dfef1df 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,6 +22,8 @@ jobs: run: cargo binstall --force cargo-component - name: Install wasm-tools run: cargo binstall --force wasm-tools + - name: Install wac-cli + run: cargo binstall --force wac-cli - name: Fetch wit run: make wit - name: Check for drift in generated wit @@ -45,6 +47,8 @@ jobs: wasm-tools component wit "${component}" echo "::endgroup::" done + - name: Build test components + run: make tests publish: if: startsWith(github.ref, 'refs/tags/') diff --git a/README.md b/README.md index ed8ec78..89ac152 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 - [`cargo component`](https://github.com/bytecodealliance/cargo-component) +- [`wac`](https://github.com/bytecodealliance/wac) - [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools) ```sh From c28c6db07dc06497f4b30890b1443b1b5e8f4be3 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 12:48:55 -0400 Subject: [PATCH 8/9] 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 345e3f1..9920e7d 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 @@ -79,13 +79,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 10db4ee3e174de4a2a54e533c6a076f887f8d36e Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Mon, 1 Jun 2026 16:31:24 -0400 Subject: [PATCH 9/9] 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 258a5fc..22d9b46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,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" @@ -196,6 +202,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" @@ -280,6 +294,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[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 89ac152..f9dac3c 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 c04b7fc..1eec608 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; }