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; +}