From 83c5beefd3c2a924f5dc0feb5746ef4ecac992e1 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 15 May 2026 00:22:52 -0400 Subject: [PATCH] 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; +}