diff --git a/.vscode/settings.json b/.vscode/settings.json index c0024f29c73..8c5ad19f07b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "editor.formatOnSave": true, "editor.formatOnPaste": false, "editor.formatOnType": false - } + }, + "rust-analyzer.showUnlinkedFileNotification": false } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3e16c3ad309..fd01f093b65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2613,6 +2613,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lz4_flex" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" +dependencies = [ + "twox-hash", +] + [[package]] name = "lzma-rs" version = "0.2.0" @@ -4234,7 +4243,21 @@ checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" dependencies = [ "lazy_static", "parking_lot 0.11.2", - "serial_test_derive", + "serial_test_derive 0.5.1", +] + +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive 2.0.0", ] [[package]] @@ -4248,6 +4271,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.42", +] + [[package]] name = "sha1" version = "0.10.6" @@ -5075,6 +5109,16 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + [[package]] name = "typenum" version = "1.17.0" @@ -5268,6 +5312,7 @@ dependencies = [ "pin-project-lite", "pretty_assertions", "replace_with", + "serde", "shared-buffer", "slab", "tempfile", @@ -5347,6 +5392,7 @@ dependencies = [ "async-trait", "base64", "bincode", + "bytecheck", "bytes", "derivative", "futures-util", @@ -5355,7 +5401,9 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "rkyv", "serde", + "serial_test 2.0.0", "socket2 0.4.10", "thiserror", "tokio", @@ -6160,6 +6208,32 @@ dependencies = [ name = "wasmer-integration-tests-ios" version = "4.2.5" +[[package]] +name = "wasmer-journal" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "bincode", + "bytecheck", + "bytes", + "derivative", + "lz4_flex", + "num_enum", + "rkyv", + "serde", + "serde_json", + "shared-buffer", + "tempfile", + "thiserror", + "tracing", + "tracing-test", + "virtual-net 0.6.2", + "wasmer", + "wasmer-wasix-types", +] + [[package]] name = "wasmer-middlewares" version = "4.2.5" @@ -6350,7 +6424,9 @@ version = "0.18.0" dependencies = [ "anyhow", "async-trait", + "base64", "bincode", + "bytecheck", "bytes", "cfg-if", "chrono", @@ -6367,12 +6443,16 @@ dependencies = [ "lazy_static", "libc", "linked_hash_set", + "lz4_flex", + "num_enum", "once_cell", "petgraph", "pin-project", "pretty_assertions", "rand", + "rayon", "reqwest", + "rkyv", "rusty_pool", "semver 1.0.20", "serde", @@ -6381,6 +6461,7 @@ dependencies = [ "serde_json", "serde_yaml 0.8.26", "sha2", + "shared-buffer", "tempfile", "term_size", "termios", @@ -6390,6 +6471,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "tracing-test", "tracing-wasm", "typetag", "url", @@ -6404,6 +6486,7 @@ dependencies = [ "wasm-bindgen-test", "wasmer", "wasmer-emscripten", + "wasmer-journal", "wasmer-types", "wasmer-wasix-types", "wcgi", @@ -6506,7 +6589,7 @@ dependencies = [ "glob", "lazy_static", "rustc_version 0.4.0", - "serial_test", + "serial_test 0.5.1", "tempfile", "test-generator", "test-log", diff --git a/Cargo.toml b/Cargo.toml index b067e8260a1..3019bed58a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "lib/wasix", "lib/wasix-http-client", "lib/wasm-interface", + "lib/journal", "tests/integration/cli", "tests/integration/ios", "tests/lib/compiler-test-derive", diff --git a/docs/journal.md b/docs/journal.md new file mode 100644 index 00000000000..70c1d89788f --- /dev/null +++ b/docs/journal.md @@ -0,0 +1,148 @@ +# WASM Journal Functionality + +Wasmer now supports journals for the state of a WASM process. This gives the ability +to persist changes made to the temporary file system and to save and store snapshots +of the running process. + +The journal file is a linear history of events that occurred when the process was +running that if replayed will bring the process made to a discrete and deterministic +state. + +Journal files can be concatenated, compacted and filtered to change the discrete state. + +These journals are maintained in a consistent and durable way thus ensuring that +failures of the system while the process is running does not corrupt the journal. + +# Snapshot Triggers + +The journal will record state changes to the sandbox built around the WASM process as +it runs however it may be important to certain use-cases to take explicit snapshot +restoration points in the journal at key points that make sense. + +When a snapshot is triggered all the running threads of the process are paused and +the state of the WASM memory and thread stacks are recorded into the journal so that +they can be restored. + +In order to use the snapshot functionality the WASM process must be compiled with +the `asyncify` modifications, this can be done using the `wasm-opt` tool. + +Note: If a process does not have the `asyncify` modifications you can still use +the journal functionality for recording the file system and WASM memory state +however the stacks of the threads will be omitted meaning a restoration will +restart the main thread. + +Various triggers are possible that will cause a snapshot to be taken at a specific +point in time, these are as follows: + +## On Idle + +Triggered when all the threads in the process go into an idle state. This trigger +is useful to take snapshots at convenient moments without causing unnecessary overhead. + +For processes that have TTY/STDIN input this is particularly useful. + +## On FirstListen + +Triggered when a listen syscall is invoked on a socket. This can be an important +milestone to take a snapshot when one wants to speed up the boot time of a WASM process +up to the moment where it is ready to accept requests. + +## On FirstStdin + +Triggered when the process reads stdin for the first time. This can be useful to +speed up the boot time of a WASM process. + +## On FirstEnviron + +Triggered when the process reads an environment variable for the first time. This can +be useful to speed up the boot time of a CGI WASM process which reads the environment +variables to parse the request that it must execute. + +## On Timer Interval + +Triggered periodically based on a timer (default 10 seconds) which can be specified +using the `journal-interval` option. This can be useful for asynchronous replication +of a WASM process from one machine to another with a particular lag latency. + +## On Sigint (Ctrl+C) + +Issued if the user sends an interrupt signal (Ctrl + C). + +## On Sigalrm + +Alarm clock signal (used for timers) +(see `man alarm`) + +## On Sigtstp + +The SIGTSTP signal is sent to a process by its controlling terminal to request it to stop +temporarily. It is commonly initiated by the user pressing Ctrl-Z. + +# On Sigstop + +The SIGSTOP signal instructs the operating system to stop a process for later resumption + +# On Non Deterministic Call + +When a non-deterministic call is made from WASM process to the outside world (i.e. it reaches +out of the sandbox) + +# Limitations + +- The WASM process that wish to record the state of the threads must have had the `asyncify` + post processing step applied to the binary (see `wasm-opt`). +- Taking a snapshot can consume large amounts of memory while its processing. +- Snapshots are not instant and have overhead when generating. +- The layout of the memory must be known by the runtime in order to take snapshots. + +# Design + +On startup if the restore journal file is specified then the runtime will restore the +state of the WASM process by reading and processing the log entries in the snapshot +journal. This restoration will bring the memory and the thread stacks back to a previous +point in time and then resume all the threads. + +When a trigger occurs a new journal will be taken of the WASM process which will +take the following steps: + +1. Pause all threads +2. Capture the stack of each thread +3. Write the thread state to the journal +4. Write the memory (excluding stacks) to the journal +5. Resume execution. + +The implementation is currently able to save and restore the following: + +- WASM Memory +- Stack memory +- Call stack +- Open sockets +- Open files +- Terminal text + +# Journal Capturer Implementations + +## Log File Journal + +Writes the log events to a linear log file on the local file system +as they are received by the trait. Log files can be concatenated +together to make larger log files. + +## Unsupported Journal + +The default implementation does not support snapshots and will error +out if an attempt is made to send it events. Using the unsupported +capturer as a restoration point will restore nothing but will not +error out. + +## Compacting Journal + +Deduplicates memory and stacks to reduce the number of volume of +log events sent to its inner capturer. Compacting the events occurs +in line as the events are generated + +## Filtered Journal + +Filters out a specific set of log events and drops the rest, this +capturer can be useful for restoring to a previous call point but +retaining the memory changes (e.g. WCGI runner). diff --git a/examples/wasi_manual_setup.rs b/examples/wasi_manual_setup.rs index fceb44b8db1..d08de6d01b9 100644 --- a/examples/wasi_manual_setup.rs +++ b/examples/wasi_manual_setup.rs @@ -65,7 +65,7 @@ fn main() -> Result<(), Box> { let start = instance.exports.get_function("_start")?; start.call(&mut store, &[])?; - wasi_env.cleanup(&mut store, None); + wasi_env.on_exit(&mut store, None); Ok(()) } diff --git a/lib/api/src/errors.rs b/lib/api/src/errors.rs index 8dff0864a24..8c3509598d3 100644 --- a/lib/api/src/errors.rs +++ b/lib/api/src/errors.rs @@ -17,7 +17,7 @@ use wasmer_types::ImportError; /// This is based on the [link error][link-error] API. /// /// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "std", derive(Error))] #[cfg_attr(feature = "std", error("Link error: {0}"))] pub enum LinkError { @@ -41,7 +41,7 @@ pub enum LinkError { /// Trap that occurs when calling the WebAssembly module /// start function, and an error when initializing the user's /// host environments. -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "std", derive(Error))] pub enum InstantiationError { /// A linking ocurred during instantiation. diff --git a/lib/api/src/exports.rs b/lib/api/src/exports.rs index e6eecf7e301..d2f821145f2 100644 --- a/lib/api/src/exports.rs +++ b/lib/api/src/exports.rs @@ -42,7 +42,7 @@ use thiserror::Error; /// // This results with an error: `ExportError::Missing`. /// let export = instance.exports.get_function("unknown").unwrap(); /// ``` -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum ExportError { /// An error than occurs when the exported type and the expected type /// are incompatible. diff --git a/lib/api/src/externals/memory.rs b/lib/api/src/externals/memory.rs index 8f2e18fa21d..1c49d866b1d 100644 --- a/lib/api/src/externals/memory.rs +++ b/lib/api/src/externals/memory.rs @@ -121,6 +121,22 @@ impl Memory { self.0.grow(store, delta) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + pub fn grow_at_least( + &self, + store: &mut impl AsStoreMut, + min_size: u64, + ) -> Result<(), MemoryError> { + self.0.grow_at_least(store, min_size) + } + + /// Resets the memory back to zero length + pub fn reset(&self, store: &mut impl AsStoreMut) -> Result<(), MemoryError> { + self.0.reset(store)?; + Ok(()) + } + /// Attempts to duplicate this memory (if its clonable) in a new store /// (copied memory) pub fn copy_to_store( diff --git a/lib/api/src/externals/memory_view.rs b/lib/api/src/externals/memory_view.rs index 7a7dc9a3192..1fbda5c9afd 100644 --- a/lib/api/src/externals/memory_view.rs +++ b/lib/api/src/externals/memory_view.rs @@ -2,6 +2,7 @@ use super::memory::{Memory, MemoryBuffer}; use crate::store::AsStoreRef; use crate::MemoryAccessError; use std::mem::MaybeUninit; +use std::ops::Range; use wasmer_types::Pages; #[cfg(feature = "js")] @@ -145,11 +146,17 @@ impl<'a> MemoryView<'a> { /// Copies the memory and returns it as a vector of bytes pub fn copy_to_vec(&self) -> Result, MemoryAccessError> { + self.copy_range_to_vec(0..self.data_size()) + } + + /// Copies a range of the memory and returns it as a vector of bytes + pub fn copy_range_to_vec(&self, range: Range) -> Result, MemoryAccessError> { let mut new_memory = Vec::new(); - let mut offset = 0; + let mut offset = range.start; + let end = range.end.min(self.data_size()); let mut chunk = [0u8; 40960]; - while offset < self.data_size() { - let remaining = self.data_size() - offset; + while offset < end { + let remaining = end - offset; let sublen = remaining.min(chunk.len() as u64) as usize; self.read(offset, &mut chunk[..sublen])?; new_memory.extend_from_slice(&chunk[..sublen]); diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index a6aed3deb54..0fb067771e5 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -144,6 +144,25 @@ impl Memory { Ok(Pages(new_pages)) } + pub fn grow_at_least( + &self, + store: &mut impl AsStoreMut, + min_size: u64, + ) -> Result<(), MemoryError> { + let cur_size = self.view(store).data_size(); + if min_size > cur_size { + let delta = min_size - cur_size; + let pages = ((delta - 1) / wasmer_types::WASM_PAGE_SIZE as u64) + 1; + + self.grow(store, Pages(pages as u32))?; + } + Ok(()) + } + + pub fn reset(&self, _store: &mut impl AsStoreMut) -> Result<(), MemoryError> { + Ok(()) + } + pub(crate) fn from_vm_extern(_store: &mut impl AsStoreMut, internal: VMMemory) -> Self { Self { handle: internal } } diff --git a/lib/api/src/js/externals/memory_view.rs b/lib/api/src/js/externals/memory_view.rs index fccd385ba88..938371b6acc 100644 --- a/lib/api/src/js/externals/memory_view.rs +++ b/lib/api/src/js/externals/memory_view.rs @@ -1,9 +1,9 @@ use crate::mem_access::MemoryAccessError; use crate::store::AsStoreRef; -use std::convert::TryInto; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::slice; +use std::{convert::TryInto, ops::Range}; #[cfg(feature = "tracing")] use tracing::warn; use wasm_bindgen::JsCast; @@ -256,11 +256,18 @@ impl<'a> MemoryView<'a> { /// Copies the memory and returns it as a vector of bytes #[allow(unused)] pub fn copy_to_vec(&self) -> Result, MemoryAccessError> { + self.copy_range_to_vec(0..self.data_size()) + } + + /// Copies a range of the memory and returns it as a vector of bytes + #[allow(unused)] + pub fn copy_range_to_vec(&self, range: Range) -> Result, MemoryAccessError> { let mut new_memory = Vec::new(); - let mut offset = 0; + let mut offset = range.start; + let end = range.end.min(self.data_size()); let mut chunk = [0u8; 40960]; - while offset < self.data_size() { - let remaining = self.data_size() - offset; + while offset < end { + let remaining = end - offset; let sublen = remaining.min(chunk.len() as u64) as usize; self.read(offset, &mut chunk[..sublen])?; new_memory.extend_from_slice(&chunk[..sublen]); diff --git a/lib/api/src/jsc/externals/memory.rs b/lib/api/src/jsc/externals/memory.rs index 38fcc952e3d..e628e4d49ee 100644 --- a/lib/api/src/jsc/externals/memory.rs +++ b/lib/api/src/jsc/externals/memory.rs @@ -140,6 +140,25 @@ impl Memory { // Ok(Pages(new_pages)) } + pub fn grow_at_least( + &self, + store: &mut impl AsStoreMut, + min_size: u64, + ) -> Result<(), MemoryError> { + let cur_size = self.view(store).data_size(); + if min_size > cur_size { + let delta = min_size - cur_size; + let pages = ((delta - 1) / wasmer_types::WASM_PAGE_SIZE as u64) + 1; + + self.grow(store, Pages(pages as u32))?; + } + Ok(()) + } + + pub fn reset(&self, _store: &mut impl AsStoreMut) -> Result<(), MemoryError> { + Ok(()) + } + pub fn copy_to_store( &self, store: &impl AsStoreRef, diff --git a/lib/api/src/jsc/externals/memory_view.rs b/lib/api/src/jsc/externals/memory_view.rs index eb3e3515477..bb891293f24 100644 --- a/lib/api/src/jsc/externals/memory_view.rs +++ b/lib/api/src/jsc/externals/memory_view.rs @@ -2,10 +2,10 @@ use crate::store::AsStoreRef; use crate::MemoryAccessError; use rusty_jsc::JSObject; use std::convert::TryFrom; -use std::convert::TryInto; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::slice; +use std::{convert::TryInto, ops::Range}; use wasmer_types::{Bytes, Pages}; use super::memory::{Memory, MemoryBuffer}; @@ -178,11 +178,18 @@ impl<'a> MemoryView<'a> { #[allow(unused)] /// Copies the memory and returns it as a vector of bytes pub fn copy_to_vec(&self) -> Result, MemoryAccessError> { + self.copy_range_to_vec(0..self.data_size()) + } + + #[allow(unused)] + /// Copies a range of the memory and returns it as a vector of bytes + pub fn copy_range_to_vec(&self, range: Range) -> Result, MemoryAccessError> { let mut new_memory = Vec::new(); - let mut offset = 0; + let mut offset = range.start; + let end = range.end.min(self.data_size()); let mut chunk = [0u8; 40960]; - while offset < self.data_size() { - let remaining = self.data_size() - offset; + while offset < end { + let remaining = end - offset; let sublen = remaining.min(chunk.len() as u64) as usize; self.read(offset, &mut chunk[..sublen])?; new_memory.extend_from_slice(&chunk[..sublen]); diff --git a/lib/api/src/sys/externals/function.rs b/lib/api/src/sys/externals/function.rs index 1ce6fe64007..41c292f90cb 100644 --- a/lib/api/src/sys/externals/function.rs +++ b/lib/api/src/sys/externals/function.rs @@ -295,7 +295,7 @@ impl Function { break; } Ok(wasmer_types::OnCalledAction::Trap(trap)) => { - return Err(RuntimeError::user(trap)) + return Err(RuntimeError::user(trap)); } Err(trap) => return Err(RuntimeError::user(trap)), } diff --git a/lib/api/src/sys/externals/memory.rs b/lib/api/src/sys/externals/memory.rs index 90c02ac3051..148c7fe4e67 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -51,6 +51,21 @@ impl Memory { self.handle.get_mut(store.objects_mut()).grow(delta.into()) } + pub fn grow_at_least( + &self, + store: &mut impl AsStoreMut, + min_size: u64, + ) -> Result<(), MemoryError> { + self.handle + .get_mut(store.objects_mut()) + .grow_at_least(min_size) + } + + pub fn reset(&self, store: &mut impl AsStoreMut) -> Result<(), MemoryError> { + self.handle.get_mut(store.objects_mut()).reset()?; + Ok(()) + } + pub(crate) fn from_vm_extern(store: &impl AsStoreRef, vm_extern: VMExternMemory) -> Self { Self { handle: unsafe { diff --git a/lib/api/src/sys/externals/memory_view.rs b/lib/api/src/sys/externals/memory_view.rs index a506abd9dc2..b1dc5e1f8dc 100644 --- a/lib/api/src/sys/externals/memory_view.rs +++ b/lib/api/src/sys/externals/memory_view.rs @@ -1,9 +1,9 @@ use crate::store::AsStoreRef; use crate::MemoryAccessError; -use std::convert::TryInto; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::slice; +use std::{convert::TryInto, ops::Range}; use wasmer_types::Pages; use wasmer_vm::LinearMemory; @@ -163,11 +163,18 @@ impl<'a> MemoryView<'a> { #[allow(unused)] /// Copies the memory and returns it as a vector of bytes pub fn copy_to_vec(&self) -> Result, MemoryAccessError> { + self.copy_range_to_vec(0..self.data_size()) + } + + #[allow(unused)] + /// Copies a range of the memory and returns it as a vector of bytes + pub fn copy_range_to_vec(&self, range: Range) -> Result, MemoryAccessError> { let mut new_memory = Vec::new(); - let mut offset = 0; + let mut offset = range.start; + let end = range.end.min(self.data_size()); let mut chunk = [0u8; 40960]; - while offset < self.data_size() { - let remaining = self.data_size() - offset; + while offset < end { + let remaining = end - offset; let sublen = remaining.min(chunk.len() as u64) as usize; self.read(offset, &mut chunk[..sublen])?; new_memory.extend_from_slice(&chunk[..sublen]); diff --git a/lib/api/src/sys/tunables.rs b/lib/api/src/sys/tunables.rs index c2853be44ff..df63e3b1d80 100644 --- a/lib/api/src/sys/tunables.rs +++ b/lib/api/src/sys/tunables.rs @@ -106,6 +106,24 @@ mod tests { attempted_delta: delta, }) } + + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + let cur_size = self.size().0 as u64 * WASM_PAGE_SIZE as u64; + if min_size > cur_size { + let delta = min_size - cur_size; + return Err(MemoryError::CouldNotGrow { + current: Pages::from(100u32), + attempted_delta: Pages(delta as u32), + }); + } + Ok(()) + } + + fn reset(&mut self) -> Result<(), MemoryError> { + self.mem.fill(0); + Ok(()) + } + fn vmmemory(&self) -> NonNull { unsafe { NonNull::new( diff --git a/lib/c-api/src/wasm_c_api/wasi/mod.rs b/lib/c-api/src/wasm_c_api/wasi/mod.rs index 508293dcb93..f8e52e9ae5e 100644 --- a/lib/c-api/src/wasm_c_api/wasi/mod.rs +++ b/lib/c-api/src/wasm_c_api/wasi/mod.rs @@ -376,7 +376,7 @@ pub unsafe extern "C" fn wasi_env_new( pub extern "C" fn wasi_env_delete(state: Option>) { if let Some(mut env) = state { env.inner - .cleanup(unsafe { &mut env.store.store_mut() }, None); + .on_exit(unsafe { &mut env.store.store_mut() }, None); } } diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 592a5b5bc96..5decba23f30 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -39,6 +39,7 @@ wasmer-vm = { version = "=4.2.5", path = "../vm", optional = true } wasmer-wasix = { version = "0.18.0", path = "../wasix", features = [ "logging", "webc_runner_rt_wcgi", + "webc_runner_rt_dcgi", "webc_runner_rt_emscripten", "host-fs", ] } @@ -158,7 +159,16 @@ unix_mode = "0.1.3" [features] # Don't add the compiler features in default, please add them on the Makefile # since we might want to autoconfigure them depending on the availability on the host. -default = ["sys", "wat", "wast", "compiler", "wasmer-artifact-create", "static-artifact-create"] +default = [ + "sys", + "wat", + "wast", + "compiler", + "journal", + "wasmer-artifact-create", + "static-artifact-create", +] +journal = ["wasmer-wasix/journal"] backend = [] coredump = ["wasm-coredump-builder"] sys = ["compiler", "wasmer-vm"] diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 6b205db7ebb..ae022a953e3 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -2,6 +2,8 @@ #[cfg(target_os = "linux")] use crate::commands::Binfmt; +#[cfg(feature = "journal")] +use crate::commands::CmdJournal; #[cfg(feature = "compiler")] use crate::commands::Compile; #[cfg(any(feature = "static-artifact-create", feature = "wasmer-artifact-create"))] @@ -130,6 +132,8 @@ impl Args { // Deploy commands. Some(Cmd::Deploy(c)) => c.run(), Some(Cmd::App(apps)) => apps.run(), + #[cfg(feature = "journal")] + Some(Cmd::Journal(journal)) => journal.run(), Some(Cmd::Ssh(ssh)) => ssh.run(), Some(Cmd::Namespace(namespace)) => namespace.run(), None => { @@ -142,6 +146,7 @@ impl Args { } #[derive(Parser, Debug)] +#[allow(clippy::large_enum_variant)] /// The options for the wasmer Command Line Interface enum Cmd { /// Login into a wasmer.io-like registry @@ -265,6 +270,11 @@ enum Cmd { #[clap(alias = "run-unstable")] Run(Run), + /// Manage journals (compacting, inspecting, filtering, ...) + #[cfg(feature = "journal")] + #[clap(subcommand)] + Journal(CmdJournal), + #[clap(subcommand)] Package(crate::commands::Package), diff --git a/lib/cli/src/commands/journal/compact.rs b/lib/cli/src/commands/journal/compact.rs new file mode 100644 index 00000000000..affe29245e7 --- /dev/null +++ b/lib/cli/src/commands/journal/compact.rs @@ -0,0 +1,34 @@ +use std::path::PathBuf; + +use clap::Parser; +use wasmer_edge_cli::cmd::AsyncCliCommand; +use wasmer_wasix::journal::{ + copy_journal, CompactingLogFileJournal, LogFileJournal, PrintingJournal, +}; + +/// Compacts a journal by removing duplicate or redundant +/// events and rewriting the log +#[derive(Debug, Parser)] +pub struct CmdJournalCompact { + /// Path to the journal that will be compacted + #[clap(index = 1)] + journal_path: PathBuf, +} + +impl AsyncCliCommand for CmdJournalCompact { + fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> { + Box::pin(self.run()) + } +} + +impl CmdJournalCompact { + async fn run(self) -> Result<(), anyhow::Error> { + let compactor = CompactingLogFileJournal::new(&self.journal_path)?.with_compact_on_drop(); + drop(compactor); + + let journal = LogFileJournal::new(&self.journal_path)?; + let printer = PrintingJournal::default(); + copy_journal(&journal, &printer)?; + Ok(()) + } +} diff --git a/lib/cli/src/commands/journal/export.rs b/lib/cli/src/commands/journal/export.rs new file mode 100644 index 00000000000..fe23adbe368 --- /dev/null +++ b/lib/cli/src/commands/journal/export.rs @@ -0,0 +1,28 @@ +use std::path::PathBuf; + +use clap::Parser; +use wasmer_edge_cli::cmd::AsyncCliCommand; +use wasmer_wasix::journal::{copy_journal, JournalPrintingMode, LogFileJournal, PrintingJournal}; + +/// Exports all the events in a journal to STDOUT as JSON data +#[derive(Debug, Parser)] +pub struct CmdJournalExport { + /// Path to the journal that will be printed + #[clap(index = 1)] + journal_path: PathBuf, +} + +impl AsyncCliCommand for CmdJournalExport { + fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> { + Box::pin(self.run()) + } +} + +impl CmdJournalExport { + async fn run(self) -> Result<(), anyhow::Error> { + let journal = LogFileJournal::new(self.journal_path)?; + let printer = PrintingJournal::new(JournalPrintingMode::Json); + copy_journal(&journal, &printer)?; + Ok(()) + } +} diff --git a/lib/cli/src/commands/journal/filter.rs b/lib/cli/src/commands/journal/filter.rs new file mode 100644 index 00000000000..815078ec354 --- /dev/null +++ b/lib/cli/src/commands/journal/filter.rs @@ -0,0 +1,117 @@ +use std::{path::PathBuf, str::FromStr}; + +use clap::Parser; +use wasmer_edge_cli::cmd::AsyncCliCommand; +use wasmer_wasix::journal::{ + copy_journal, FilteredJournalBuilder, LogFileJournal, PrintingJournal, +}; + +/// Flags that specify what should be filtered out +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub enum FilterOut { + /// Filters out all the memory events + Memory, + /// Filters out all the thread stacks + Threads, + /// Filters out all the file system operations + FileSystem, + /// Filters out all core syscalls + Core, + /// Filters out the snapshots + Snapshots, + /// Filters out the networking + Networking, +} + +impl FromStr for FilterOut { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_str() { + "mem" | "memory" => Self::Memory, + "thread" | "threads" => Self::Threads, + "fs" | "file" | "filesystem" | "file-system" => Self::FileSystem, + "core" => Self::Core, + "snap" | "snapshot" | "snapshots" => Self::Snapshots, + "net" | "network" | "networking" => Self::Networking, + t => return Err(format!("unknown filter type - {}", t)), + }) + } +} + +/// Rewrites a journal log removing events that match the +/// filter parameters. +#[derive(Debug, Parser)] +pub struct CmdJournalFilter { + /// Path to the journal that will be read + #[clap(index = 1)] + source_path: PathBuf, + /// Path to the journal that will be the output of the filter + #[clap(index = 2)] + target_path: PathBuf, + /// Filters to be applied to the output journal, filter options are + /// - 'mem' | 'memory' -> removes all WASM memory related events + /// - 'thread' | 'threads' -> removes all events related to the state of the threads + /// - 'fs' | 'file' -> removes file system mutation events + /// - 'core' -> removes core operating system operations such as TTY + /// - 'snap' | 'snapshot' -> removes the snapshots from the journal + /// - 'net' | 'network' -> removes network socket and interface events + #[clap(short, long = "filter")] + filters: Vec, +} + +impl AsyncCliCommand for CmdJournalFilter { + fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> { + Box::pin(self.run()) + } +} + +impl CmdJournalFilter { + async fn run(self) -> Result<(), anyhow::Error> { + // Create a new file name that will be the temp new file + // while its written + let mut temp_filename = self + .target_path + .file_name() + .ok_or_else(|| { + anyhow::format_err!( + "The path is not a valid filename - {}", + self.target_path.to_string_lossy() + ) + })? + .to_string_lossy() + .to_string(); + temp_filename.insert_str(0, ".staging."); + let temp_path = self.target_path.with_file_name(&temp_filename); + std::fs::remove_file(&temp_path).ok(); + + // Load the source journal and the target journal (in the temp location) + let source = LogFileJournal::new(self.source_path)?; + let target = LogFileJournal::new(temp_path.clone())?; + + // Put a filter on the farget + let mut builder = FilteredJournalBuilder::new(); + for filter in self.filters { + builder = match filter { + FilterOut::Memory => builder.with_ignore_memory(true), + FilterOut::Threads => builder.with_ignore_threads(true), + FilterOut::FileSystem => builder.with_ignore_fs(true), + FilterOut::Core => builder.with_ignore_core(true), + FilterOut::Snapshots => builder.with_ignore_snapshots(true), + FilterOut::Networking => builder.with_ignore_networking(true), + } + } + let target = builder.build(target); + + // Copy the journal over and rename the temp file to the target file + copy_journal(&source, &target)?; + drop(target); + std::fs::rename(temp_path, self.target_path.clone())?; + + // Now print the outcome + let journal = LogFileJournal::new(&self.target_path)?; + let printer = PrintingJournal::default(); + copy_journal(&journal, &printer)?; + Ok(()) + } +} diff --git a/lib/cli/src/commands/journal/import.rs b/lib/cli/src/commands/journal/import.rs new file mode 100644 index 00000000000..e0377e05ee1 --- /dev/null +++ b/lib/cli/src/commands/journal/import.rs @@ -0,0 +1,57 @@ +use std::{io::ErrorKind, path::PathBuf}; + +use clap::Parser; +use wasmer_edge_cli::cmd::AsyncCliCommand; +use wasmer_wasix::journal::{JournalEntry, LogFileJournal, WritableJournal}; + +/// Imports events into a journal file. Events are streamed as JSON +/// objects into `stdin` +#[derive(Debug, Parser)] +pub struct CmdJournaImport { + /// Path to the journal that will be printed + #[clap(index = 1)] + journal_path: PathBuf, +} + +impl AsyncCliCommand for CmdJournaImport { + fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> { + Box::pin(self.run()) + } +} + +impl CmdJournaImport { + async fn run(self) -> Result<(), anyhow::Error> { + // Erase the journal file at the path and reopen it + if self.journal_path.exists() { + std::fs::remove_file(&self.journal_path)?; + } + let journal = LogFileJournal::new(self.journal_path)?; + + // Read all the events from `stdin`, deserialize them and save them to the journal + let stdin = std::io::stdin(); + let mut lines = String::new(); + let mut line = String::new(); + loop { + // Read until the end marker + loop { + line.clear(); + match stdin.read_line(&mut line) { + Ok(0) => return Ok(()), + Ok(_) => { + lines.push_str(&line); + if line.trim_end() == "}" { + break; + } + } + Err(err) if err.kind() == ErrorKind::UnexpectedEof => return Ok(()), + Err(err) => return Err(err.into()), + } + } + + let record = serde_json::from_str::>(&lines)?; + println!("{}", record); + journal.write(record)?; + lines.clear(); + } + } +} diff --git a/lib/cli/src/commands/journal/inspect.rs b/lib/cli/src/commands/journal/inspect.rs new file mode 100644 index 00000000000..9a19e56d77f --- /dev/null +++ b/lib/cli/src/commands/journal/inspect.rs @@ -0,0 +1,28 @@ +use std::path::PathBuf; + +use clap::Parser; +use wasmer_edge_cli::cmd::AsyncCliCommand; +use wasmer_wasix::journal::{copy_journal, LogFileJournal, PrintingJournal}; + +/// Prints a summarized version of contents of a journal to stdout +#[derive(Debug, Parser)] +pub struct CmdJournaInspect { + /// Path to the journal that will be printed + #[clap(index = 1)] + journal_path: PathBuf, +} + +impl AsyncCliCommand for CmdJournaInspect { + fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> { + Box::pin(self.run()) + } +} + +impl CmdJournaInspect { + async fn run(self) -> Result<(), anyhow::Error> { + let journal = LogFileJournal::new(self.journal_path)?; + let printer = PrintingJournal::default(); + copy_journal(&journal, &printer)?; + Ok(()) + } +} diff --git a/lib/cli/src/commands/journal/mod.rs b/lib/cli/src/commands/journal/mod.rs new file mode 100644 index 00000000000..524cc0c6463 --- /dev/null +++ b/lib/cli/src/commands/journal/mod.rs @@ -0,0 +1,40 @@ +use wasmer_edge_cli::cmd::AsyncCliCommand; + +mod compact; +mod export; +mod filter; +mod import; +mod inspect; + +pub use compact::*; +pub use export::*; +pub use filter::*; +pub use import::*; +pub use inspect::*; + +/// Manage Journal files. +#[derive(clap::Subcommand, Debug)] +pub enum CmdJournal { + /// Compacts a journal into a smaller size by removed redundant or duplicate events + Compact(CmdJournalCompact), + /// Exports the contents of a journal to stdout as JSON objects + Export(CmdJournalExport), + /// Imports the events into a journal as JSON objects + Import(CmdJournaImport), + /// Inspects the contents of a journal and summarizes it to `stdout` + Inspect(CmdJournaInspect), + /// Filters out certain events from a journal + Filter(CmdJournalFilter), +} + +impl AsyncCliCommand for CmdJournal { + fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> { + match self { + Self::Compact(cmd) => cmd.run_async(), + Self::Import(cmd) => cmd.run_async(), + Self::Export(cmd) => cmd.run_async(), + Self::Inspect(cmd) => cmd.run_async(), + Self::Filter(cmd) => cmd.run_async(), + } + } +} diff --git a/lib/cli/src/commands/mod.rs b/lib/cli/src/commands/mod.rs index 337faa19c8c..9db38db12e7 100644 --- a/lib/cli/src/commands/mod.rs +++ b/lib/cli/src/commands/mod.rs @@ -15,6 +15,8 @@ mod create_obj; mod gen_c_header; mod init; mod inspect; +#[cfg(feature = "journal")] +mod journal; mod login; mod package; mod publish; @@ -25,6 +27,8 @@ mod validate; mod wast; mod whoami; +#[cfg(feature = "journal")] +pub use self::journal::*; pub use self::{ add::*, cache::*, config::*, container::*, init::*, inspect::*, login::*, package::*, publish::*, run::Run, self_update::*, validate::*, whoami::*, diff --git a/lib/cli/src/commands/run/mod.rs b/lib/cli/src/commands/run/mod.rs index c7af2cc2c97..1f430493635 100644 --- a/lib/cli/src/commands/run/mod.rs +++ b/lib/cli/src/commands/run/mod.rs @@ -28,9 +28,16 @@ use wasmer::{ #[cfg(feature = "compiler")] use wasmer_compiler::ArtifactBuild; use wasmer_registry::{wasmer_env::WasmerEnv, Package}; +#[cfg(feature = "journal")] +use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger}; use wasmer_wasix::{ bin_factory::BinaryPackage, - runners::{MappedCommand, MappedDirectory, Runner}, + journal::CompactingLogFileJournal, + runners::{ + dcgi::DcgiInstanceFactory, + wcgi::{self, NoOpWcgiCallbacks}, + MappedCommand, MappedDirectory, Runner, + }, runtime::{ module_cache::{CacheError, ModuleHash}, package_loader::PackageLoader, @@ -41,6 +48,7 @@ use wasmer_wasix::{ }; use wasmer_wasix::{ runners::{ + dcgi::DcgiRunner, emscripten::EmscriptenRunner, wasi::WasiRunner, wcgi::{AbortHandle, WcgiRunner}, @@ -119,15 +127,27 @@ impl Run { pb.finish_and_clear(); + // push the TTY state so we can restore it after the program finishes + let tty = runtime.tty().map(|tty| tty.tty_get()); + let result = { match target { - ExecutableTarget::WebAssembly { module, path } => { - self.execute_wasm(&path, &module, store, runtime) - } - ExecutableTarget::Package(pkg) => self.execute_webc(&pkg, runtime), + ExecutableTarget::WebAssembly { + module, + module_hash, + path, + } => self.execute_wasm(&path, &module, module_hash, store, runtime.clone()), + ExecutableTarget::Package(pkg) => self.execute_webc(&pkg, runtime.clone()), } }; + // restore the TTY state as the execution may have changed it + if let Some(state) = tty { + if let Some(tty) = runtime.tty() { + tty.tty_set(state); + } + } + if let Err(e) = &result { self.maybe_save_coredump(e); } @@ -140,13 +160,14 @@ impl Run { &self, path: &Path, module: &Module, + module_hash: ModuleHash, mut store: Store, runtime: Arc, ) -> Result<(), Error> { if wasmer_emscripten::is_emscripten_module(module) { self.execute_emscripten_module() } else if wasmer_wasix::is_wasi_module(module) || wasmer_wasix::is_wasix_module(module) { - self.execute_wasi_module(path, module, runtime, store) + self.execute_wasi_module(path, module, module_hash, runtime, store) } else { self.execute_pure_wasm_module(module, &mut store) } @@ -168,7 +189,9 @@ impl Run { let uses = self.load_injected_packages(&runtime)?; - if WcgiRunner::can_run_command(cmd.metadata())? { + if DcgiRunner::can_run_command(cmd.metadata())? { + self.run_dcgi(id, pkg, uses, runtime) + } else if WcgiRunner::can_run_command(cmd.metadata())? { self.run_wcgi(id, pkg, uses, runtime) } else if WasiRunner::can_run_command(cmd.metadata())? { self.run_wasi(id, pkg, uses, runtime) @@ -226,21 +249,62 @@ impl Run { uses: Vec, runtime: Arc, ) -> Result<(), Error> { - let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(); + let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(NoOpWcgiCallbacks); + self.config_wcgi(runner.config(), uses)?; + runner.run_command(command_name, pkg, runtime) + } - runner - .config() + fn config_wcgi( + &self, + config: &mut wcgi::Config, + uses: Vec, + ) -> Result<(), Error> { + config .args(self.args.clone()) .addr(self.wcgi.addr) .envs(self.wasi.env_vars.clone()) .map_directories(self.wasi.mapped_dirs.clone()) .callbacks(Callbacks::new(self.wcgi.addr)) .inject_packages(uses); - *runner.config().capabilities() = self.wasi.capabilities(); + *config.capabilities() = self.wasi.capabilities(); if self.wasi.forward_host_env { - runner.config().forward_host_env(); + config.forward_host_env(); } + #[cfg(feature = "journal")] + { + for trigger in self.wasi.snapshot_on.iter().cloned() { + config.add_snapshot_trigger(trigger); + } + if self.wasi.snapshot_on.is_empty() && !self.wasi.journals.is_empty() { + config.add_default_snapshot_triggers(); + } + if let Some(period) = self.wasi.snapshot_interval { + if self.wasi.journals.is_empty() { + return Err(anyhow::format_err!( + "If you specify a snapshot interval then you must also specify a journal file" + )); + } + config.with_snapshot_interval(Duration::from_millis(period)); + } + for journal in self.wasi.build_journals()? { + config.add_journal(journal); + } + } + + Ok(()) + } + + fn run_dcgi( + &self, + command_name: &str, + pkg: &BinaryPackage, + uses: Vec, + runtime: Arc, + ) -> Result<(), Error> { + let factory = DcgiInstanceFactory::new(); + let mut runner = wasmer_wasix::runners::dcgi::DcgiRunner::new(factory); + self.config_wcgi(runner.config().inner(), uses); runner.run_command(command_name, pkg, runtime) } @@ -294,7 +358,7 @@ impl Run { ) -> Result { let packages = self.load_injected_packages(runtime)?; - let runner = WasiRunner::new() + let mut runner = WasiRunner::new() .with_args(&self.args) .with_injected_packages(packages) .with_envs(self.wasi.env_vars.clone()) @@ -303,6 +367,27 @@ impl Run { .with_forward_host_env(self.wasi.forward_host_env) .with_capabilities(self.wasi.capabilities()); + #[cfg(feature = "journal")] + { + for trigger in self.wasi.snapshot_on.iter().cloned() { + runner.add_snapshot_trigger(trigger); + } + if self.wasi.snapshot_on.is_empty() && !self.wasi.journals.is_empty() { + runner.add_default_snapshot_triggers(); + } + if let Some(period) = self.wasi.snapshot_interval { + if self.wasi.journals.is_empty() { + return Err(anyhow::format_err!( + "If you specify a snapshot interval then you must also specify a journal file" + )); + } + runner.with_snapshot_interval(Duration::from_millis(period)); + } + for journal in self.wasi.build_journals()? { + runner.add_journal(journal); + } + } + Ok(runner) } @@ -311,6 +396,7 @@ impl Run { &self, wasm_path: &Path, module: &Module, + module_hash: ModuleHash, runtime: Arc, mut store: Store, ) -> Result<(), Error> { @@ -321,6 +407,7 @@ impl Run { runtime, &program_name, module, + module_hash, self.wasi.enable_async_threads, ) } @@ -556,7 +643,11 @@ impl TargetOnDisk { #[derive(Debug, Clone)] enum ExecutableTarget { - WebAssembly { module: Module, path: PathBuf }, + WebAssembly { + module: Module, + module_hash: ModuleHash, + path: PathBuf, + }, Package(BinaryPackage), } @@ -604,6 +695,7 @@ impl ExecutableTarget { Ok(ExecutableTarget::WebAssembly { module, + module_hash: ModuleHash::hash(&wasm), path: path.to_path_buf(), }) } @@ -611,9 +703,14 @@ impl ExecutableTarget { let engine = runtime.engine(); pb.set_message("Deserializing pre-compiled WebAssembly module"); let module = unsafe { Module::deserialize_from_file(&engine, path)? }; + let module_hash = { + let wasm = std::fs::read(path)?; + ModuleHash::hash(wasm) + }; Ok(ExecutableTarget::WebAssembly { module, + module_hash, path: path.to_path_buf(), }) } diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index f5581e942a5..d04c92e16ef 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -1,6 +1,7 @@ use std::{ collections::{BTreeSet, HashMap}, path::{Path, PathBuf}, + str::FromStr, sync::{mpsc::Sender, Arc}, time::Duration, }; @@ -13,16 +14,19 @@ use url::Url; use virtual_fs::{DeviceFile, FileSystem, PassthruFileSystem, RootFileSystemBuilder}; use wasmer::{Engine, Function, Instance, Memory32, Memory64, Module, RuntimeError, Store, Value}; use wasmer_registry::wasmer_env::WasmerEnv; +#[cfg(feature = "journal")] +use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger}; use wasmer_wasix::{ bin_factory::BinaryPackage, capabilities::Capabilities, default_fs_backing, get_wasi_versions, http::HttpClient, + journal::{CompactingLogFileJournal, DynJournal}, os::{tty_sys::SysTty, TtyBridge}, rewind_ext, runners::{MappedCommand, MappedDirectory}, runtime::{ - module_cache::{FileSystemCache, ModuleCache}, + module_cache::{FileSystemCache, ModuleCache, ModuleHash}, package_loader::{BuiltinPackageLoader, PackageLoader}, resolver::{ FileSystemSource, InMemorySource, MultiSource, PackageSpecifier, Source, WapmSource, @@ -105,6 +109,59 @@ pub struct Wasi { #[clap(long = "enable-async-threads")] pub enable_async_threads: bool, + /// Specifies one or more journal files that Wasmer will use to restore + /// and save the state of the WASM process as it executes. + /// + /// The state of the WASM process and its sandbox will be reapplied using + /// the journals in the order that you specify here. + /// + /// The last journal file specified will be created if it does not exist + /// and opened for read and write. New journal events will be written to this + /// file + #[cfg(feature = "journal")] + #[clap(long = "journal")] + pub journals: Vec, + + /// Flag that indicates if the journal will be automatically compacted + /// as it fills up and when the process exits + #[cfg(feature = "journal")] + #[clap(long = "enable-compaction")] + pub enable_compaction: bool, + + /// Tells the compactor not to compact when the journal log file is closed + #[cfg(feature = "journal")] + #[clap(long = "without-compact-on-drop")] + pub without_compact_on_drop: bool, + + /// Tells the compactor to compact when it grows by a certain factor of + /// its original size. (i.e. '0.2' would be it compacts after the journal + /// has grown by 20 percent) + /// + /// Default is to compact on growth that exceeds 15% + #[cfg(feature = "journal")] + #[clap(long = "with-compact-on-growth", default_value = "0.15")] + pub with_compact_on_growth: f32, + + /// Indicates what events will cause a snapshot to be taken + /// and written to the journal file. + /// + /// If not specified, the default is to snapshot when the process idles, when + /// the process exits or periodically if an interval argument is also supplied. + /// + /// Additionally if the snapshot-on is not specified it will also take a snapshot + /// on the first stdin, environ or socket listen - this can be used to accelerate + /// the boot up time of WASM processes. + #[cfg(feature = "journal")] + #[clap(long = "snapshot-on")] + pub snapshot_on: Vec, + + /// Adds a periodic interval (measured in milli-seconds) that the runtime will automatically + /// takes snapshots of the running process and write them to the journal. When specifying + /// this parameter it implies that `--snapshot-on interval` has also been specified. + #[cfg(feature = "journal")] + #[clap(long = "snapshot-period")] + pub snapshot_interval: Option, + /// Allow instances to send http requests. /// /// Access to domains is granted by default. @@ -306,6 +363,19 @@ impl Wasi { *builder.capabilities_mut() = self.capabilities(); + #[cfg(feature = "journal")] + { + for trigger in self.snapshot_on.iter().cloned() { + builder.add_snapshot_trigger(trigger); + } + if let Some(interval) = self.snapshot_interval { + builder.with_snapshot_interval(std::time::Duration::from_millis(interval)); + } + for journal in self.build_journals()? { + builder.add_journal(journal); + } + } + #[cfg(feature = "experimental-io-devices")] { if self.enable_experimental_io_devices { @@ -317,6 +387,31 @@ impl Wasi { Ok(builder) } + #[cfg(feature = "journal")] + pub fn build_journals(&self) -> anyhow::Result>> { + let mut ret = Vec::new(); + for journal in self.journals.clone() { + if self.enable_compaction { + let mut journal = CompactingLogFileJournal::new(journal)?; + if !self.without_compact_on_drop { + journal = journal.with_compact_on_drop() + } + if self.with_compact_on_growth.is_normal() && self.with_compact_on_growth != 0f32 { + journal = journal.with_compact_on_factor_size(self.with_compact_on_growth); + } + ret.push(Arc::new(journal) as Arc); + } else { + ret.push(Arc::new(LogFileJournal::new(journal)?)); + } + } + Ok(ret) + } + + #[cfg(not(feature = "journal"))] + pub fn build_journals(&self) -> anyhow::Result>> { + Ok(Vec::new()) + } + pub fn build_mapped_directories(&self) -> Result, anyhow::Error> { let mut mapped_dirs = Vec::new(); @@ -460,6 +555,11 @@ impl Wasi { rt.set_networking_implementation(virtual_net::UnsupportedVirtualNetworking::default()); } + #[cfg(feature = "journal")] + for journal in self.build_journals()? { + rt.add_journal(journal); + } + if !self.no_tty { let tty = Arc::new(SysTty::default()); tty.reset(); @@ -492,13 +592,14 @@ impl Wasi { pub fn instantiate( &self, module: &Module, + module_hash: ModuleHash, program_name: String, args: Vec, runtime: Arc, store: &mut Store, ) -> Result<(WasiFunctionEnv, Instance)> { let builder = self.prepare(module, program_name, args, runtime)?; - let (instance, wasi_env) = builder.instantiate(module.clone(), store)?; + let (instance, wasi_env) = builder.instantiate_ext(module.clone(), module_hash, store)?; Ok((wasi_env, instance)) } diff --git a/lib/journal/Cargo.toml b/lib/journal/Cargo.toml new file mode 100644 index 00000000000..452454aef47 --- /dev/null +++ b/lib/journal/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "wasmer-journal" +version = "0.1.0" +description = "Journaling functionality used by Wasmer to save and restore WASM state" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[features] +default = [ "log-file", "wasmer/sys" ] +log-file = [ "shared-buffer" ] + +[dependencies] +wasmer = { default-features = false, path = "../api", version = "=4.2.5" } +wasmer-wasix-types = { path = "../wasi-types", version = "0.18.0", features = [ "enable-serde" ] } +virtual-net = { path = "../virtual-net", version = "0.6.2", default-features = false, features = ["rkyv"] } + +shared-buffer = { workspace = true, optional = true } +thiserror = "1" +bytes = "1.1" +async-trait = { version = "^0.1" } +tracing = "0.1" +derivative = { version = "^2" } +base64 = "0.21" +bincode = { version = "1.3" } +serde = { version = "1.0", default-features = false, features = ["derive"] } +anyhow = "1.0" +rkyv = { version = "0.7.40", features = ["indexmap", "validation", "strict"] } +bytecheck = { version = "0.6.8" } +lz4_flex = { version = "0.11" } +num_enum = "0.5.7" +serde_json = { version = "^1" } + +[dev-dependencies] +tracing-test = "0.2.4" +tempfile = "3.6.0" \ No newline at end of file diff --git a/lib/journal/src/base64.rs b/lib/journal/src/base64.rs new file mode 100644 index 00000000000..50d251f8329 --- /dev/null +++ b/lib/journal/src/base64.rs @@ -0,0 +1,20 @@ +use std::borrow::Cow; + +use lz4_flex::block::{compress_prepend_size, decompress_size_prepended}; + +use serde::{Deserialize, Serialize}; +use serde::{Deserializer, Serializer}; + +pub fn serialize(v: &[u8], s: S) -> Result { + #[allow(deprecated)] + let base64 = base64::encode(compress_prepend_size(v)); + String::serialize(&base64, s) +} + +pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { + let base64 = String::deserialize(d)?; + #[allow(deprecated)] + base64::decode(decompress_size_prepended(base64.as_bytes()).map_err(serde::de::Error::custom)?) + .map_err(serde::de::Error::custom) + .map(|d| d.into()) +} diff --git a/lib/journal/src/concrete/arc.rs b/lib/journal/src/concrete/arc.rs new file mode 100644 index 00000000000..fa7a84146d8 --- /dev/null +++ b/lib/journal/src/concrete/arc.rs @@ -0,0 +1,41 @@ +use super::*; +use std::ops::Deref; +use std::sync::Arc; + +impl ReadableJournal for Arc { + fn read(&self) -> anyhow::Result>> { + self.deref().read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.deref().as_restarted() + } +} + +impl WritableJournal for Arc { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.deref().write(entry) + } +} + +impl ReadableJournal for Arc { + fn read(&self) -> anyhow::Result>> { + self.deref().read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.deref().as_restarted() + } +} + +impl WritableJournal for Arc { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.deref().write(entry) + } +} + +impl Journal for Arc { + fn split(self) -> (Box, Box) { + (Box::new(self.clone()), Box::new(self.clone())) + } +} diff --git a/lib/journal/src/concrete/archived.rs b/lib/journal/src/concrete/archived.rs new file mode 100644 index 00000000000..ad0cbf98a88 --- /dev/null +++ b/lib/journal/src/concrete/archived.rs @@ -0,0 +1,1775 @@ +use lz4_flex::block::compress_prepend_size; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use rkyv::ser::{ScratchSpace, Serializer}; +use rkyv::{Archive, CheckBytes, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::time::{Duration, SystemTime}; + +use super::*; + +pub const JOURNAL_MAGIC_NUMBER: u64 = 0x310d6dd027362979; +pub const JOURNAL_MAGIC_NUMBER_BYTES: [u8; 8] = JOURNAL_MAGIC_NUMBER.to_be_bytes(); + +#[repr(u16)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + IntoPrimitive, + TryFromPrimitive, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalEntryRecordType { + InitModuleV1 = 1, + ProcessExitV1 = 2, + SetThreadV1 = 3, + CloseThreadV1 = 4, + FileDescriptorSeekV1 = 5, + FileDescriptorWriteV1 = 6, + UpdateMemoryRegionV1 = 7, + SetClockTimeV1 = 9, + OpenFileDescriptorV1 = 10, + CloseFileDescriptorV1 = 11, + RenumberFileDescriptorV1 = 12, + DuplicateFileDescriptorV1 = 13, + CreateDirectoryV1 = 14, + RemoveDirectoryV1 = 15, + PathSetTimesV1 = 16, + FileDescriptorSetTimesV1 = 17, + FileDescriptorSetSizeV1 = 18, + FileDescriptorSetFlagsV1 = 19, + FileDescriptorSetRightsV1 = 20, + FileDescriptorAdviseV1 = 21, + FileDescriptorAllocateV1 = 22, + CreateHardLinkV1 = 23, + CreateSymbolicLinkV1 = 24, + UnlinkFileV1 = 25, + PathRenameV1 = 26, + ChangeDirectoryV1 = 27, + EpollCreateV1 = 28, + EpollCtlV1 = 29, + TtySetV1 = 30, + CreatePipeV1 = 31, + CreateEventV1 = 32, + PortAddAddrV1 = 33, + PortDelAddrV1 = 34, + PortAddrClearV1 = 35, + PortBridgeV1 = 36, + PortUnbridgeV1 = 37, + PortDhcpAcquireV1 = 38, + PortGatewaySetV1 = 39, + PortRouteAddV1 = 40, + PortRouteClearV1 = 41, + PortRouteDelV1 = 42, + SocketOpenV1 = 43, + SocketListenV1 = 44, + SocketBindV1 = 45, + SocketConnectedV1 = 46, + SocketAcceptedV1 = 47, + SocketJoinIpv4MulticastV1 = 48, + SocketJoinIpv6MulticastV1 = 49, + SocketLeaveIpv4MulticastV1 = 50, + SocketLeaveIpv6MulticastV1 = 51, + SocketSendFileV1 = 52, + SocketSendToV1 = 53, + SocketSendV1 = 54, + SocketSetOptFlagV1 = 55, + SocketSetOptSizeV1 = 56, + SocketSetOptTimeV1 = 57, + SocketShutdownV1 = 58, + SnapshotV1 = 59, +} + +impl JournalEntryRecordType { + /// # Safety + /// + /// `rykv` makes direct memory references to achieve high performance + /// however this does mean care must be taken that the data itself + /// can not be manipulated or corrupted. + pub unsafe fn deserialize_archive(self, data: &[u8]) -> anyhow::Result> { + match self { + JournalEntryRecordType::InitModuleV1 => ArchivedJournalEntry::InitModuleV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::ProcessExitV1 => ArchivedJournalEntry::ProcessExitV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::SetThreadV1 => ArchivedJournalEntry::SetThreadV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::CloseThreadV1 => ArchivedJournalEntry::CloseThreadV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::FileDescriptorSeekV1 => { + ArchivedJournalEntry::FileDescriptorSeekV1(rkyv::archived_root::< + JournalEntryFileDescriptorSeekV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorWriteV1 => { + ArchivedJournalEntry::FileDescriptorWriteV1(rkyv::archived_root::< + JournalEntryFileDescriptorWriteV1, + >(data)) + } + JournalEntryRecordType::UpdateMemoryRegionV1 => { + ArchivedJournalEntry::UpdateMemoryRegionV1(rkyv::archived_root::< + JournalEntryUpdateMemoryRegionV1, + >(data)) + } + JournalEntryRecordType::SetClockTimeV1 => { + ArchivedJournalEntry::SetClockTimeV1(rkyv::archived_root::< + JournalEntrySetClockTimeV1, + >(data)) + } + JournalEntryRecordType::OpenFileDescriptorV1 => { + ArchivedJournalEntry::OpenFileDescriptorV1(rkyv::archived_root::< + JournalEntryOpenFileDescriptorV1, + >(data)) + } + JournalEntryRecordType::CloseFileDescriptorV1 => { + ArchivedJournalEntry::CloseFileDescriptorV1(rkyv::archived_root::< + JournalEntryCloseFileDescriptorV1, + >(data)) + } + JournalEntryRecordType::RenumberFileDescriptorV1 => { + ArchivedJournalEntry::RenumberFileDescriptorV1(rkyv::archived_root::< + JournalEntryRenumberFileDescriptorV1, + >(data)) + } + JournalEntryRecordType::DuplicateFileDescriptorV1 => { + ArchivedJournalEntry::DuplicateFileDescriptorV1(rkyv::archived_root::< + JournalEntryDuplicateFileDescriptorV1, + >(data)) + } + JournalEntryRecordType::CreateDirectoryV1 => { + ArchivedJournalEntry::CreateDirectoryV1(rkyv::archived_root::< + JournalEntryCreateDirectoryV1, + >(data)) + } + JournalEntryRecordType::RemoveDirectoryV1 => { + ArchivedJournalEntry::RemoveDirectoryV1(rkyv::archived_root::< + JournalEntryRemoveDirectoryV1, + >(data)) + } + JournalEntryRecordType::PathSetTimesV1 => { + ArchivedJournalEntry::PathSetTimesV1(rkyv::archived_root::< + JournalEntryPathSetTimesV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorSetTimesV1 => { + ArchivedJournalEntry::FileDescriptorSetTimesV1(rkyv::archived_root::< + JournalEntryFileDescriptorSetTimesV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorSetSizeV1 => { + ArchivedJournalEntry::FileDescriptorSetSizeV1(rkyv::archived_root::< + JournalEntryFileDescriptorSetSizeV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorSetFlagsV1 => { + ArchivedJournalEntry::FileDescriptorSetFlagsV1(rkyv::archived_root::< + JournalEntryFileDescriptorSetFlagsV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorSetRightsV1 => { + ArchivedJournalEntry::FileDescriptorSetRightsV1(rkyv::archived_root::< + JournalEntryFileDescriptorSetRightsV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorAdviseV1 => { + ArchivedJournalEntry::FileDescriptorAdviseV1(rkyv::archived_root::< + JournalEntryFileDescriptorAdviseV1, + >(data)) + } + JournalEntryRecordType::FileDescriptorAllocateV1 => { + ArchivedJournalEntry::FileDescriptorAllocateV1(rkyv::archived_root::< + JournalEntryFileDescriptorAllocateV1, + >(data)) + } + JournalEntryRecordType::CreateHardLinkV1 => { + ArchivedJournalEntry::CreateHardLinkV1(rkyv::archived_root::< + JournalEntryCreateHardLinkV1, + >(data)) + } + JournalEntryRecordType::CreateSymbolicLinkV1 => { + ArchivedJournalEntry::CreateSymbolicLinkV1(rkyv::archived_root::< + JournalEntryCreateSymbolicLinkV1, + >(data)) + } + JournalEntryRecordType::UnlinkFileV1 => ArchivedJournalEntry::UnlinkFileV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::PathRenameV1 => ArchivedJournalEntry::PathRenameV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::ChangeDirectoryV1 => { + ArchivedJournalEntry::ChangeDirectoryV1(rkyv::archived_root::< + JournalEntryChangeDirectoryV1, + >(data)) + } + JournalEntryRecordType::EpollCreateV1 => ArchivedJournalEntry::EpollCreateV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::EpollCtlV1 => ArchivedJournalEntry::EpollCtlV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::TtySetV1 => { + ArchivedJournalEntry::TtySetV1(rkyv::archived_root::(data)) + } + JournalEntryRecordType::CreatePipeV1 => ArchivedJournalEntry::CreatePipeV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::CreateEventV1 => ArchivedJournalEntry::CreateEventV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::PortAddAddrV1 => ArchivedJournalEntry::PortAddAddrV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::PortDelAddrV1 => ArchivedJournalEntry::PortDelAddrV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::PortAddrClearV1 => return Ok(JournalEntry::PortAddrClearV1), + JournalEntryRecordType::PortBridgeV1 => ArchivedJournalEntry::PortBridgeV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::PortUnbridgeV1 => return Ok(JournalEntry::PortUnbridgeV1), + JournalEntryRecordType::PortDhcpAcquireV1 => { + return Ok(JournalEntry::PortDhcpAcquireV1) + } + JournalEntryRecordType::PortGatewaySetV1 => { + ArchivedJournalEntry::PortGatewaySetV1(rkyv::archived_root::< + JournalEntryPortGatewaySetV1, + >(data)) + } + JournalEntryRecordType::PortRouteAddV1 => { + ArchivedJournalEntry::PortRouteAddV1(rkyv::archived_root::< + JournalEntryPortRouteAddV1, + >(data)) + } + JournalEntryRecordType::PortRouteClearV1 => return Ok(JournalEntry::PortRouteClearV1), + JournalEntryRecordType::PortRouteDelV1 => { + ArchivedJournalEntry::PortRouteDelV1(rkyv::archived_root::< + JournalEntryPortRouteDelV1, + >(data)) + } + JournalEntryRecordType::SocketOpenV1 => ArchivedJournalEntry::SocketOpenV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::SocketListenV1 => { + ArchivedJournalEntry::SocketListenV1(rkyv::archived_root::< + JournalEntrySocketListenV1, + >(data)) + } + JournalEntryRecordType::SocketBindV1 => ArchivedJournalEntry::SocketBindV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::SocketConnectedV1 => { + ArchivedJournalEntry::SocketConnectedV1(rkyv::archived_root::< + JournalEntrySocketConnectedV1, + >(data)) + } + JournalEntryRecordType::SocketAcceptedV1 => { + ArchivedJournalEntry::SocketAcceptedV1(rkyv::archived_root::< + JournalEntrySocketAcceptedV1, + >(data)) + } + JournalEntryRecordType::SocketJoinIpv4MulticastV1 => { + ArchivedJournalEntry::SocketJoinIpv4MulticastV1(rkyv::archived_root::< + JournalEntrySocketJoinIpv4MulticastV1, + >(data)) + } + JournalEntryRecordType::SocketJoinIpv6MulticastV1 => { + ArchivedJournalEntry::SocketJoinIpv6MulticastV1(rkyv::archived_root::< + JournalEntrySocketJoinIpv6MulticastV1, + >(data)) + } + JournalEntryRecordType::SocketLeaveIpv4MulticastV1 => { + ArchivedJournalEntry::SocketLeaveIpv4MulticastV1(rkyv::archived_root::< + JournalEntrySocketLeaveIpv4MulticastV1, + >(data)) + } + JournalEntryRecordType::SocketLeaveIpv6MulticastV1 => { + ArchivedJournalEntry::SocketLeaveIpv6MulticastV1(rkyv::archived_root::< + JournalEntrySocketLeaveIpv6MulticastV1, + >(data)) + } + JournalEntryRecordType::SocketSendFileV1 => { + ArchivedJournalEntry::SocketSendFileV1(rkyv::archived_root::< + JournalEntrySocketSendFileV1, + >(data)) + } + JournalEntryRecordType::SocketSendToV1 => { + ArchivedJournalEntry::SocketSendToV1(rkyv::archived_root::< + JournalEntrySocketSendToV1, + >(data)) + } + JournalEntryRecordType::SocketSendV1 => ArchivedJournalEntry::SocketSendV1( + rkyv::archived_root::(data), + ), + JournalEntryRecordType::SocketSetOptFlagV1 => { + ArchivedJournalEntry::SocketSetOptFlagV1(rkyv::archived_root::< + JournalEntrySocketSetOptFlagV1, + >(data)) + } + JournalEntryRecordType::SocketSetOptSizeV1 => { + ArchivedJournalEntry::SocketSetOptSizeV1(rkyv::archived_root::< + JournalEntrySocketSetOptSizeV1, + >(data)) + } + JournalEntryRecordType::SocketSetOptTimeV1 => { + ArchivedJournalEntry::SocketSetOptTimeV1(rkyv::archived_root::< + JournalEntrySocketSetOptTimeV1, + >(data)) + } + JournalEntryRecordType::SocketShutdownV1 => { + ArchivedJournalEntry::SocketShutdownV1(rkyv::archived_root::< + JournalEntrySocketShutdownV1, + >(data)) + } + JournalEntryRecordType::SnapshotV1 => ArchivedJournalEntry::SnapshotV1( + rkyv::archived_root::(data), + ), + } + .try_into() + } +} + +impl<'a> JournalEntry<'a> { + pub fn archive_record_type(&self) -> JournalEntryRecordType { + match self { + Self::InitModuleV1 { .. } => JournalEntryRecordType::InitModuleV1, + Self::UpdateMemoryRegionV1 { .. } => JournalEntryRecordType::UpdateMemoryRegionV1, + Self::ProcessExitV1 { .. } => JournalEntryRecordType::ProcessExitV1, + Self::SetThreadV1 { .. } => JournalEntryRecordType::SetThreadV1, + Self::CloseThreadV1 { .. } => JournalEntryRecordType::CloseThreadV1, + Self::FileDescriptorSeekV1 { .. } => JournalEntryRecordType::FileDescriptorSeekV1, + Self::FileDescriptorWriteV1 { .. } => JournalEntryRecordType::FileDescriptorWriteV1, + Self::SetClockTimeV1 { .. } => JournalEntryRecordType::SetClockTimeV1, + Self::CloseFileDescriptorV1 { .. } => JournalEntryRecordType::CloseFileDescriptorV1, + Self::OpenFileDescriptorV1 { .. } => JournalEntryRecordType::OpenFileDescriptorV1, + Self::RenumberFileDescriptorV1 { .. } => { + JournalEntryRecordType::RenumberFileDescriptorV1 + } + Self::DuplicateFileDescriptorV1 { .. } => { + JournalEntryRecordType::DuplicateFileDescriptorV1 + } + Self::CreateDirectoryV1 { .. } => JournalEntryRecordType::CreateDirectoryV1, + Self::RemoveDirectoryV1 { .. } => JournalEntryRecordType::RemoveDirectoryV1, + Self::PathSetTimesV1 { .. } => JournalEntryRecordType::PathSetTimesV1, + Self::FileDescriptorSetTimesV1 { .. } => { + JournalEntryRecordType::FileDescriptorSetTimesV1 + } + Self::FileDescriptorSetFlagsV1 { .. } => { + JournalEntryRecordType::FileDescriptorSetFlagsV1 + } + Self::FileDescriptorSetRightsV1 { .. } => { + JournalEntryRecordType::FileDescriptorSetRightsV1 + } + Self::FileDescriptorSetSizeV1 { .. } => JournalEntryRecordType::FileDescriptorSetSizeV1, + Self::FileDescriptorAdviseV1 { .. } => JournalEntryRecordType::FileDescriptorAdviseV1, + Self::FileDescriptorAllocateV1 { .. } => { + JournalEntryRecordType::FileDescriptorAllocateV1 + } + Self::CreateHardLinkV1 { .. } => JournalEntryRecordType::CreateHardLinkV1, + Self::CreateSymbolicLinkV1 { .. } => JournalEntryRecordType::CreateSymbolicLinkV1, + Self::UnlinkFileV1 { .. } => JournalEntryRecordType::UnlinkFileV1, + Self::PathRenameV1 { .. } => JournalEntryRecordType::PathRenameV1, + Self::ChangeDirectoryV1 { .. } => JournalEntryRecordType::ChangeDirectoryV1, + Self::EpollCreateV1 { .. } => JournalEntryRecordType::EpollCreateV1, + Self::EpollCtlV1 { .. } => JournalEntryRecordType::EpollCtlV1, + Self::TtySetV1 { .. } => JournalEntryRecordType::TtySetV1, + Self::CreatePipeV1 { .. } => JournalEntryRecordType::CreatePipeV1, + Self::CreateEventV1 { .. } => JournalEntryRecordType::CreateEventV1, + Self::PortAddAddrV1 { .. } => JournalEntryRecordType::PortAddAddrV1, + Self::PortDelAddrV1 { .. } => JournalEntryRecordType::PortDelAddrV1, + Self::PortAddrClearV1 => JournalEntryRecordType::PortAddrClearV1, + Self::PortBridgeV1 { .. } => JournalEntryRecordType::PortBridgeV1, + Self::PortUnbridgeV1 => JournalEntryRecordType::PortUnbridgeV1, + Self::PortDhcpAcquireV1 => JournalEntryRecordType::PortDhcpAcquireV1, + Self::PortGatewaySetV1 { .. } => JournalEntryRecordType::PortGatewaySetV1, + Self::PortRouteAddV1 { .. } => JournalEntryRecordType::PortRouteAddV1, + Self::PortRouteClearV1 => JournalEntryRecordType::PortRouteClearV1, + Self::PortRouteDelV1 { .. } => JournalEntryRecordType::PortRouteDelV1, + Self::SocketOpenV1 { .. } => JournalEntryRecordType::SocketOpenV1, + Self::SocketListenV1 { .. } => JournalEntryRecordType::SocketListenV1, + Self::SocketBindV1 { .. } => JournalEntryRecordType::SocketBindV1, + Self::SocketConnectedV1 { .. } => JournalEntryRecordType::SocketConnectedV1, + Self::SocketAcceptedV1 { .. } => JournalEntryRecordType::SocketAcceptedV1, + Self::SocketJoinIpv4MulticastV1 { .. } => { + JournalEntryRecordType::SocketJoinIpv4MulticastV1 + } + Self::SocketJoinIpv6MulticastV1 { .. } => { + JournalEntryRecordType::SocketJoinIpv6MulticastV1 + } + Self::SocketLeaveIpv4MulticastV1 { .. } => { + JournalEntryRecordType::SocketLeaveIpv4MulticastV1 + } + Self::SocketLeaveIpv6MulticastV1 { .. } => { + JournalEntryRecordType::SocketLeaveIpv6MulticastV1 + } + Self::SocketSendFileV1 { .. } => JournalEntryRecordType::SocketSendFileV1, + Self::SocketSendToV1 { .. } => JournalEntryRecordType::SocketSendToV1, + Self::SocketSendV1 { .. } => JournalEntryRecordType::SocketSendV1, + Self::SocketSetOptFlagV1 { .. } => JournalEntryRecordType::SocketSetOptFlagV1, + Self::SocketSetOptSizeV1 { .. } => JournalEntryRecordType::SocketSetOptSizeV1, + Self::SocketSetOptTimeV1 { .. } => JournalEntryRecordType::SocketSetOptTimeV1, + Self::SocketShutdownV1 { .. } => JournalEntryRecordType::SocketShutdownV1, + Self::SnapshotV1 { .. } => JournalEntryRecordType::SnapshotV1, + } + } + + pub fn serialize_archive( + self, + serializer: &mut T, + ) -> anyhow::Result<()> + where + T::Error: std::fmt::Display, + { + let padding = |size: usize| { + let padding = size % 16; + let padding = match padding { + 0 => 0, + a => 16 - a, + }; + vec![0u8; padding] + }; + match self { + JournalEntry::InitModuleV1 { wasm_hash } => { + serializer.serialize_value(&JournalEntryInitModuleV1 { wasm_hash }) + } + JournalEntry::UpdateMemoryRegionV1 { region, data } => { + serializer.serialize_value(&JournalEntryUpdateMemoryRegionV1 { + start: region.start, + end: region.end, + _padding: padding(data.len()), + compressed_data: compress_prepend_size(data.as_ref()), + }) + } + JournalEntry::ProcessExitV1 { exit_code } => { + serializer.serialize_value(&JournalEntryProcessExitV1 { + exit_code: exit_code.map(|e| e.into()), + _padding: 0, + }) + } + JournalEntry::SetThreadV1 { + id, + call_stack, + memory_stack, + store_data, + is_64bit, + } => serializer.serialize_value(&JournalEntrySetThreadV1 { + id, + _padding: padding(call_stack.len() + memory_stack.len() + store_data.len()), + call_stack: call_stack.into_owned(), + memory_stack: memory_stack.into_owned(), + store_data: store_data.into_owned(), + is_64bit, + }), + JournalEntry::CloseThreadV1 { id, exit_code } => { + serializer.serialize_value(&JournalEntryCloseThreadV1 { + id, + exit_code: exit_code.map(|e| e.into()), + }) + } + JournalEntry::FileDescriptorSeekV1 { fd, offset, whence } => serializer + .serialize_value(&JournalEntryFileDescriptorSeekV1 { + fd, + offset, + whence: whence.into(), + }), + JournalEntry::FileDescriptorWriteV1 { + fd, + offset, + data, + is_64bit, + } => serializer.serialize_value(&JournalEntryFileDescriptorWriteV1 { + fd, + offset, + _padding: padding(data.len()), + data: data.into_owned(), + is_64bit, + }), + JournalEntry::SetClockTimeV1 { clock_id, time } => { + serializer.serialize_value(&JournalEntrySetClockTimeV1 { + clock_id: clock_id.into(), + time, + }) + } + JournalEntry::CloseFileDescriptorV1 { fd } => { + serializer.serialize_value(&JournalEntryCloseFileDescriptorV1 { fd, _padding: 0 }) + } + JournalEntry::OpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + path, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + } => serializer.serialize_value(&JournalEntryOpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + _padding: padding(path.as_bytes().len()), + path: path.into_owned(), + o_flags: o_flags.bits(), + fs_rights_base: fs_rights_base.bits(), + fs_rights_inheriting: fs_rights_inheriting.bits(), + fs_flags: fs_flags.bits(), + }), + JournalEntry::RenumberFileDescriptorV1 { old_fd, new_fd } => { + serializer.serialize_value(&JournalEntryRenumberFileDescriptorV1 { old_fd, new_fd }) + } + JournalEntry::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + } => serializer.serialize_value(&JournalEntryDuplicateFileDescriptorV1 { + original_fd, + copied_fd, + }), + JournalEntry::CreateDirectoryV1 { fd, path } => { + serializer.serialize_value(&JournalEntryCreateDirectoryV1 { + fd, + _padding: padding(path.as_bytes().len()), + path: path.into_owned(), + }) + } + JournalEntry::RemoveDirectoryV1 { fd, path } => { + serializer.serialize_value(&JournalEntryRemoveDirectoryV1 { + fd, + _padding: padding(path.as_bytes().len()), + path: path.into_owned(), + }) + } + JournalEntry::PathSetTimesV1 { + fd, + flags, + path, + st_atim, + st_mtim, + fst_flags, + } => serializer.serialize_value(&JournalEntryPathSetTimesV1 { + fd, + flags, + _padding: padding(path.as_bytes().len()), + path: path.into_owned(), + st_atim, + st_mtim, + fst_flags: fst_flags.bits(), + }), + JournalEntry::FileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags, + } => serializer.serialize_value(&JournalEntryFileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags: fst_flags.bits(), + }), + JournalEntry::FileDescriptorSetFlagsV1 { fd, flags } => { + serializer.serialize_value(&JournalEntryFileDescriptorSetFlagsV1 { + fd, + flags: flags.bits(), + }) + } + JournalEntry::FileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + } => serializer.serialize_value(&JournalEntryFileDescriptorSetRightsV1 { + fd, + fs_rights_base: fs_rights_base.bits(), + fs_rights_inheriting: fs_rights_inheriting.bits(), + }), + JournalEntry::FileDescriptorSetSizeV1 { fd, st_size } => { + serializer.serialize_value(&JournalEntryFileDescriptorSetSizeV1 { fd, st_size }) + } + JournalEntry::FileDescriptorAdviseV1 { + fd, + offset, + len, + advice, + } => serializer.serialize_value(&JournalEntryFileDescriptorAdviseV1 { + fd, + offset, + len, + advice: advice.into(), + }), + JournalEntry::FileDescriptorAllocateV1 { fd, offset, len } => serializer + .serialize_value(&JournalEntryFileDescriptorAllocateV1 { fd, offset, len }), + JournalEntry::CreateHardLinkV1 { + old_fd, + old_path, + old_flags, + new_fd, + new_path, + } => serializer.serialize_value(&JournalEntryCreateHardLinkV1 { + old_fd, + _padding: padding(old_path.as_bytes().len() + new_path.as_bytes().len()), + old_path: old_path.into_owned(), + old_flags, + new_fd, + new_path: new_path.into_owned(), + }), + JournalEntry::CreateSymbolicLinkV1 { + old_path, + fd, + new_path, + } => serializer.serialize_value(&JournalEntryCreateSymbolicLinkV1 { + _padding: padding(old_path.as_bytes().len() + new_path.as_bytes().len()), + old_path: old_path.into_owned(), + fd, + new_path: new_path.into_owned(), + }), + JournalEntry::UnlinkFileV1 { fd, path } => { + serializer.serialize_value(&JournalEntryUnlinkFileV1 { + fd, + _padding: padding(path.as_bytes().len()), + path: path.into_owned(), + }) + } + JournalEntry::PathRenameV1 { + old_fd, + old_path, + new_fd, + new_path, + } => serializer.serialize_value(&JournalEntryPathRenameV1 { + old_fd, + _padding: padding(old_path.as_bytes().len() + new_path.as_bytes().len()), + old_path: old_path.into_owned(), + new_fd, + new_path: new_path.into_owned(), + }), + JournalEntry::ChangeDirectoryV1 { path } => { + serializer.serialize_value(&JournalEntryChangeDirectoryV1 { + path: path.into_owned(), + }) + } + JournalEntry::EpollCreateV1 { fd } => { + serializer.serialize_value(&JournalEntryEpollCreateV1 { fd, _padding: 0 }) + } + JournalEntry::EpollCtlV1 { + epfd, + op, + fd, + event, + } => serializer.serialize_value(&JournalEntryEpollCtlV1 { + epfd, + op: op.into(), + fd, + event: event.map(|e| e.into()), + }), + JournalEntry::TtySetV1 { tty, line_feeds } => { + serializer.serialize_value(&JournalEntryTtySetV1 { + cols: tty.cols, + rows: tty.rows, + width: tty.width, + height: tty.height, + stdin_tty: tty.stdin_tty, + stdout_tty: tty.stdout_tty, + stderr_tty: tty.stderr_tty, + echo: tty.echo, + line_buffered: tty.line_buffered, + line_feeds, + }) + } + JournalEntry::CreatePipeV1 { fd1, fd2 } => { + serializer.serialize_value(&JournalEntryCreatePipeV1 { fd1, fd2 }) + } + JournalEntry::CreateEventV1 { + initial_val, + flags, + fd, + } => serializer.serialize_value(&JournalEntryCreateEventV1 { + initial_val, + flags, + fd, + }), + JournalEntry::PortAddAddrV1 { cidr } => { + serializer.serialize_value(&JournalEntryPortAddAddrV1 { cidr: cidr.into() }) + } + JournalEntry::PortDelAddrV1 { addr } => { + serializer.serialize_value(&JournalEntryPortDelAddrV1 { addr }) + } + JournalEntry::PortAddrClearV1 => return Ok(()), + JournalEntry::PortBridgeV1 { + network, + token, + security, + } => serializer.serialize_value(&JournalEntryPortBridgeV1 { + _padding: padding(network.as_bytes().len() + token.as_bytes().len()), + network: network.into_owned(), + token: token.into_owned(), + security: security.into(), + }), + JournalEntry::PortUnbridgeV1 => return Ok(()), + JournalEntry::PortDhcpAcquireV1 => return Ok(()), + JournalEntry::PortGatewaySetV1 { ip } => { + serializer.serialize_value(&JournalEntryPortGatewaySetV1 { ip }) + } + JournalEntry::PortRouteAddV1 { + cidr, + via_router, + preferred_until, + expires_at, + } => serializer.serialize_value(&JournalEntryPortRouteAddV1 { + cidr: cidr.into(), + via_router, + preferred_until, + expires_at, + }), + JournalEntry::PortRouteClearV1 => return Ok(()), + JournalEntry::PortRouteDelV1 { ip } => { + serializer.serialize_value(&JournalEntryPortRouteDelV1 { ip }) + } + JournalEntry::SocketOpenV1 { af, ty, pt, fd } => { + serializer.serialize_value(&JournalEntrySocketOpenV1 { + af: af.into(), + ty: ty.into(), + pt: pt.into(), + fd, + }) + } + JournalEntry::SocketListenV1 { fd, backlog } => { + serializer.serialize_value(&JournalEntrySocketListenV1 { fd, backlog }) + } + JournalEntry::SocketBindV1 { fd, addr } => { + serializer.serialize_value(&JournalEntrySocketBindV1 { fd, addr }) + } + JournalEntry::SocketConnectedV1 { fd, addr } => { + serializer.serialize_value(&JournalEntrySocketConnectedV1 { fd, addr }) + } + JournalEntry::SocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags, + non_blocking: nonblocking, + } => serializer.serialize_value(&JournalEntrySocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags: fd_flags.bits(), + nonblocking, + }), + JournalEntry::SocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + } => serializer.serialize_value(&JournalEntrySocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + }), + JournalEntry::SocketJoinIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => serializer.serialize_value(&JournalEntrySocketJoinIpv6MulticastV1 { + fd, + multiaddr, + iface, + }), + JournalEntry::SocketLeaveIpv4MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => serializer.serialize_value(&JournalEntrySocketLeaveIpv4MulticastV1 { + fd, + multiaddr, + iface, + }), + JournalEntry::SocketLeaveIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => serializer.serialize_value(&JournalEntrySocketLeaveIpv6MulticastV1 { + fd, + multiaddr, + iface, + }), + JournalEntry::SocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + } => serializer.serialize_value(&JournalEntrySocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + }), + JournalEntry::SocketSendToV1 { + fd, + data, + flags, + addr, + is_64bit, + } => serializer.serialize_value(&JournalEntrySocketSendToV1 { + fd, + _padding: padding(data.len()), + data: data.into_owned(), + flags, + addr, + is_64bit, + }), + JournalEntry::SocketSendV1 { + fd, + data, + flags, + is_64bit, + } => serializer.serialize_value(&JournalEntrySocketSendV1 { + fd, + _padding: padding(data.len()), + data: data.into_owned(), + flags, + is_64bit, + }), + JournalEntry::SocketSetOptFlagV1 { fd, opt, flag } => { + serializer.serialize_value(&JournalEntrySocketSetOptFlagV1 { + fd, + opt: opt.into(), + flag, + }) + } + JournalEntry::SocketSetOptSizeV1 { fd, opt, size } => { + serializer.serialize_value(&JournalEntrySocketSetOptSizeV1 { + fd, + opt: opt.into(), + size, + }) + } + JournalEntry::SocketSetOptTimeV1 { fd, ty, time } => { + serializer.serialize_value(&JournalEntrySocketSetOptTimeV1 { + fd, + ty: ty.into(), + time, + }) + } + JournalEntry::SocketShutdownV1 { fd, how } => { + serializer.serialize_value(&JournalEntrySocketShutdownV1 { + fd, + how: how.into(), + }) + } + JournalEntry::SnapshotV1 { when, trigger } => { + serializer.serialize_value(&JournalEntrySnapshotV1 { + since_epoch: when + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or(Duration::ZERO), + trigger: trigger.into(), + }) + } + } + .map_err(|err| anyhow::format_err!("failed to serialize journal record - {}", err))?; + Ok(()) + } +} + +/// The journal log entries are serializable which +/// allows them to be written directly to a file +/// +/// Note: This structure is versioned which allows for +/// changes to the journal entry types without having to +/// worry about backward and forward compatibility +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub(crate) struct JournalEntryHeader { + pub record_type: u16, + pub record_size: u64, +} + +pub enum ArchivedJournalEntry<'a> { + InitModuleV1(&'a ArchivedJournalEntryInitModuleV1), + ProcessExitV1(&'a ArchivedJournalEntryProcessExitV1), + SetThreadV1(&'a ArchivedJournalEntrySetThreadV1), + CloseThreadV1(&'a ArchivedJournalEntryCloseThreadV1), + FileDescriptorSeekV1(&'a ArchivedJournalEntryFileDescriptorSeekV1), + FileDescriptorWriteV1(&'a ArchivedJournalEntryFileDescriptorWriteV1), + UpdateMemoryRegionV1(&'a ArchivedJournalEntryUpdateMemoryRegionV1), + SetClockTimeV1(&'a ArchivedJournalEntrySetClockTimeV1), + OpenFileDescriptorV1(&'a ArchivedJournalEntryOpenFileDescriptorV1), + CloseFileDescriptorV1(&'a ArchivedJournalEntryCloseFileDescriptorV1), + RenumberFileDescriptorV1(&'a ArchivedJournalEntryRenumberFileDescriptorV1), + DuplicateFileDescriptorV1(&'a ArchivedJournalEntryDuplicateFileDescriptorV1), + CreateDirectoryV1(&'a ArchivedJournalEntryCreateDirectoryV1), + RemoveDirectoryV1(&'a ArchivedJournalEntryRemoveDirectoryV1), + PathSetTimesV1(&'a ArchivedJournalEntryPathSetTimesV1), + FileDescriptorSetTimesV1(&'a ArchivedJournalEntryFileDescriptorSetTimesV1), + FileDescriptorSetSizeV1(&'a ArchivedJournalEntryFileDescriptorSetSizeV1), + FileDescriptorSetFlagsV1(&'a ArchivedJournalEntryFileDescriptorSetFlagsV1), + FileDescriptorSetRightsV1(&'a ArchivedJournalEntryFileDescriptorSetRightsV1), + FileDescriptorAdviseV1(&'a ArchivedJournalEntryFileDescriptorAdviseV1), + FileDescriptorAllocateV1(&'a ArchivedJournalEntryFileDescriptorAllocateV1), + CreateHardLinkV1(&'a ArchivedJournalEntryCreateHardLinkV1), + CreateSymbolicLinkV1(&'a ArchivedJournalEntryCreateSymbolicLinkV1), + UnlinkFileV1(&'a ArchivedJournalEntryUnlinkFileV1), + PathRenameV1(&'a ArchivedJournalEntryPathRenameV1), + ChangeDirectoryV1(&'a ArchivedJournalEntryChangeDirectoryV1), + EpollCreateV1(&'a ArchivedJournalEntryEpollCreateV1), + EpollCtlV1(&'a ArchivedJournalEntryEpollCtlV1), + TtySetV1(&'a ArchivedJournalEntryTtySetV1), + CreatePipeV1(&'a ArchivedJournalEntryCreatePipeV1), + CreateEventV1(&'a ArchivedJournalEntryCreateEventV1), + PortAddAddrV1(&'a ArchivedJournalEntryPortAddAddrV1), + PortDelAddrV1(&'a ArchivedJournalEntryPortDelAddrV1), + PortAddrClearV1, + PortBridgeV1(&'a ArchivedJournalEntryPortBridgeV1), + PortUnbridgeV1, + PortDhcpAcquireV1, + PortGatewaySetV1(&'a ArchivedJournalEntryPortGatewaySetV1), + PortRouteAddV1(&'a ArchivedJournalEntryPortRouteAddV1), + PortRouteClearV1, + PortRouteDelV1(&'a ArchivedJournalEntryPortRouteDelV1), + SocketOpenV1(&'a ArchivedJournalEntrySocketOpenV1), + SocketListenV1(&'a ArchivedJournalEntrySocketListenV1), + SocketBindV1(&'a ArchivedJournalEntrySocketBindV1), + SocketConnectedV1(&'a ArchivedJournalEntrySocketConnectedV1), + SocketAcceptedV1(&'a ArchivedJournalEntrySocketAcceptedV1), + SocketJoinIpv4MulticastV1(&'a ArchivedJournalEntrySocketJoinIpv4MulticastV1), + SocketJoinIpv6MulticastV1(&'a ArchivedJournalEntrySocketJoinIpv6MulticastV1), + SocketLeaveIpv4MulticastV1(&'a ArchivedJournalEntrySocketLeaveIpv4MulticastV1), + SocketLeaveIpv6MulticastV1(&'a ArchivedJournalEntrySocketLeaveIpv6MulticastV1), + SocketSendFileV1(&'a ArchivedJournalEntrySocketSendFileV1), + SocketSendToV1(&'a ArchivedJournalEntrySocketSendToV1), + SocketSendV1(&'a ArchivedJournalEntrySocketSendV1), + SocketSetOptFlagV1(&'a ArchivedJournalEntrySocketSetOptFlagV1), + SocketSetOptSizeV1(&'a ArchivedJournalEntrySocketSetOptSizeV1), + SocketSetOptTimeV1(&'a ArchivedJournalEntrySocketSetOptTimeV1), + SocketShutdownV1(&'a ArchivedJournalEntrySocketShutdownV1), + SnapshotV1(&'a ArchivedJournalEntrySnapshotV1), +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryInitModuleV1 { + pub wasm_hash: [u8; 8], +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryProcessExitV1 { + pub exit_code: Option, + pub _padding: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySetThreadV1 { + pub id: u32, + pub call_stack: Vec, + pub memory_stack: Vec, + pub store_data: Vec, + pub _padding: Vec, + pub is_64bit: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCloseThreadV1 { + pub id: u32, + pub exit_code: Option, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorSeekV1 { + pub fd: u32, + pub offset: i64, + pub whence: JournalWhenceV1, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorWriteV1 { + pub fd: u32, + pub offset: u64, + pub data: Vec, + pub _padding: Vec, + pub is_64bit: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryUpdateMemoryRegionV1 { + pub start: u64, + pub end: u64, + pub compressed_data: Vec, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySetClockTimeV1 { + pub clock_id: JournalSnapshot0ClockidV1, + pub time: u64, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryOpenFileDescriptorV1 { + pub fd: u32, + pub dirfd: u32, + pub dirflags: u32, + pub path: String, + pub _padding: Vec, + pub o_flags: u16, + pub fs_rights_base: u64, + pub fs_rights_inheriting: u64, + pub fs_flags: u16, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCloseFileDescriptorV1 { + pub fd: u32, + pub _padding: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryRenumberFileDescriptorV1 { + pub old_fd: u32, + pub new_fd: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryDuplicateFileDescriptorV1 { + pub original_fd: u32, + pub copied_fd: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCreateDirectoryV1 { + pub fd: u32, + pub path: String, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryRemoveDirectoryV1 { + pub fd: u32, + pub path: String, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPathSetTimesV1 { + pub fd: u32, + pub flags: u32, + pub path: String, + pub _padding: Vec, + pub st_atim: u64, + pub st_mtim: u64, + pub fst_flags: u16, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorSetTimesV1 { + pub fd: u32, + pub st_atim: u64, + pub st_mtim: u64, + pub fst_flags: u16, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorSetSizeV1 { + pub fd: u32, + pub st_size: u64, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorSetFlagsV1 { + pub fd: u32, + pub flags: u16, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorSetRightsV1 { + pub fd: u32, + pub fs_rights_base: u64, + pub fs_rights_inheriting: u64, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorAdviseV1 { + pub fd: u32, + pub offset: u64, + pub len: u64, + pub advice: JournalAdviceV1, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryFileDescriptorAllocateV1 { + pub fd: u32, + pub offset: u64, + pub len: u64, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCreateHardLinkV1 { + pub old_fd: u32, + pub old_path: String, + pub old_flags: u32, + pub new_fd: u32, + pub new_path: String, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCreateSymbolicLinkV1 { + pub old_path: String, + pub fd: u32, + pub new_path: String, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryUnlinkFileV1 { + pub fd: u32, + pub path: String, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPathRenameV1 { + pub old_fd: u32, + pub old_path: String, + pub new_fd: u32, + pub new_path: String, + pub _padding: Vec, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryChangeDirectoryV1 { + pub path: String, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryEpollCreateV1 { + pub fd: u32, + pub _padding: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryEpollCtlV1 { + pub epfd: u32, + pub op: JournalEpollCtlV1, + pub fd: u32, + pub event: Option, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryTtySetV1 { + pub cols: u32, + pub rows: u32, + pub width: u32, + pub height: u32, + pub stdin_tty: bool, + pub stdout_tty: bool, + pub stderr_tty: bool, + pub echo: bool, + pub line_buffered: bool, + pub line_feeds: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCreatePipeV1 { + pub fd1: u32, + pub fd2: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryCreateEventV1 { + pub initial_val: u64, + pub flags: u16, + pub fd: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPortAddAddrV1 { + pub cidr: JournalIpCidrV1, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPortDelAddrV1 { + pub addr: IpAddr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPortBridgeV1 { + pub network: String, + pub token: String, + pub _padding: Vec, + pub security: JournalStreamSecurityV1, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPortGatewaySetV1 { + pub ip: IpAddr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPortRouteAddV1 { + pub cidr: JournalIpCidrV1, + pub via_router: IpAddr, + pub preferred_until: Option, + pub expires_at: Option, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntryPortRouteDelV1 { + pub ip: IpAddr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketOpenV1 { + pub af: JournalAddressfamilyV1, + pub ty: JournalSocktypeV1, + pub pt: u16, + pub fd: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketListenV1 { + pub fd: u32, + pub backlog: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketBindV1 { + pub fd: u32, + pub addr: SocketAddr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketConnectedV1 { + pub fd: u32, + pub addr: SocketAddr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketAcceptedV1 { + pub listen_fd: u32, + pub fd: u32, + pub peer_addr: SocketAddr, + pub fd_flags: u16, + pub nonblocking: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketJoinIpv4MulticastV1 { + pub fd: u32, + pub multiaddr: Ipv4Addr, + pub iface: Ipv4Addr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketJoinIpv6MulticastV1 { + pub fd: u32, + pub multiaddr: Ipv6Addr, + pub iface: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketLeaveIpv4MulticastV1 { + pub fd: u32, + pub multiaddr: Ipv4Addr, + pub iface: Ipv4Addr, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketLeaveIpv6MulticastV1 { + pub fd: u32, + pub multiaddr: Ipv6Addr, + pub iface: u32, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketSendFileV1 { + pub socket_fd: u32, + pub file_fd: u32, + pub offset: u64, + pub count: u64, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketSendToV1 { + pub fd: u32, + pub data: Vec, + pub _padding: Vec, + pub flags: u16, + pub addr: SocketAddr, + pub is_64bit: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketSendV1 { + pub fd: u32, + pub data: Vec, + pub _padding: Vec, + pub flags: u16, + pub is_64bit: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketSetOptFlagV1 { + pub fd: u32, + pub opt: JournalSockoptionV1, + pub flag: bool, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketSetOptSizeV1 { + pub fd: u32, + pub opt: JournalSockoptionV1, + pub size: u64, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketSetOptTimeV1 { + pub fd: u32, + pub ty: JournalTimeTypeV1, + pub time: Option, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySocketShutdownV1 { + pub fd: u32, + pub how: JournalSocketShutdownV1, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEntrySnapshotV1 { + pub since_epoch: Duration, + pub trigger: JournalSnapshotTriggerV1, +} + +#[repr(C)] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalSnapshot0ClockidV1 { + Realtime, + Monotonic, + ProcessCputimeId, + ThreadCputimeId, + Unknown = 255, +} + +#[repr(C)] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalWhenceV1 { + Set, + Cur, + End, + Unknown = 255, +} + +#[repr(C)] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalAdviceV1 { + Normal, + Sequential, + Random, + Willneed, + Dontneed, + Noreuse, + Unknown = 255, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalIpCidrV1 { + pub ip: IpAddr, + pub prefix: u8, +} + +#[repr(C)] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalExitCodeV1 { + Errno(u16), + Other(i32), +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalSnapshotTriggerV1 { + Idle, + Listen, + Environ, + Stdin, + Timer, + Sigint, + Sigalrm, + Sigtstp, + Sigstop, + NonDeterministicCall, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalEpollCtlV1 { + Add, + Mod, + Del, + Unknown, +} + +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Clone, RkyvSerialize, RkyvDeserialize, Archive)] +#[archive_attr(derive(CheckBytes))] +pub struct JournalEpollEventCtlV1 { + pub events: u32, + pub ptr: u64, + pub fd: u32, + pub data1: u32, + pub data2: u64, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalStreamSecurityV1 { + Unencrypted, + AnyEncryption, + ClassicEncryption, + DoubleEncryption, + Unknown, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalAddressfamilyV1 { + Unspec, + Inet4, + Inet6, + Unix, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalSocktypeV1 { + Unknown, + Stream, + Dgram, + Raw, + Seqpacket, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalSockoptionV1 { + Noop, + ReusePort, + ReuseAddr, + NoDelay, + DontRoute, + OnlyV6, + Broadcast, + MulticastLoopV4, + MulticastLoopV6, + Promiscuous, + Listening, + LastError, + KeepAlive, + Linger, + OobInline, + RecvBufSize, + SendBufSize, + RecvLowat, + SendLowat, + RecvTimeout, + SendTimeout, + ConnectTimeout, + AcceptTimeout, + Ttl, + MulticastTtlV4, + Type, + Proto, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalTimeTypeV1 { + ReadTimeout, + WriteTimeout, + AcceptTimeout, + ConnectTimeout, + BindTimeout, + Linger, +} + +#[repr(C)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + RkyvSerialize, + RkyvDeserialize, + Archive, +)] +#[archive_attr(derive(CheckBytes))] +pub enum JournalSocketShutdownV1 { + Read, + Write, + Both, +} diff --git a/lib/journal/src/concrete/archived_from.rs b/lib/journal/src/concrete/archived_from.rs new file mode 100644 index 00000000000..b37e53f3c26 --- /dev/null +++ b/lib/journal/src/concrete/archived_from.rs @@ -0,0 +1,1107 @@ +use lz4_flex::block::decompress_size_prepended; +use std::borrow::Cow; +use std::time::SystemTime; +use wasmer_wasix_types::wasi; + +use super::*; + +impl From for JournalSnapshot0ClockidV1 { + fn from(val: wasi::Snapshot0Clockid) -> Self { + match val { + wasi::Snapshot0Clockid::Realtime => JournalSnapshot0ClockidV1::Realtime, + wasi::Snapshot0Clockid::Monotonic => JournalSnapshot0ClockidV1::Monotonic, + wasi::Snapshot0Clockid::ProcessCputimeId => JournalSnapshot0ClockidV1::ProcessCputimeId, + wasi::Snapshot0Clockid::ThreadCputimeId => JournalSnapshot0ClockidV1::ThreadCputimeId, + wasi::Snapshot0Clockid::Unknown => JournalSnapshot0ClockidV1::Unknown, + } + } +} + +impl From for wasi::Snapshot0Clockid { + fn from(val: JournalSnapshot0ClockidV1) -> Self { + match val { + JournalSnapshot0ClockidV1::Realtime => wasi::Snapshot0Clockid::Realtime, + JournalSnapshot0ClockidV1::Monotonic => wasi::Snapshot0Clockid::Monotonic, + JournalSnapshot0ClockidV1::ProcessCputimeId => wasi::Snapshot0Clockid::ProcessCputimeId, + JournalSnapshot0ClockidV1::ThreadCputimeId => wasi::Snapshot0Clockid::ThreadCputimeId, + JournalSnapshot0ClockidV1::Unknown => wasi::Snapshot0Clockid::Unknown, + } + } +} + +impl From<&'_ ArchivedJournalSnapshot0ClockidV1> for wasi::Snapshot0Clockid { + fn from(val: &'_ ArchivedJournalSnapshot0ClockidV1) -> Self { + match val { + ArchivedJournalSnapshot0ClockidV1::Realtime => wasi::Snapshot0Clockid::Realtime, + ArchivedJournalSnapshot0ClockidV1::Monotonic => wasi::Snapshot0Clockid::Monotonic, + ArchivedJournalSnapshot0ClockidV1::ProcessCputimeId => { + wasi::Snapshot0Clockid::ProcessCputimeId + } + ArchivedJournalSnapshot0ClockidV1::ThreadCputimeId => { + wasi::Snapshot0Clockid::ThreadCputimeId + } + ArchivedJournalSnapshot0ClockidV1::Unknown => wasi::Snapshot0Clockid::Unknown, + } + } +} + +impl From for JournalWhenceV1 { + fn from(val: wasi::Whence) -> Self { + match val { + wasi::Whence::Set => JournalWhenceV1::Set, + wasi::Whence::Cur => JournalWhenceV1::Cur, + wasi::Whence::End => JournalWhenceV1::End, + wasi::Whence::Unknown => JournalWhenceV1::Unknown, + } + } +} + +impl From for wasi::Whence { + fn from(val: JournalWhenceV1) -> Self { + match val { + JournalWhenceV1::Set => wasi::Whence::Set, + JournalWhenceV1::Cur => wasi::Whence::Cur, + JournalWhenceV1::End => wasi::Whence::End, + JournalWhenceV1::Unknown => wasi::Whence::Unknown, + } + } +} + +impl From<&'_ ArchivedJournalWhenceV1> for wasi::Whence { + fn from(val: &'_ ArchivedJournalWhenceV1) -> Self { + match val { + ArchivedJournalWhenceV1::Set => wasi::Whence::Set, + ArchivedJournalWhenceV1::Cur => wasi::Whence::Cur, + ArchivedJournalWhenceV1::End => wasi::Whence::End, + ArchivedJournalWhenceV1::Unknown => wasi::Whence::Unknown, + } + } +} + +impl From for JournalAdviceV1 { + fn from(val: wasi::Advice) -> Self { + match val { + wasi::Advice::Normal => JournalAdviceV1::Normal, + wasi::Advice::Sequential => JournalAdviceV1::Sequential, + wasi::Advice::Random => JournalAdviceV1::Random, + wasi::Advice::Willneed => JournalAdviceV1::Willneed, + wasi::Advice::Dontneed => JournalAdviceV1::Dontneed, + wasi::Advice::Noreuse => JournalAdviceV1::Noreuse, + wasi::Advice::Unknown => JournalAdviceV1::Unknown, + } + } +} + +impl From for wasi::Advice { + fn from(val: JournalAdviceV1) -> Self { + match val { + JournalAdviceV1::Normal => wasi::Advice::Normal, + JournalAdviceV1::Sequential => wasi::Advice::Sequential, + JournalAdviceV1::Random => wasi::Advice::Random, + JournalAdviceV1::Willneed => wasi::Advice::Willneed, + JournalAdviceV1::Dontneed => wasi::Advice::Dontneed, + JournalAdviceV1::Noreuse => wasi::Advice::Noreuse, + JournalAdviceV1::Unknown => wasi::Advice::Unknown, + } + } +} + +impl From<&'_ ArchivedJournalAdviceV1> for wasi::Advice { + fn from(val: &'_ ArchivedJournalAdviceV1) -> Self { + match val { + ArchivedJournalAdviceV1::Normal => wasi::Advice::Normal, + ArchivedJournalAdviceV1::Sequential => wasi::Advice::Sequential, + ArchivedJournalAdviceV1::Random => wasi::Advice::Random, + ArchivedJournalAdviceV1::Willneed => wasi::Advice::Willneed, + ArchivedJournalAdviceV1::Dontneed => wasi::Advice::Dontneed, + ArchivedJournalAdviceV1::Noreuse => wasi::Advice::Noreuse, + ArchivedJournalAdviceV1::Unknown => wasi::Advice::Unknown, + } + } +} + +impl From for JournalIpCidrV1 { + fn from(value: virtual_net::IpCidr) -> Self { + Self { + ip: value.ip, + prefix: value.prefix, + } + } +} + +impl From for virtual_net::IpCidr { + fn from(value: JournalIpCidrV1) -> Self { + Self { + ip: value.ip, + prefix: value.prefix, + } + } +} + +impl From for JournalExitCodeV1 { + fn from(val: wasi::ExitCode) -> Self { + match val { + wasi::ExitCode::Errno(errno) => JournalExitCodeV1::Errno(errno as u16), + wasi::ExitCode::Other(id) => JournalExitCodeV1::Other(id), + } + } +} + +impl From for wasi::ExitCode { + fn from(val: JournalExitCodeV1) -> Self { + match val { + JournalExitCodeV1::Errno(errno) => { + wasi::ExitCode::Errno(errno.try_into().unwrap_or(wasi::Errno::Unknown)) + } + JournalExitCodeV1::Other(id) => wasi::ExitCode::Other(id), + } + } +} + +impl From<&'_ ArchivedJournalExitCodeV1> for wasi::ExitCode { + fn from(val: &'_ ArchivedJournalExitCodeV1) -> Self { + match val { + ArchivedJournalExitCodeV1::Errno(errno) => { + wasi::ExitCode::Errno((*errno).try_into().unwrap_or(wasi::Errno::Unknown)) + } + ArchivedJournalExitCodeV1::Other(id) => wasi::ExitCode::Other(*id), + } + } +} + +impl From for JournalSnapshotTriggerV1 { + fn from(val: SnapshotTrigger) -> Self { + match val { + SnapshotTrigger::Idle => JournalSnapshotTriggerV1::Idle, + SnapshotTrigger::FirstListen => JournalSnapshotTriggerV1::Listen, + SnapshotTrigger::FirstEnviron => JournalSnapshotTriggerV1::Environ, + SnapshotTrigger::FirstStdin => JournalSnapshotTriggerV1::Stdin, + SnapshotTrigger::PeriodicInterval => JournalSnapshotTriggerV1::Timer, + SnapshotTrigger::Sigint => JournalSnapshotTriggerV1::Sigint, + SnapshotTrigger::Sigalrm => JournalSnapshotTriggerV1::Sigalrm, + SnapshotTrigger::Sigtstp => JournalSnapshotTriggerV1::Sigtstp, + SnapshotTrigger::Sigstop => JournalSnapshotTriggerV1::Sigstop, + SnapshotTrigger::NonDeterministicCall => JournalSnapshotTriggerV1::NonDeterministicCall, + } + } +} + +impl From for SnapshotTrigger { + fn from(val: JournalSnapshotTriggerV1) -> Self { + match val { + JournalSnapshotTriggerV1::Idle => SnapshotTrigger::Idle, + JournalSnapshotTriggerV1::Listen => SnapshotTrigger::FirstListen, + JournalSnapshotTriggerV1::Environ => SnapshotTrigger::FirstEnviron, + JournalSnapshotTriggerV1::Stdin => SnapshotTrigger::FirstStdin, + JournalSnapshotTriggerV1::Timer => SnapshotTrigger::PeriodicInterval, + JournalSnapshotTriggerV1::Sigint => SnapshotTrigger::Sigint, + JournalSnapshotTriggerV1::Sigalrm => SnapshotTrigger::Sigalrm, + JournalSnapshotTriggerV1::Sigtstp => SnapshotTrigger::Sigtstp, + JournalSnapshotTriggerV1::Sigstop => SnapshotTrigger::Sigstop, + JournalSnapshotTriggerV1::NonDeterministicCall => SnapshotTrigger::NonDeterministicCall, + } + } +} + +impl From<&'_ ArchivedJournalSnapshotTriggerV1> for SnapshotTrigger { + fn from(val: &'_ ArchivedJournalSnapshotTriggerV1) -> Self { + match val { + ArchivedJournalSnapshotTriggerV1::Idle => SnapshotTrigger::Idle, + ArchivedJournalSnapshotTriggerV1::Listen => SnapshotTrigger::FirstListen, + ArchivedJournalSnapshotTriggerV1::Environ => SnapshotTrigger::FirstEnviron, + ArchivedJournalSnapshotTriggerV1::Stdin => SnapshotTrigger::FirstStdin, + ArchivedJournalSnapshotTriggerV1::Timer => SnapshotTrigger::PeriodicInterval, + ArchivedJournalSnapshotTriggerV1::Sigint => SnapshotTrigger::Sigint, + ArchivedJournalSnapshotTriggerV1::Sigalrm => SnapshotTrigger::Sigalrm, + ArchivedJournalSnapshotTriggerV1::Sigtstp => SnapshotTrigger::Sigtstp, + ArchivedJournalSnapshotTriggerV1::Sigstop => SnapshotTrigger::Sigstop, + ArchivedJournalSnapshotTriggerV1::NonDeterministicCall => { + SnapshotTrigger::NonDeterministicCall + } + } + } +} + +impl From for JournalEpollCtlV1 { + fn from(val: wasi::EpollCtl) -> Self { + match val { + wasi::EpollCtl::Add => JournalEpollCtlV1::Add, + wasi::EpollCtl::Mod => JournalEpollCtlV1::Mod, + wasi::EpollCtl::Del => JournalEpollCtlV1::Del, + wasi::EpollCtl::Unknown => JournalEpollCtlV1::Unknown, + } + } +} + +impl From for wasi::EpollCtl { + fn from(val: JournalEpollCtlV1) -> Self { + match val { + JournalEpollCtlV1::Add => wasi::EpollCtl::Add, + JournalEpollCtlV1::Mod => wasi::EpollCtl::Mod, + JournalEpollCtlV1::Del => wasi::EpollCtl::Del, + JournalEpollCtlV1::Unknown => wasi::EpollCtl::Unknown, + } + } +} + +impl From<&'_ ArchivedJournalEpollCtlV1> for wasi::EpollCtl { + fn from(val: &'_ ArchivedJournalEpollCtlV1) -> Self { + match val { + ArchivedJournalEpollCtlV1::Add => wasi::EpollCtl::Add, + ArchivedJournalEpollCtlV1::Mod => wasi::EpollCtl::Mod, + ArchivedJournalEpollCtlV1::Del => wasi::EpollCtl::Del, + ArchivedJournalEpollCtlV1::Unknown => wasi::EpollCtl::Unknown, + } + } +} + +impl From for JournalEpollEventCtlV1 { + fn from(val: wasi::EpollEventCtl) -> Self { + JournalEpollEventCtlV1 { + events: val.events.bits(), + ptr: val.ptr, + fd: val.fd, + data1: val.data1, + data2: val.data2, + } + } +} + +impl From for wasi::EpollEventCtl { + fn from(val: JournalEpollEventCtlV1) -> Self { + Self { + events: wasi::EpollType::from_bits_truncate(val.events), + ptr: val.ptr, + fd: val.fd, + data1: val.data1, + data2: val.data2, + } + } +} + +impl From<&'_ ArchivedJournalEpollEventCtlV1> for wasi::EpollEventCtl { + fn from(val: &'_ ArchivedJournalEpollEventCtlV1) -> Self { + Self { + events: wasi::EpollType::from_bits_truncate(val.events), + ptr: val.ptr, + fd: val.fd, + data1: val.data1, + data2: val.data2, + } + } +} + +impl From for JournalStreamSecurityV1 { + fn from(val: virtual_net::StreamSecurity) -> Self { + use virtual_net::StreamSecurity; + match val { + StreamSecurity::Unencrypted => JournalStreamSecurityV1::Unencrypted, + StreamSecurity::AnyEncyption => JournalStreamSecurityV1::AnyEncryption, + StreamSecurity::ClassicEncryption => JournalStreamSecurityV1::ClassicEncryption, + StreamSecurity::DoubleEncryption => JournalStreamSecurityV1::DoubleEncryption, + } + } +} + +impl From for virtual_net::StreamSecurity { + fn from(val: JournalStreamSecurityV1) -> Self { + use virtual_net::StreamSecurity; + match val { + JournalStreamSecurityV1::Unencrypted => StreamSecurity::Unencrypted, + JournalStreamSecurityV1::AnyEncryption => StreamSecurity::AnyEncyption, + JournalStreamSecurityV1::ClassicEncryption => StreamSecurity::ClassicEncryption, + JournalStreamSecurityV1::DoubleEncryption => StreamSecurity::DoubleEncryption, + JournalStreamSecurityV1::Unknown => StreamSecurity::AnyEncyption, + } + } +} + +impl From<&'_ ArchivedJournalStreamSecurityV1> for virtual_net::StreamSecurity { + fn from(val: &'_ ArchivedJournalStreamSecurityV1) -> Self { + use virtual_net::StreamSecurity; + match val { + ArchivedJournalStreamSecurityV1::Unencrypted => StreamSecurity::Unencrypted, + ArchivedJournalStreamSecurityV1::AnyEncryption => StreamSecurity::AnyEncyption, + ArchivedJournalStreamSecurityV1::ClassicEncryption => StreamSecurity::ClassicEncryption, + ArchivedJournalStreamSecurityV1::DoubleEncryption => StreamSecurity::DoubleEncryption, + ArchivedJournalStreamSecurityV1::Unknown => StreamSecurity::AnyEncyption, + } + } +} + +impl From for JournalAddressfamilyV1 { + fn from(val: wasi::Addressfamily) -> Self { + match val { + wasi::Addressfamily::Unspec => JournalAddressfamilyV1::Unspec, + wasi::Addressfamily::Inet4 => JournalAddressfamilyV1::Inet4, + wasi::Addressfamily::Inet6 => JournalAddressfamilyV1::Inet6, + wasi::Addressfamily::Unix => JournalAddressfamilyV1::Unix, + } + } +} + +impl From for wasi::Addressfamily { + fn from(val: JournalAddressfamilyV1) -> Self { + match val { + JournalAddressfamilyV1::Unspec => wasi::Addressfamily::Unspec, + JournalAddressfamilyV1::Inet4 => wasi::Addressfamily::Inet4, + JournalAddressfamilyV1::Inet6 => wasi::Addressfamily::Inet6, + JournalAddressfamilyV1::Unix => wasi::Addressfamily::Unix, + } + } +} + +impl From<&'_ ArchivedJournalAddressfamilyV1> for wasi::Addressfamily { + fn from(val: &'_ ArchivedJournalAddressfamilyV1) -> Self { + match val { + ArchivedJournalAddressfamilyV1::Unspec => wasi::Addressfamily::Unspec, + ArchivedJournalAddressfamilyV1::Inet4 => wasi::Addressfamily::Inet4, + ArchivedJournalAddressfamilyV1::Inet6 => wasi::Addressfamily::Inet6, + ArchivedJournalAddressfamilyV1::Unix => wasi::Addressfamily::Unix, + } + } +} + +impl From for JournalSocktypeV1 { + fn from(val: wasi::Socktype) -> Self { + match val { + wasi::Socktype::Stream => JournalSocktypeV1::Stream, + wasi::Socktype::Dgram => JournalSocktypeV1::Dgram, + wasi::Socktype::Raw => JournalSocktypeV1::Raw, + wasi::Socktype::Seqpacket => JournalSocktypeV1::Seqpacket, + wasi::Socktype::Unknown => JournalSocktypeV1::Unknown, + } + } +} + +impl From for wasi::Socktype { + fn from(val: JournalSocktypeV1) -> Self { + match val { + JournalSocktypeV1::Stream => wasi::Socktype::Stream, + JournalSocktypeV1::Dgram => wasi::Socktype::Dgram, + JournalSocktypeV1::Raw => wasi::Socktype::Raw, + JournalSocktypeV1::Seqpacket => wasi::Socktype::Seqpacket, + JournalSocktypeV1::Unknown => wasi::Socktype::Unknown, + } + } +} + +impl From<&'_ ArchivedJournalSocktypeV1> for wasi::Socktype { + fn from(val: &'_ ArchivedJournalSocktypeV1) -> Self { + match val { + ArchivedJournalSocktypeV1::Stream => wasi::Socktype::Stream, + ArchivedJournalSocktypeV1::Dgram => wasi::Socktype::Dgram, + ArchivedJournalSocktypeV1::Raw => wasi::Socktype::Raw, + ArchivedJournalSocktypeV1::Seqpacket => wasi::Socktype::Seqpacket, + ArchivedJournalSocktypeV1::Unknown => wasi::Socktype::Unknown, + } + } +} + +impl From for JournalSockoptionV1 { + fn from(val: wasi::Sockoption) -> Self { + match val { + wasi::Sockoption::Noop => JournalSockoptionV1::Noop, + wasi::Sockoption::ReusePort => JournalSockoptionV1::ReusePort, + wasi::Sockoption::ReuseAddr => JournalSockoptionV1::ReuseAddr, + wasi::Sockoption::NoDelay => JournalSockoptionV1::NoDelay, + wasi::Sockoption::DontRoute => JournalSockoptionV1::DontRoute, + wasi::Sockoption::OnlyV6 => JournalSockoptionV1::OnlyV6, + wasi::Sockoption::Broadcast => JournalSockoptionV1::Broadcast, + wasi::Sockoption::MulticastLoopV4 => JournalSockoptionV1::MulticastLoopV4, + wasi::Sockoption::MulticastLoopV6 => JournalSockoptionV1::MulticastLoopV6, + wasi::Sockoption::Promiscuous => JournalSockoptionV1::Promiscuous, + wasi::Sockoption::Listening => JournalSockoptionV1::Listening, + wasi::Sockoption::LastError => JournalSockoptionV1::LastError, + wasi::Sockoption::KeepAlive => JournalSockoptionV1::KeepAlive, + wasi::Sockoption::Linger => JournalSockoptionV1::Linger, + wasi::Sockoption::OobInline => JournalSockoptionV1::OobInline, + wasi::Sockoption::RecvBufSize => JournalSockoptionV1::RecvBufSize, + wasi::Sockoption::SendBufSize => JournalSockoptionV1::SendBufSize, + wasi::Sockoption::RecvLowat => JournalSockoptionV1::RecvLowat, + wasi::Sockoption::SendLowat => JournalSockoptionV1::SendLowat, + wasi::Sockoption::RecvTimeout => JournalSockoptionV1::RecvTimeout, + wasi::Sockoption::SendTimeout => JournalSockoptionV1::SendTimeout, + wasi::Sockoption::ConnectTimeout => JournalSockoptionV1::ConnectTimeout, + wasi::Sockoption::AcceptTimeout => JournalSockoptionV1::AcceptTimeout, + wasi::Sockoption::Ttl => JournalSockoptionV1::Ttl, + wasi::Sockoption::MulticastTtlV4 => JournalSockoptionV1::MulticastTtlV4, + wasi::Sockoption::Type => JournalSockoptionV1::Type, + wasi::Sockoption::Proto => JournalSockoptionV1::Proto, + } + } +} + +impl From for wasi::Sockoption { + fn from(val: JournalSockoptionV1) -> Self { + match val { + JournalSockoptionV1::Noop => wasi::Sockoption::Noop, + JournalSockoptionV1::ReusePort => wasi::Sockoption::ReusePort, + JournalSockoptionV1::ReuseAddr => wasi::Sockoption::ReuseAddr, + JournalSockoptionV1::NoDelay => wasi::Sockoption::NoDelay, + JournalSockoptionV1::DontRoute => wasi::Sockoption::DontRoute, + JournalSockoptionV1::OnlyV6 => wasi::Sockoption::OnlyV6, + JournalSockoptionV1::Broadcast => wasi::Sockoption::Broadcast, + JournalSockoptionV1::MulticastLoopV4 => wasi::Sockoption::MulticastLoopV4, + JournalSockoptionV1::MulticastLoopV6 => wasi::Sockoption::MulticastLoopV6, + JournalSockoptionV1::Promiscuous => wasi::Sockoption::Promiscuous, + JournalSockoptionV1::Listening => wasi::Sockoption::Listening, + JournalSockoptionV1::LastError => wasi::Sockoption::LastError, + JournalSockoptionV1::KeepAlive => wasi::Sockoption::KeepAlive, + JournalSockoptionV1::Linger => wasi::Sockoption::Linger, + JournalSockoptionV1::OobInline => wasi::Sockoption::OobInline, + JournalSockoptionV1::RecvBufSize => wasi::Sockoption::RecvBufSize, + JournalSockoptionV1::SendBufSize => wasi::Sockoption::SendBufSize, + JournalSockoptionV1::RecvLowat => wasi::Sockoption::RecvLowat, + JournalSockoptionV1::SendLowat => wasi::Sockoption::SendLowat, + JournalSockoptionV1::RecvTimeout => wasi::Sockoption::RecvTimeout, + JournalSockoptionV1::SendTimeout => wasi::Sockoption::SendTimeout, + JournalSockoptionV1::ConnectTimeout => wasi::Sockoption::ConnectTimeout, + JournalSockoptionV1::AcceptTimeout => wasi::Sockoption::AcceptTimeout, + JournalSockoptionV1::Ttl => wasi::Sockoption::Ttl, + JournalSockoptionV1::MulticastTtlV4 => wasi::Sockoption::MulticastTtlV4, + JournalSockoptionV1::Type => wasi::Sockoption::Type, + JournalSockoptionV1::Proto => wasi::Sockoption::Proto, + } + } +} + +impl From<&'_ ArchivedJournalSockoptionV1> for wasi::Sockoption { + fn from(val: &'_ ArchivedJournalSockoptionV1) -> Self { + match val { + ArchivedJournalSockoptionV1::Noop => wasi::Sockoption::Noop, + ArchivedJournalSockoptionV1::ReusePort => wasi::Sockoption::ReusePort, + ArchivedJournalSockoptionV1::ReuseAddr => wasi::Sockoption::ReuseAddr, + ArchivedJournalSockoptionV1::NoDelay => wasi::Sockoption::NoDelay, + ArchivedJournalSockoptionV1::DontRoute => wasi::Sockoption::DontRoute, + ArchivedJournalSockoptionV1::OnlyV6 => wasi::Sockoption::OnlyV6, + ArchivedJournalSockoptionV1::Broadcast => wasi::Sockoption::Broadcast, + ArchivedJournalSockoptionV1::MulticastLoopV4 => wasi::Sockoption::MulticastLoopV4, + ArchivedJournalSockoptionV1::MulticastLoopV6 => wasi::Sockoption::MulticastLoopV6, + ArchivedJournalSockoptionV1::Promiscuous => wasi::Sockoption::Promiscuous, + ArchivedJournalSockoptionV1::Listening => wasi::Sockoption::Listening, + ArchivedJournalSockoptionV1::LastError => wasi::Sockoption::LastError, + ArchivedJournalSockoptionV1::KeepAlive => wasi::Sockoption::KeepAlive, + ArchivedJournalSockoptionV1::Linger => wasi::Sockoption::Linger, + ArchivedJournalSockoptionV1::OobInline => wasi::Sockoption::OobInline, + ArchivedJournalSockoptionV1::RecvBufSize => wasi::Sockoption::RecvBufSize, + ArchivedJournalSockoptionV1::SendBufSize => wasi::Sockoption::SendBufSize, + ArchivedJournalSockoptionV1::RecvLowat => wasi::Sockoption::RecvLowat, + ArchivedJournalSockoptionV1::SendLowat => wasi::Sockoption::SendLowat, + ArchivedJournalSockoptionV1::RecvTimeout => wasi::Sockoption::RecvTimeout, + ArchivedJournalSockoptionV1::SendTimeout => wasi::Sockoption::SendTimeout, + ArchivedJournalSockoptionV1::ConnectTimeout => wasi::Sockoption::ConnectTimeout, + ArchivedJournalSockoptionV1::AcceptTimeout => wasi::Sockoption::AcceptTimeout, + ArchivedJournalSockoptionV1::Ttl => wasi::Sockoption::Ttl, + ArchivedJournalSockoptionV1::MulticastTtlV4 => wasi::Sockoption::MulticastTtlV4, + ArchivedJournalSockoptionV1::Type => wasi::Sockoption::Type, + ArchivedJournalSockoptionV1::Proto => wasi::Sockoption::Proto, + } + } +} + +impl From for JournalTimeTypeV1 { + fn from(val: SocketOptTimeType) -> Self { + match val { + SocketOptTimeType::ReadTimeout => JournalTimeTypeV1::ReadTimeout, + SocketOptTimeType::WriteTimeout => JournalTimeTypeV1::WriteTimeout, + SocketOptTimeType::AcceptTimeout => JournalTimeTypeV1::AcceptTimeout, + SocketOptTimeType::ConnectTimeout => JournalTimeTypeV1::ConnectTimeout, + SocketOptTimeType::BindTimeout => JournalTimeTypeV1::BindTimeout, + SocketOptTimeType::Linger => JournalTimeTypeV1::Linger, + } + } +} + +impl From for SocketOptTimeType { + fn from(val: JournalTimeTypeV1) -> Self { + match val { + JournalTimeTypeV1::ReadTimeout => SocketOptTimeType::ReadTimeout, + JournalTimeTypeV1::WriteTimeout => SocketOptTimeType::WriteTimeout, + JournalTimeTypeV1::AcceptTimeout => SocketOptTimeType::AcceptTimeout, + JournalTimeTypeV1::ConnectTimeout => SocketOptTimeType::ConnectTimeout, + JournalTimeTypeV1::BindTimeout => SocketOptTimeType::BindTimeout, + JournalTimeTypeV1::Linger => SocketOptTimeType::Linger, + } + } +} + +impl From<&'_ ArchivedJournalTimeTypeV1> for SocketOptTimeType { + fn from(val: &'_ ArchivedJournalTimeTypeV1) -> Self { + match val { + ArchivedJournalTimeTypeV1::ReadTimeout => SocketOptTimeType::ReadTimeout, + ArchivedJournalTimeTypeV1::WriteTimeout => SocketOptTimeType::WriteTimeout, + ArchivedJournalTimeTypeV1::AcceptTimeout => SocketOptTimeType::AcceptTimeout, + ArchivedJournalTimeTypeV1::ConnectTimeout => SocketOptTimeType::ConnectTimeout, + ArchivedJournalTimeTypeV1::BindTimeout => SocketOptTimeType::BindTimeout, + ArchivedJournalTimeTypeV1::Linger => SocketOptTimeType::Linger, + } + } +} + +impl From for JournalSocketShutdownV1 { + fn from(val: SocketShutdownHow) -> Self { + match val { + SocketShutdownHow::Read => JournalSocketShutdownV1::Read, + SocketShutdownHow::Write => JournalSocketShutdownV1::Write, + SocketShutdownHow::Both => JournalSocketShutdownV1::Both, + } + } +} + +impl From for SocketShutdownHow { + fn from(val: JournalSocketShutdownV1) -> Self { + match val { + JournalSocketShutdownV1::Read => SocketShutdownHow::Read, + JournalSocketShutdownV1::Write => SocketShutdownHow::Write, + JournalSocketShutdownV1::Both => SocketShutdownHow::Both, + } + } +} + +impl From<&'_ ArchivedJournalSocketShutdownV1> for SocketShutdownHow { + fn from(val: &'_ ArchivedJournalSocketShutdownV1) -> Self { + match val { + ArchivedJournalSocketShutdownV1::Read => SocketShutdownHow::Read, + ArchivedJournalSocketShutdownV1::Write => SocketShutdownHow::Write, + ArchivedJournalSocketShutdownV1::Both => SocketShutdownHow::Both, + } + } +} + +impl<'a> TryFrom> for JournalEntry<'a> { + type Error = anyhow::Error; + + fn try_from(value: ArchivedJournalEntry<'a>) -> anyhow::Result { + Ok(match value { + ArchivedJournalEntry::InitModuleV1(ArchivedJournalEntryInitModuleV1 { wasm_hash }) => { + Self::InitModuleV1 { + wasm_hash: *wasm_hash, + } + } + ArchivedJournalEntry::UpdateMemoryRegionV1( + ArchivedJournalEntryUpdateMemoryRegionV1 { + start, + end, + compressed_data, + _padding: _, + }, + ) => Self::UpdateMemoryRegionV1 { + region: (*start)..(*end), + data: Cow::Owned(decompress_size_prepended(compressed_data.as_ref())?), + }, + ArchivedJournalEntry::ProcessExitV1(ArchivedJournalEntryProcessExitV1 { + exit_code, + _padding: _, + }) => Self::ProcessExitV1 { + exit_code: exit_code.as_ref().map(|code| code.into()), + }, + ArchivedJournalEntry::SetThreadV1(ArchivedJournalEntrySetThreadV1 { + id, + call_stack, + memory_stack, + store_data, + _padding: _, + is_64bit, + }) => Self::SetThreadV1 { + id: *id, + call_stack: call_stack.as_ref().into(), + memory_stack: memory_stack.as_ref().into(), + store_data: store_data.as_ref().into(), + is_64bit: *is_64bit, + }, + ArchivedJournalEntry::CloseThreadV1(ArchivedJournalEntryCloseThreadV1 { + id, + exit_code, + }) => Self::CloseThreadV1 { + id: *id, + exit_code: exit_code.as_ref().map(|code| code.into()), + }, + ArchivedJournalEntry::FileDescriptorWriteV1( + ArchivedJournalEntryFileDescriptorWriteV1 { + data, + fd, + offset, + is_64bit, + _padding: _, + }, + ) => Self::FileDescriptorWriteV1 { + data: data.as_ref().into(), + fd: *fd, + offset: *offset, + is_64bit: *is_64bit, + }, + ArchivedJournalEntry::FileDescriptorSeekV1( + ArchivedJournalEntryFileDescriptorSeekV1 { + fd, + offset, + ref whence, + }, + ) => Self::FileDescriptorSeekV1 { + fd: *fd, + offset: *offset, + whence: whence.into(), + }, + ArchivedJournalEntry::OpenFileDescriptorV1( + ArchivedJournalEntryOpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + path, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + _padding: _, + }, + ) => Self::OpenFileDescriptorV1 { + fd: *fd, + dirfd: *dirfd, + dirflags: *dirflags, + path: path.as_ref().into(), + o_flags: wasi::Oflags::from_bits_truncate(*o_flags), + fs_rights_base: wasi::Rights::from_bits_truncate(*fs_rights_base), + fs_rights_inheriting: wasi::Rights::from_bits_truncate(*fs_rights_inheriting), + fs_flags: wasi::Fdflags::from_bits_truncate(*fs_flags), + }, + ArchivedJournalEntry::CloseFileDescriptorV1( + ArchivedJournalEntryCloseFileDescriptorV1 { fd, _padding: _ }, + ) => Self::CloseFileDescriptorV1 { fd: *fd }, + ArchivedJournalEntry::RemoveDirectoryV1(ArchivedJournalEntryRemoveDirectoryV1 { + fd, + path, + _padding: _, + }) => Self::RemoveDirectoryV1 { + fd: *fd, + path: path.as_ref().into(), + }, + ArchivedJournalEntry::UnlinkFileV1(ArchivedJournalEntryUnlinkFileV1 { + fd, + path, + _padding: _, + }) => Self::UnlinkFileV1 { + fd: *fd, + path: path.as_ref().into(), + }, + ArchivedJournalEntry::PathRenameV1(ArchivedJournalEntryPathRenameV1 { + old_fd, + old_path, + new_fd, + new_path, + _padding: _, + }) => Self::PathRenameV1 { + old_fd: *old_fd, + old_path: old_path.as_ref().into(), + new_fd: *new_fd, + new_path: new_path.as_ref().into(), + }, + ArchivedJournalEntry::SnapshotV1(ArchivedJournalEntrySnapshotV1 { + since_epoch, + ref trigger, + }) => Self::SnapshotV1 { + when: SystemTime::UNIX_EPOCH + .checked_add((*since_epoch).try_into().unwrap()) + .unwrap_or(SystemTime::UNIX_EPOCH), + trigger: trigger.into(), + }, + ArchivedJournalEntry::SetClockTimeV1(ArchivedJournalEntrySetClockTimeV1 { + ref clock_id, + time, + }) => Self::SetClockTimeV1 { + clock_id: clock_id.into(), + time: *time, + }, + ArchivedJournalEntry::RenumberFileDescriptorV1( + ArchivedJournalEntryRenumberFileDescriptorV1 { old_fd, new_fd }, + ) => Self::RenumberFileDescriptorV1 { + old_fd: *old_fd, + new_fd: *new_fd, + }, + ArchivedJournalEntry::DuplicateFileDescriptorV1( + ArchivedJournalEntryDuplicateFileDescriptorV1 { + original_fd: old_fd, + copied_fd: new_fd, + }, + ) => Self::DuplicateFileDescriptorV1 { + original_fd: *old_fd, + copied_fd: *new_fd, + }, + ArchivedJournalEntry::CreateDirectoryV1(ArchivedJournalEntryCreateDirectoryV1 { + fd, + path, + _padding: _, + }) => Self::CreateDirectoryV1 { + fd: *fd, + path: path.as_ref().into(), + }, + ArchivedJournalEntry::PathSetTimesV1(ArchivedJournalEntryPathSetTimesV1 { + fd, + path, + flags, + st_atim, + st_mtim, + fst_flags, + _padding: _, + }) => Self::PathSetTimesV1 { + fd: *fd, + path: path.as_ref().into(), + flags: *flags, + st_atim: *st_atim, + st_mtim: *st_mtim, + fst_flags: wasi::Fstflags::from_bits_truncate(*fst_flags), + }, + ArchivedJournalEntry::FileDescriptorSetTimesV1( + ArchivedJournalEntryFileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags, + }, + ) => Self::FileDescriptorSetTimesV1 { + fd: *fd, + st_atim: *st_atim, + st_mtim: *st_mtim, + fst_flags: wasi::Fstflags::from_bits_truncate(*fst_flags), + }, + ArchivedJournalEntry::FileDescriptorSetSizeV1( + ArchivedJournalEntryFileDescriptorSetSizeV1 { fd, st_size }, + ) => Self::FileDescriptorSetSizeV1 { + fd: *fd, + st_size: *st_size, + }, + ArchivedJournalEntry::FileDescriptorSetFlagsV1( + ArchivedJournalEntryFileDescriptorSetFlagsV1 { fd, flags }, + ) => Self::FileDescriptorSetFlagsV1 { + fd: *fd, + flags: wasi::Fdflags::from_bits_truncate(*flags), + }, + ArchivedJournalEntry::FileDescriptorSetRightsV1( + ArchivedJournalEntryFileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + }, + ) => Self::FileDescriptorSetRightsV1 { + fd: *fd, + fs_rights_base: wasi::Rights::from_bits_truncate(*fs_rights_base), + fs_rights_inheriting: wasi::Rights::from_bits_truncate(*fs_rights_inheriting), + }, + ArchivedJournalEntry::FileDescriptorAdviseV1( + ArchivedJournalEntryFileDescriptorAdviseV1 { + fd, + offset, + len, + ref advice, + }, + ) => Self::FileDescriptorAdviseV1 { + fd: *fd, + offset: *offset, + len: *len, + advice: advice.into(), + }, + ArchivedJournalEntry::FileDescriptorAllocateV1( + ArchivedJournalEntryFileDescriptorAllocateV1 { fd, offset, len }, + ) => Self::FileDescriptorAllocateV1 { + fd: *fd, + offset: *offset, + len: *len, + }, + ArchivedJournalEntry::CreateHardLinkV1(ArchivedJournalEntryCreateHardLinkV1 { + old_fd, + old_path, + old_flags, + new_fd, + new_path, + _padding: _, + }) => Self::CreateHardLinkV1 { + old_fd: *old_fd, + old_path: old_path.as_ref().into(), + old_flags: *old_flags, + new_fd: *new_fd, + new_path: new_path.as_ref().into(), + }, + ArchivedJournalEntry::CreateSymbolicLinkV1( + ArchivedJournalEntryCreateSymbolicLinkV1 { + old_path, + fd, + new_path, + _padding: _, + }, + ) => Self::CreateSymbolicLinkV1 { + old_path: old_path.as_ref().into(), + fd: *fd, + new_path: new_path.as_ref().into(), + }, + ArchivedJournalEntry::ChangeDirectoryV1(ArchivedJournalEntryChangeDirectoryV1 { + path, + }) => Self::ChangeDirectoryV1 { + path: path.as_ref().into(), + }, + ArchivedJournalEntry::EpollCreateV1(ArchivedJournalEntryEpollCreateV1 { + fd, + _padding: _, + }) => Self::EpollCreateV1 { fd: *fd }, + ArchivedJournalEntry::EpollCtlV1(ArchivedJournalEntryEpollCtlV1 { + epfd, + ref op, + fd, + ref event, + }) => Self::EpollCtlV1 { + epfd: *epfd, + op: op.into(), + fd: *fd, + event: event.as_ref().map(|e| e.into()), + }, + ArchivedJournalEntry::TtySetV1(ArchivedJournalEntryTtySetV1 { + cols, + rows, + width, + height, + stdin_tty, + stdout_tty, + stderr_tty, + echo, + line_buffered, + line_feeds, + }) => Self::TtySetV1 { + tty: wasi::Tty { + cols: *cols, + rows: *rows, + width: *width, + height: *height, + stdin_tty: *stdin_tty, + stdout_tty: *stdout_tty, + stderr_tty: *stderr_tty, + echo: *echo, + line_buffered: *line_buffered, + }, + line_feeds: *line_feeds, + }, + ArchivedJournalEntry::CreatePipeV1(ArchivedJournalEntryCreatePipeV1 { fd1, fd2 }) => { + Self::CreatePipeV1 { + fd1: *fd1, + fd2: *fd2, + } + } + ArchivedJournalEntry::PortAddAddrV1(ArchivedJournalEntryPortAddAddrV1 { cidr }) => { + Self::PortAddAddrV1 { + cidr: JournalIpCidrV1 { + ip: cidr.ip.as_ipaddr(), + prefix: cidr.prefix, + } + .into(), + } + } + ArchivedJournalEntry::PortDelAddrV1(ArchivedJournalEntryPortDelAddrV1 { addr }) => { + Self::PortDelAddrV1 { + addr: addr.as_ipaddr(), + } + } + ArchivedJournalEntry::PortAddrClearV1 => Self::PortAddrClearV1, + ArchivedJournalEntry::PortBridgeV1(ArchivedJournalEntryPortBridgeV1 { + network, + token, + ref security, + _padding: _, + }) => Self::PortBridgeV1 { + network: network.as_ref().into(), + token: token.as_ref().into(), + security: security.into(), + }, + ArchivedJournalEntry::PortUnbridgeV1 => Self::PortUnbridgeV1, + ArchivedJournalEntry::PortDhcpAcquireV1 => Self::PortDhcpAcquireV1, + ArchivedJournalEntry::PortGatewaySetV1(ArchivedJournalEntryPortGatewaySetV1 { ip }) => { + Self::PortGatewaySetV1 { ip: ip.as_ipaddr() } + } + ArchivedJournalEntry::PortRouteAddV1(ArchivedJournalEntryPortRouteAddV1 { + cidr, + via_router, + preferred_until, + expires_at, + }) => Self::PortRouteAddV1 { + cidr: JournalIpCidrV1 { + ip: cidr.ip.as_ipaddr(), + prefix: cidr.prefix, + } + .into(), + via_router: via_router.as_ipaddr(), + preferred_until: preferred_until + .as_ref() + .map(|time| (*time).try_into().unwrap()), + expires_at: expires_at.as_ref().map(|time| (*time).try_into().unwrap()), + }, + ArchivedJournalEntry::PortRouteClearV1 => Self::PortRouteClearV1, + ArchivedJournalEntry::PortRouteDelV1(ArchivedJournalEntryPortRouteDelV1 { ip }) => { + Self::PortRouteDelV1 { ip: ip.as_ipaddr() } + } + ArchivedJournalEntry::SocketOpenV1(ArchivedJournalEntrySocketOpenV1 { + ref af, + ref ty, + pt, + fd, + }) => Self::SocketOpenV1 { + af: af.into(), + ty: ty.into(), + pt: (*pt).try_into().unwrap_or(wasi::SockProto::Max), + fd: *fd, + }, + ArchivedJournalEntry::SocketListenV1(ArchivedJournalEntrySocketListenV1 { + fd, + backlog, + }) => Self::SocketListenV1 { + fd: *fd, + backlog: *backlog, + }, + ArchivedJournalEntry::SocketBindV1(ArchivedJournalEntrySocketBindV1 { fd, addr }) => { + Self::SocketBindV1 { + fd: *fd, + addr: addr.as_socket_addr(), + } + } + ArchivedJournalEntry::SocketConnectedV1(ArchivedJournalEntrySocketConnectedV1 { + fd, + addr, + }) => Self::SocketConnectedV1 { + fd: *fd, + addr: addr.as_socket_addr(), + }, + ArchivedJournalEntry::SocketAcceptedV1(ArchivedJournalEntrySocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags, + nonblocking, + }) => Self::SocketAcceptedV1 { + listen_fd: *listen_fd, + fd: *fd, + peer_addr: peer_addr.as_socket_addr(), + fd_flags: wasi::Fdflags::from_bits_truncate(*fd_flags), + non_blocking: *nonblocking, + }, + ArchivedJournalEntry::SocketJoinIpv4MulticastV1( + ArchivedJournalEntrySocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + }, + ) => Self::SocketJoinIpv4MulticastV1 { + fd: *fd, + multiaddr: multiaddr.as_ipv4(), + iface: iface.as_ipv4(), + }, + ArchivedJournalEntry::SocketJoinIpv6MulticastV1( + ArchivedJournalEntrySocketJoinIpv6MulticastV1 { + fd, + multiaddr, + iface, + }, + ) => Self::SocketJoinIpv6MulticastV1 { + fd: *fd, + multi_addr: multiaddr.as_ipv6(), + iface: *iface, + }, + ArchivedJournalEntry::SocketLeaveIpv4MulticastV1( + ArchivedJournalEntrySocketLeaveIpv4MulticastV1 { + fd, + multiaddr, + iface, + }, + ) => Self::SocketLeaveIpv4MulticastV1 { + fd: *fd, + multi_addr: multiaddr.as_ipv4(), + iface: iface.as_ipv4(), + }, + ArchivedJournalEntry::SocketLeaveIpv6MulticastV1( + ArchivedJournalEntrySocketLeaveIpv6MulticastV1 { + fd, + multiaddr, + iface, + }, + ) => Self::SocketLeaveIpv6MulticastV1 { + fd: *fd, + multi_addr: multiaddr.as_ipv6(), + iface: *iface, + }, + ArchivedJournalEntry::SocketSendFileV1(ArchivedJournalEntrySocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + }) => Self::SocketSendFileV1 { + socket_fd: *socket_fd, + file_fd: *file_fd, + offset: *offset, + count: *count, + }, + ArchivedJournalEntry::SocketSendToV1(ArchivedJournalEntrySocketSendToV1 { + fd, + data, + flags, + addr, + is_64bit, + _padding: _, + }) => Self::SocketSendToV1 { + fd: *fd, + data: data.as_ref().into(), + flags: *flags, + addr: addr.as_socket_addr(), + is_64bit: *is_64bit, + }, + ArchivedJournalEntry::SocketSendV1(ArchivedJournalEntrySocketSendV1 { + fd, + data, + flags, + is_64bit, + _padding: _, + }) => Self::SocketSendV1 { + fd: *fd, + data: data.as_ref().into(), + flags: *flags, + is_64bit: *is_64bit, + }, + ArchivedJournalEntry::SocketSetOptFlagV1(ArchivedJournalEntrySocketSetOptFlagV1 { + fd, + ref opt, + flag, + }) => Self::SocketSetOptFlagV1 { + fd: *fd, + opt: opt.into(), + flag: *flag, + }, + ArchivedJournalEntry::SocketSetOptSizeV1(ArchivedJournalEntrySocketSetOptSizeV1 { + fd, + ref opt, + size, + }) => Self::SocketSetOptSizeV1 { + fd: *fd, + opt: opt.into(), + size: *size, + }, + ArchivedJournalEntry::SocketSetOptTimeV1(ArchivedJournalEntrySocketSetOptTimeV1 { + fd, + ref ty, + time, + }) => Self::SocketSetOptTimeV1 { + fd: *fd, + ty: ty.into(), + time: time.as_ref().map(|time| (*time).try_into().unwrap()), + }, + ArchivedJournalEntry::SocketShutdownV1(ArchivedJournalEntrySocketShutdownV1 { + fd, + ref how, + }) => Self::SocketShutdownV1 { + fd: *fd, + how: how.into(), + }, + ArchivedJournalEntry::CreateEventV1(ArchivedJournalEntryCreateEventV1 { + initial_val, + flags, + fd, + }) => Self::CreateEventV1 { + initial_val: *initial_val, + flags: *flags, + fd: *fd, + }, + }) + } +} diff --git a/lib/journal/src/concrete/boxed.rs b/lib/journal/src/concrete/boxed.rs new file mode 100644 index 00000000000..75cb3de356a --- /dev/null +++ b/lib/journal/src/concrete/boxed.rs @@ -0,0 +1,35 @@ +use std::ops::Deref; + +use super::*; + +impl ReadableJournal for Box { + fn read(&self) -> anyhow::Result>> { + self.deref().read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.deref().as_restarted() + } +} + +impl WritableJournal for Box { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.deref().write(entry) + } +} + +impl ReadableJournal for Box { + fn read(&self) -> anyhow::Result>> { + self.deref().read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.deref().as_restarted() + } +} + +impl WritableJournal for Box { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.deref().write(entry) + } +} diff --git a/lib/journal/src/concrete/buffered.rs b/lib/journal/src/concrete/buffered.rs new file mode 100644 index 00000000000..67b16631120 --- /dev/null +++ b/lib/journal/src/concrete/buffered.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; +use std::sync::Mutex; + +use super::*; + +// The buffered journal will keep all the events in memory until it +// is either reset or dropped. +#[derive(Debug)] +pub struct BufferedJournal { + tx: BufferedJournalTx, + rx: BufferedJournalRx, +} + +#[derive(Debug, Default, Clone)] +struct State { + records: Arc>>>, + offset: usize, +} + +#[derive(Debug)] +pub struct BufferedJournalRx { + state: Arc>, +} + +#[derive(Debug)] +pub struct BufferedJournalTx { + state: Arc>, +} + +impl Default for BufferedJournal { + fn default() -> Self { + let state = Arc::new(Mutex::new(State::default())); + Self { + tx: BufferedJournalTx { + state: state.clone(), + }, + rx: BufferedJournalRx { state }, + } + } +} + +impl WritableJournal for BufferedJournalTx { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + let entry = entry.into_owned(); + let state = self.state.lock().unwrap(); + let estimate_size = entry.estimate_size(); + state.records.lock().unwrap().push(entry); + Ok(estimate_size as u64) + } +} + +impl ReadableJournal for BufferedJournalRx { + fn read(&self) -> anyhow::Result>> { + let mut state = self.state.lock().unwrap(); + let ret = state.records.lock().unwrap().get(state.offset).cloned(); + if ret.is_some() { + state.offset += 1; + } + Ok(ret) + } + + fn as_restarted(&self) -> anyhow::Result> { + let mut state = self.state.lock().unwrap().clone(); + state.offset = 0; + Ok(Box::new(BufferedJournalRx { + state: Arc::new(Mutex::new(state)), + })) + } +} + +impl WritableJournal for BufferedJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl ReadableJournal for BufferedJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.rx.as_restarted() + } +} + +impl Journal for BufferedJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.tx), Box::new(self.rx)) + } +} diff --git a/lib/journal/src/concrete/compacting.rs b/lib/journal/src/concrete/compacting.rs new file mode 100644 index 00000000000..8098c2ce2b5 --- /dev/null +++ b/lib/journal/src/concrete/compacting.rs @@ -0,0 +1,1175 @@ +use derivative::Derivative; +use std::{ + collections::{HashMap, HashSet}, + ops::{DerefMut, Range}, + sync::{Arc, Mutex}, +}; +use wasmer_wasix_types::wasi; + +use super::*; + +pub type Fd = u32; + +#[derive(Debug, Default)] +struct StateDescriptor { + events: Vec, + write_map: HashMap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct MemoryRange { + start: u64, + end: u64, +} +impl From> for MemoryRange { + fn from(value: Range) -> Self { + Self { + start: value.start, + end: value.end, + } + } +} + +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +struct DescriptorLookup(u64); + +#[derive(Derivative)] +#[derivative(Debug)] +struct State { + /// The descriptor seed is used generate descriptor lookups + descriptor_seed: u64, + // We maintain a memory map of the events that are significant + memory_map: HashMap, + // List of all the snapshots + snapshots: Vec, + // Last tty event thats been set + tty: Option, + // Events that create a particular directory + create_directory: HashMap, + // Events that remove a particular directory + remove_directory: HashMap, + // When creating and truncating a file we have a special + // lookup so that duplicates can be erased + create_trunc_file: HashMap, + // Thread events are only maintained while the thread and the + // process are still running + thread_map: HashMap, + // Any descriptors are assumed to be read only operations until + // they actually do something that changes the system + suspect_descriptors: HashMap, + // Any descriptors are assumed to be read only operations until + // they actually do something that changes the system + keep_descriptors: HashMap, + // We put the IO related to stdio into a special list + // which can be purged when the program exits as its no longer + // important. + stdio_descriptors: HashMap, + // We abstract the descriptor state so that multiple file descriptors + // can refer to the same file descriptors + descriptors: HashMap, + // Everything that will be retained during the next compact + whitelist: HashSet, + // We use an event index to track what to keep + event_index: usize, + // The delta list is used for all the events that happened + // after a compact started + delta_list: Option>, + // The inner journal that we will write to + #[derivative(Debug = "ignore")] + inner_tx: Box, + // The inner journal that we read from + #[derivative(Debug = "ignore")] + inner_rx: Box, +} + +impl State { + fn create_filter(&self, inner: J) -> FilteredJournal + where + J: Journal, + { + let has_threads = !self.thread_map.is_empty(); + + let mut filter = FilteredJournalBuilder::new() + .with_filter_events(self.whitelist.clone().into_iter().collect()); + if let Some(tty) = self.tty.as_ref() { + filter.add_event_to_whitelist(*tty); + } + for e in self.snapshots.iter() { + filter.add_event_to_whitelist(*e); + } + for (_, e) in self.memory_map.iter() { + filter.add_event_to_whitelist(*e); + } + for t in self.thread_map.iter() { + filter.add_event_to_whitelist(*t.1); + } + for (_, e) in self.create_directory.iter() { + filter.add_event_to_whitelist(*e); + } + for (_, e) in self.remove_directory.iter() { + filter.add_event_to_whitelist(*e); + } + for (_, l) in self + .suspect_descriptors + .iter() + .chain(self.keep_descriptors.iter()) + { + if let Some(d) = self.descriptors.get(l) { + for e in d.events.iter() { + filter.add_event_to_whitelist(*e); + } + for e in d.write_map.values() { + filter.add_event_to_whitelist(*e); + } + } + } + if has_threads { + for (_, l) in self.stdio_descriptors.iter() { + if let Some(d) = self.descriptors.get(l) { + for e in d.events.iter() { + filter.add_event_to_whitelist(*e); + } + for e in d.write_map.values() { + filter.add_event_to_whitelist(*e); + } + } + } + } + filter.build(inner) + } +} + +/// Deduplicates memory and stacks to reduce the number of volume of +/// log events sent to its inner capturer. Compacting the events occurs +/// in line as the events are generated +#[derive(Debug, Clone)] +pub struct CompactingJournalTx { + state: Arc>, + compacting: Arc>, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct CompactingJournalRx { + #[derivative(Debug = "ignore")] + inner: Box, +} + +impl CompactingJournalRx { + pub fn swap_inner(&mut self, mut with: Box) -> Box { + std::mem::swap(&mut self.inner, &mut with); + with + } +} + +#[derive(Debug)] +pub struct CompactingJournal { + tx: CompactingJournalTx, + rx: CompactingJournalRx, +} + +impl CompactingJournal { + pub fn new(inner: J) -> anyhow::Result + where + J: Journal, + { + let (tx, rx) = inner.split(); + let state = State { + inner_tx: tx, + inner_rx: rx.as_restarted()?, + tty: None, + snapshots: Default::default(), + memory_map: Default::default(), + thread_map: Default::default(), + create_directory: Default::default(), + remove_directory: Default::default(), + create_trunc_file: Default::default(), + suspect_descriptors: Default::default(), + keep_descriptors: Default::default(), + stdio_descriptors: Default::default(), + descriptor_seed: 0, + descriptors: Default::default(), + whitelist: Default::default(), + delta_list: None, + event_index: 0, + }; + Ok(Self { + tx: CompactingJournalTx { + state: Arc::new(Mutex::new(state)), + compacting: Arc::new(Mutex::new(())), + }, + rx: CompactingJournalRx { inner: rx }, + }) + } +} + +/// Represents the results of a compaction operation +#[derive(Debug, Default)] +pub struct CompactResult { + pub total_size: u64, + pub total_events: usize, +} + +impl CompactingJournalTx { + pub fn create_filter(&self, inner: J) -> FilteredJournal + where + J: Journal, + { + let state = self.state.lock().unwrap(); + state.create_filter(inner) + } + + pub fn swap(&self, other: Self) -> Self { + let mut state1 = self.state.lock().unwrap(); + let mut state2 = other.state.lock().unwrap(); + std::mem::swap(state1.deref_mut(), state2.deref_mut()); + drop(state1); + drop(state2); + other + } + + /// Compacts the inner journal into a new journal + pub fn compact_to(&self, new_journal: J) -> anyhow::Result + where + J: Journal, + { + // Enter a compacting lock + let _guard = self.compacting.lock().unwrap(); + + // The first thing we do is create a filter that we + // place around the new journal so that it only receives new events + let (new_journal, replay_rx) = { + let mut state = self.state.lock().unwrap(); + state.delta_list.replace(Default::default()); + ( + state.create_filter(new_journal), + state.inner_rx.as_restarted()?, + ) + }; + + let mut result = CompactResult::default(); + + // Read all the events and feed them into the filtered journal and then + // strip off the filter so that its a normal journal again + while let Some(entry) = replay_rx.read()? { + let amt = new_journal.write(entry)?; + if amt > 0 { + result.total_size += amt; + result.total_events += 1; + } + } + let new_journal = new_journal.into_inner(); + + // We now go into a blocking situation which will freeze the journals + let mut state = self.state.lock().unwrap(); + + // Now we build a filtered journal which will pick up any events that were + // added which we did the compacting. + let new_journal = FilteredJournalBuilder::new() + .with_filter_events( + state + .delta_list + .take() + .unwrap_or_default() + .into_iter() + .collect(), + ) + .build(new_journal); + + // Now we feed all the events into the new journal using the delta filter. After the + // extra events are added we strip off the filter again + let replay_rx = state.inner_rx.as_restarted()?; + while let Some(entry) = replay_rx.read()? { + new_journal.write(entry)?; + } + let new_journal = new_journal.into_inner(); + + // Now we install the new journal + let (mut tx, mut rx) = new_journal.split(); + std::mem::swap(&mut state.inner_tx, &mut tx); + std::mem::swap(&mut state.inner_rx, &mut rx); + + Ok(result) + } + + pub fn replace_inner(&self, inner: J) { + let mut state = self.state.lock().unwrap(); + let (mut tx, mut rx) = inner.split(); + std::mem::swap(&mut state.inner_tx, &mut tx); + std::mem::swap(&mut state.inner_rx, &mut rx); + } +} + +impl WritableJournal for CompactingJournalTx { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + let mut state = self.state.lock().unwrap(); + let event_index = state.event_index; + state.event_index += 1; + + if let Some(delta) = state.delta_list.as_mut() { + delta.push(event_index); + } + + match &entry { + JournalEntry::UpdateMemoryRegionV1 { region, .. } => { + state.memory_map.insert(region.clone().into(), event_index); + } + JournalEntry::SetThreadV1 { id, .. } => { + state.thread_map.insert(*id, event_index); + } + JournalEntry::CloseThreadV1 { id, .. } => { + state.thread_map.remove(id); + } + JournalEntry::SnapshotV1 { .. } => { + state.snapshots.push(event_index); + } + JournalEntry::ProcessExitV1 { .. } => { + state.thread_map.clear(); + state.memory_map.clear(); + for (_, lookup) in state.suspect_descriptors.clone() { + state.descriptors.remove(&lookup); + } + state.suspect_descriptors.clear(); + for (_, lookup) in state.stdio_descriptors.clone() { + state.descriptors.remove(&lookup); + } + state.stdio_descriptors.clear(); + state.whitelist.insert(event_index); + state.snapshots.clear(); + } + JournalEntry::TtySetV1 { .. } => { + state.tty.replace(event_index); + } + JournalEntry::OpenFileDescriptorV1 { + fd, o_flags, path, .. + } => { + // All file descriptors are opened in a suspect state which + // means if they are closed without modifying the file system + // then the events will be ignored. + let lookup = DescriptorLookup(state.descriptor_seed); + state.descriptor_seed += 1; + state.suspect_descriptors.insert(*fd, lookup); + + // There is an exception to the rule which is if the create + // flag is specified its always recorded as a mutating operation + // because it may create a file that does not exist on the file system + if o_flags.contains(wasi::Oflags::CREATE) { + if let Some(lookup) = state.suspect_descriptors.remove(fd) { + state.keep_descriptors.insert(*fd, lookup); + } + } + + // The event itself must be recorded in a staging area + state + .descriptors + .entry(lookup) + .or_insert_with(Default::default) + .events + .push(event_index); + + // Creating a file and erasing anything that was there before means + // the entire create branch that exists before this one can be ignored + if o_flags.contains(wasi::Oflags::CREATE) && o_flags.contains(wasi::Oflags::TRUNC) { + let path = path.to_string(); + if let Some(existing) = state.create_trunc_file.remove(&path) { + state.suspect_descriptors.remove(&existing); + state.keep_descriptors.remove(&existing); + } + state.create_trunc_file.insert(path, *fd); + } + } + // We keep non-mutable events for file descriptors that are suspect + JournalEntry::FileDescriptorSeekV1 { fd, .. } + | JournalEntry::CloseFileDescriptorV1 { fd } => { + // Get the lookup + // (if its suspect then it will remove the entry and + // thus the entire branch of events it represents is discarded) + let lookup = if matches!(&entry, JournalEntry::CloseFileDescriptorV1 { .. }) { + state.suspect_descriptors.remove(fd) + } else { + state.suspect_descriptors.get(fd).cloned() + }; + let lookup = lookup + .or_else(|| state.keep_descriptors.get(fd).cloned()) + .or_else(|| state.stdio_descriptors.get(fd).cloned()); + + if let Some(lookup) = lookup { + let state = state + .descriptors + .entry(lookup) + .or_insert_with(Default::default); + state.events.push(event_index); + } else { + state.whitelist.insert(event_index); + } + } + // Things that modify a file descriptor mean that it is + // no longer suspect and thus it needs to be kept + JournalEntry::FileDescriptorAdviseV1 { fd, .. } + | JournalEntry::FileDescriptorAllocateV1 { fd, .. } + | JournalEntry::FileDescriptorSetFlagsV1 { fd, .. } + | JournalEntry::FileDescriptorSetTimesV1 { fd, .. } + | JournalEntry::FileDescriptorWriteV1 { fd, .. } => { + // Its no longer suspect + if let Some(lookup) = state.suspect_descriptors.remove(fd) { + state.keep_descriptors.insert(*fd, lookup); + } + + // Get the lookup + let lookup = state + .suspect_descriptors + .get(fd) + .cloned() + .or_else(|| state.keep_descriptors.get(fd).cloned()) + .or_else(|| state.stdio_descriptors.get(fd).cloned()); + + // Update the state + if let Some(lookup) = lookup { + let state = state + .descriptors + .entry(lookup) + .or_insert_with(Default::default); + if let JournalEntry::FileDescriptorWriteV1 { offset, data, .. } = &entry { + state.write_map.insert( + MemoryRange { + start: *offset, + end: *offset + data.len() as u64, + }, + event_index, + ); + } else { + state.events.push(event_index); + } + } else { + state.whitelist.insert(event_index); + } + } + // Duplicating the file descriptor + JournalEntry::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + } => { + if let Some(lookup) = state.suspect_descriptors.remove(original_fd) { + state.suspect_descriptors.insert(*copied_fd, lookup); + } else if let Some(lookup) = state.keep_descriptors.remove(original_fd) { + state.keep_descriptors.insert(*copied_fd, lookup); + } else if let Some(lookup) = state.stdio_descriptors.remove(original_fd) { + state.stdio_descriptors.insert(*copied_fd, lookup); + } else { + state.whitelist.insert(event_index); + } + } + // Renumbered file descriptors will retain their suspect status + JournalEntry::RenumberFileDescriptorV1 { old_fd, new_fd } => { + if let Some(lookup) = state.suspect_descriptors.remove(old_fd) { + state.suspect_descriptors.insert(*new_fd, lookup); + } else if let Some(lookup) = state.keep_descriptors.remove(old_fd) { + state.keep_descriptors.insert(*new_fd, lookup); + } else if let Some(lookup) = state.stdio_descriptors.remove(old_fd) { + state.stdio_descriptors.insert(*new_fd, lookup); + } else { + state.whitelist.insert(event_index); + } + } + // Creating a new directory only needs to be done once + JournalEntry::CreateDirectoryV1 { path, .. } => { + let path = path.to_string(); + state.remove_directory.remove(&path); + state.create_directory.entry(path).or_insert(event_index); + } + // Deleting a directory only needs to be done once + JournalEntry::RemoveDirectoryV1 { path, .. } => { + let path = path.to_string(); + state.create_directory.remove(&path); + state.remove_directory.entry(path).or_insert(event_index); + } + _ => { + // The fallthrough is to whitelist the event so that it will + // be reflected in the next compaction event + state.whitelist.insert(event_index); + } + } + state.inner_tx.write(entry) + } +} + +impl CompactingJournal { + /// Compacts the inner journal into a new journal + pub fn compact_to(&mut self, new_journal: J) -> anyhow::Result + where + J: Journal, + { + self.tx.compact_to(new_journal) + } + + pub fn into_split(self) -> (CompactingJournalTx, CompactingJournalRx) { + (self.tx, self.rx) + } + + pub fn replace_inner(&mut self, inner: J) { + let (inner_tx, inner_rx) = inner.split(); + let inner_rx_restarted = inner_rx.as_restarted().unwrap(); + + self.tx + .replace_inner(RecombinedJournal::new(inner_tx, inner_rx)); + self.rx.inner = inner_rx_restarted; + } +} + +impl ReadableJournal for CompactingJournalRx { + fn read(&self) -> anyhow::Result>> { + self.inner.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.inner.as_restarted() + } +} + +impl WritableJournal for CompactingJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl ReadableJournal for CompactingJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + let state = self.tx.state.lock().unwrap(); + state.inner_rx.as_restarted() + } +} + +impl Journal for CompactingJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.tx), Box::new(self.rx)) + } +} + +#[cfg(feature = "journal")] +#[cfg(test)] +mod tests { + use super::*; + + use wasmer_wasix_types::wasi::Tty; + + pub fn run_test<'a>( + in_records: Vec>, + out_records: Vec>, + ) -> anyhow::Result<()> { + // Build a journal that will store the records before compacting + let mut compacting_journal = CompactingJournal::new(BufferedJournal::default())?; + for record in in_records { + compacting_journal.write(record)?; + } + + // Now we build a new one using the compactor + let new_journal = BufferedJournal::default(); + compacting_journal.compact_to(new_journal)?; + + // Read the records + let new_records = compacting_journal.as_restarted()?; + for record1 in out_records { + let record2 = new_records.read()?; + assert_eq!(Some(record1), record2); + } + assert!(new_records.read()?.is_none()); + + Ok(()) + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_purge_duplicate_memory_writes() { + run_test( + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [22u8; 16].to_vec().into(), + }, + ], + vec![JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [22u8; 16].to_vec().into(), + }], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_keep_overlapping_memory() { + run_test( + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::UpdateMemoryRegionV1 { + region: 20..36, + data: [22u8; 16].to_vec().into(), + }, + ], + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::UpdateMemoryRegionV1 { + region: 20..36, + data: [22u8; 16].to_vec().into(), + }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_keep_adjacent_memory_writes() { + run_test( + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::UpdateMemoryRegionV1 { + region: 16..32, + data: [22u8; 16].to_vec().into(), + }, + ], + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::UpdateMemoryRegionV1 { + region: 16..32, + data: [22u8; 16].to_vec().into(), + }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_purge_identical_memory_writes() { + run_test( + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + ], + vec![JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_thread_stacks() { + run_test( + vec![ + JournalEntry::SetThreadV1 { + id: 4321.into(), + call_stack: [44u8; 87].to_vec().into(), + memory_stack: [55u8; 34].to_vec().into(), + store_data: [66u8; 70].to_vec().into(), + is_64bit: true, + }, + JournalEntry::SetThreadV1 { + id: 1234.into(), + call_stack: [11u8; 124].to_vec().into(), + memory_stack: [22u8; 51].to_vec().into(), + store_data: [33u8; 87].to_vec().into(), + is_64bit: true, + }, + JournalEntry::SetThreadV1 { + id: 65.into(), + call_stack: [77u8; 34].to_vec().into(), + memory_stack: [88u8; 51].to_vec().into(), + store_data: [99u8; 12].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseThreadV1 { + id: 1234.into(), + exit_code: None, + }, + ], + vec![ + JournalEntry::SetThreadV1 { + id: 4321.into(), + call_stack: [44u8; 87].to_vec().into(), + memory_stack: [55u8; 34].to_vec().into(), + store_data: [66u8; 70].to_vec().into(), + is_64bit: true, + }, + JournalEntry::SetThreadV1 { + id: 65.into(), + call_stack: [77u8; 34].to_vec().into(), + memory_stack: [88u8; 51].to_vec().into(), + store_data: [99u8; 12].to_vec().into(), + is_64bit: true, + }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_processed_exited() { + run_test( + vec![ + JournalEntry::UpdateMemoryRegionV1 { + region: 0..16, + data: [11u8; 16].to_vec().into(), + }, + JournalEntry::SetThreadV1 { + id: 4321.into(), + call_stack: [44u8; 87].to_vec().into(), + memory_stack: [55u8; 34].to_vec().into(), + store_data: [66u8; 70].to_vec().into(), + is_64bit: true, + }, + JournalEntry::SnapshotV1 { + when: SystemTime::now(), + trigger: SnapshotTrigger::FirstListen, + }, + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::ProcessExitV1 { exit_code: None }, + ], + vec![JournalEntry::ProcessExitV1 { exit_code: None }], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_partial_write_survives() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + ], + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_write_survives_close() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + ], + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_write_survives_exit() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::ProcessExitV1 { exit_code: None }, + ], + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::ProcessExitV1 { exit_code: None }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_read_is_ignored() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorSeekV1 { + fd: 1234, + offset: 1234, + whence: wasi::Whence::End, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + ], + Vec::new(), + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_touch() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::CREATE | wasi::Oflags::TRUNC, + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + JournalEntry::ProcessExitV1 { exit_code: None }, + ], + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::CREATE | wasi::Oflags::TRUNC, + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + JournalEntry::ProcessExitV1 { exit_code: None }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_redundant_file() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::CREATE | wasi::Oflags::TRUNC, + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [5u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + JournalEntry::OpenFileDescriptorV1 { + fd: 1235, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::CREATE | wasi::Oflags::TRUNC, + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1235, + offset: 1234, + data: [6u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1235 }, + ], + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1235, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::CREATE | wasi::Oflags::TRUNC, + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1235, + offset: 1234, + data: [6u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1235 }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_ignore_double_writes() { + run_test( + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [1u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [5u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + ], + vec![ + JournalEntry::OpenFileDescriptorV1 { + fd: 1234, + dirfd: 3452345, + dirflags: 0, + path: "/blah".into(), + o_flags: wasi::Oflags::empty(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }, + JournalEntry::FileDescriptorWriteV1 { + fd: 1234, + offset: 1234, + data: [5u8; 16].to_vec().into(), + is_64bit: true, + }, + JournalEntry::CloseFileDescriptorV1 { fd: 1234 }, + ], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_create_directory() { + run_test( + vec![JournalEntry::CreateDirectoryV1 { + fd: 1234, + path: "/blah".into(), + }], + vec![JournalEntry::CreateDirectoryV1 { + fd: 1234, + path: "/blah".into(), + }], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_file_system_redundant_create_directory() { + run_test( + vec![ + JournalEntry::CreateDirectoryV1 { + fd: 1234, + path: "/blah".into(), + }, + JournalEntry::CreateDirectoryV1 { + fd: 1235, + path: "/blah".into(), + }, + ], + vec![JournalEntry::CreateDirectoryV1 { + fd: 1234, + path: "/blah".into(), + }], + ) + .unwrap() + } + + #[tracing_test::traced_test] + #[test] + pub fn test_compact_duplicate_tty() { + run_test( + vec![ + JournalEntry::TtySetV1 { + tty: Tty { + cols: 123, + rows: 65, + width: 2341, + height: 573457, + stdin_tty: true, + stdout_tty: true, + stderr_tty: true, + echo: true, + line_buffered: true, + }, + line_feeds: true, + }, + JournalEntry::TtySetV1 { + tty: Tty { + cols: 12, + rows: 65, + width: 2341, + height: 573457, + stdin_tty: true, + stdout_tty: false, + stderr_tty: true, + echo: true, + line_buffered: true, + }, + line_feeds: true, + }, + ], + vec![JournalEntry::TtySetV1 { + tty: Tty { + cols: 12, + rows: 65, + width: 2341, + height: 573457, + stdin_tty: true, + stdout_tty: false, + stderr_tty: true, + echo: true, + line_buffered: true, + }, + line_feeds: true, + }], + ) + .unwrap() + } +} diff --git a/lib/journal/src/concrete/compacting_log_file.rs b/lib/journal/src/concrete/compacting_log_file.rs new file mode 100644 index 00000000000..1face3b2ecd --- /dev/null +++ b/lib/journal/src/concrete/compacting_log_file.rs @@ -0,0 +1,262 @@ +use std::{ + path::{Path, PathBuf}, + sync::{Arc, Mutex}, +}; + +use super::*; + +#[derive(Debug)] +struct State { + on_n_records: Option, + on_n_size: Option, + on_factor_size: Option, + on_drop: bool, + cnt_records: u64, + cnt_size: u64, + ref_size: u64, +} + +#[derive(Debug)] +pub struct CompactingLogFileJournal { + tx: CompactingLogFileJournalTx, + rx: CompactingLogFileJournalRx, +} + +#[derive(Debug)] +pub struct CompactingLogFileJournalTx { + state: Arc>, + inner: CompactingJournalTx, + main_path: PathBuf, + temp_path: PathBuf, +} + +#[derive(Debug)] +pub struct CompactingLogFileJournalRx { + #[allow(dead_code)] + state: Arc>, + inner: CompactingJournalRx, +} + +impl CompactingLogFileJournalRx { + pub fn swap_inner(&mut self, with: Box) -> Box { + self.inner.swap_inner(with) + } +} + +impl CompactingLogFileJournal { + pub fn new(path: impl AsRef) -> anyhow::Result { + // We prepare a compacting journal which does nothing + // with the events other than learn from them + let counting = CountingJournal::default(); + let mut compacting = CompactingJournal::new(counting.clone())?; + + // We first feed all the entries into the compactor so that + // it learns all the records + let log_file = LogFileJournal::new(path.as_ref())?; + copy_journal(&log_file, &compacting)?; + + // Now everything is learned its time to attach the + // log file to the compacting journal + compacting.replace_inner(log_file); + let (tx, rx) = compacting.into_split(); + + let mut temp_filename = path + .as_ref() + .file_name() + .ok_or_else(|| { + anyhow::format_err!( + "The path is not a valid filename - {}", + path.as_ref().to_string_lossy() + ) + })? + .to_string_lossy() + .to_string(); + temp_filename.insert_str(0, ".compacting."); + let temp_path = path.as_ref().with_file_name(&temp_filename); + + let state = Arc::new(Mutex::new(State { + on_drop: false, + on_n_records: None, + on_n_size: None, + on_factor_size: None, + cnt_records: 0, + cnt_size: 0, + ref_size: counting.size(), + })); + let tx = CompactingLogFileJournalTx { + state: state.clone(), + inner: tx, + main_path: path.as_ref().to_path_buf(), + temp_path, + }; + let rx = CompactingLogFileJournalRx { state, inner: rx }; + + Ok(Self { tx, rx }) + } + + pub fn compact_now(&mut self) -> anyhow::Result { + let (result, new_rx) = self.tx.compact_now()?; + self.rx.inner = new_rx; + Ok(result) + } + + pub fn with_compact_on_drop(self) -> Self { + self.tx.state.lock().unwrap().on_drop = true; + self + } + + pub fn with_compact_on_n_records(self, n_records: u64) -> Self { + self.tx + .state + .lock() + .unwrap() + .on_n_records + .replace(n_records); + self + } + + pub fn with_compact_on_n_size(self, n_size: u64) -> Self { + self.tx.state.lock().unwrap().on_n_size.replace(n_size); + self + } + + pub fn with_compact_on_factor_size(self, factor_size: f32) -> Self { + self.tx + .state + .lock() + .unwrap() + .on_factor_size + .replace(factor_size); + self + } +} + +impl CompactingLogFileJournalTx { + pub fn compact_now(&self) -> anyhow::Result<(CompactResult, CompactingJournalRx)> { + // Reset the counters + self.reset_counters(); + + // Create the staging file and open it + std::fs::remove_file(&self.temp_path).ok(); + let target = LogFileJournal::new(self.temp_path.clone())?; + + // Compact the data into the new target and rename it over the last one + let result = self.inner.compact_to(target)?; + std::fs::rename(&self.temp_path, &self.main_path)?; + + // Renaming the file has quite a detrimental effect on the file as + // it means any new mmap operations will fail, hence we need to + // reopen the log file, seek to the end and reattach it + let target = LogFileJournal::new(self.main_path.clone())?; + + // We prepare a compacting journal which does nothing + // with the events other than learn from them + let counting = CountingJournal::default(); + let mut compacting = CompactingJournal::new(counting)?; + copy_journal(&target, &compacting)?; + + // Now everything is learned its time to attach the log file to the compacting journal + // and replace the current one + compacting.replace_inner(target); + let (tx, rx) = compacting.into_split(); + self.inner.swap(tx); + + // We take a new reference point for the size of the journal + { + let mut state = self.state.lock().unwrap(); + state.ref_size = result.total_size; + } + + Ok((result, rx)) + } + + pub fn reset_counters(&self) { + let mut state = self.state.lock().unwrap(); + state.cnt_records = 0; + state.cnt_size = 0; + } +} + +impl Drop for CompactingLogFileJournalTx { + fn drop(&mut self) { + let triggered = self.state.lock().unwrap().on_drop; + if triggered { + if let Err(err) = self.compact_now() { + tracing::error!("failed to compact log - {}", err); + } + } + } +} + +impl ReadableJournal for CompactingLogFileJournalRx { + fn read(&self) -> anyhow::Result>> { + self.inner.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.inner.as_restarted() + } +} + +impl WritableJournal for CompactingLogFileJournalTx { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + let amt = self.inner.write(entry)?; + + let triggered = { + let mut state = self.state.lock().unwrap(); + if amt > 0 { + state.cnt_records += 1; + state.cnt_size += amt; + } + + let mut triggered = false; + if let Some(on) = state.on_n_records.as_ref() { + if state.cnt_records >= *on { + triggered = true; + } + } + if let Some(on) = state.on_n_size.as_ref() { + if state.cnt_size >= *on { + triggered = true; + } + } + + if let Some(factor) = state.on_factor_size.as_ref() { + let next_ref = (*factor * state.ref_size as f32) as u64; + if state.cnt_size > next_ref { + triggered = true; + } + } + + triggered + }; + + if triggered { + self.compact_now()?; + } + + Ok(amt) + } +} + +impl ReadableJournal for CompactingLogFileJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.rx.as_restarted() + } +} + +impl WritableJournal for CompactingLogFileJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl Journal for CompactingLogFileJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.tx), Box::new(self.rx)) + } +} diff --git a/lib/journal/src/concrete/counting.rs b/lib/journal/src/concrete/counting.rs new file mode 100644 index 00000000000..ddeda96a5a7 --- /dev/null +++ b/lib/journal/src/concrete/counting.rs @@ -0,0 +1,48 @@ +use std::sync::{ + atomic::{AtomicU64, AtomicUsize, Ordering}, + Arc, +}; + +use super::*; + +/// Journal that counts the size of the entries that are written to it +#[derive(Debug, Clone, Default)] +pub struct CountingJournal { + n_cnt: Arc, + n_size: Arc, +} + +impl CountingJournal { + pub fn cnt(&self) -> usize { + self.n_cnt.load(Ordering::SeqCst) + } + + pub fn size(&self) -> u64 { + self.n_size.load(Ordering::SeqCst) + } +} + +impl ReadableJournal for CountingJournal { + fn read(&self) -> anyhow::Result>> { + Ok(None) + } + + fn as_restarted(&self) -> anyhow::Result> { + Ok(Box::::default()) + } +} + +impl WritableJournal for CountingJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + let size = entry.estimate_size() as u64; + self.n_cnt.fetch_add(1, Ordering::SeqCst); + self.n_size.fetch_add(size, Ordering::SeqCst); + Ok(size) + } +} + +impl Journal for CountingJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.clone()), Box::new(self)) + } +} diff --git a/lib/journal/src/concrete/filter.rs b/lib/journal/src/concrete/filter.rs new file mode 100644 index 00000000000..32a8ee3ce7c --- /dev/null +++ b/lib/journal/src/concrete/filter.rs @@ -0,0 +1,353 @@ +use std::{ + collections::HashSet, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use derivative::Derivative; + +use super::*; + +/// Filters out a specific set of journal events and drops the rest, this +/// journal can be useful for restoring to a previous call point but +/// retaining the memory changes (e.g. WCGI runner). +#[derive(Debug)] +pub struct FilteredJournal { + tx: FilteredJournalTx, + rx: FilteredJournalRx, +} + +/// Represents what will be filtered by the filtering process +#[derive(Debug)] +struct FilteredJournalConfig { + filter_memory: bool, + filter_threads: bool, + filter_fs: bool, + filter_stdio: bool, + filter_core: bool, + filter_snapshots: bool, + filter_net: bool, + filter_events: Option>, + event_index: AtomicUsize, +} + +impl Default for FilteredJournalConfig { + fn default() -> Self { + Self { + filter_memory: false, + filter_threads: false, + filter_fs: false, + filter_stdio: false, + filter_core: false, + filter_snapshots: false, + filter_net: false, + filter_events: None, + event_index: AtomicUsize::new(0), + } + } +} + +impl Clone for FilteredJournalConfig { + fn clone(&self) -> Self { + Self { + filter_memory: self.filter_memory, + filter_threads: self.filter_threads, + filter_fs: self.filter_fs, + filter_stdio: self.filter_stdio, + filter_core: self.filter_core, + filter_snapshots: self.filter_snapshots, + filter_net: self.filter_net, + filter_events: self.filter_events.clone(), + event_index: AtomicUsize::new(self.event_index.load(Ordering::SeqCst)), + } + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct FilteredJournalTx { + #[derivative(Debug = "ignore")] + inner: Box, + config: FilteredJournalConfig, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct FilteredJournalRx { + #[derivative(Debug = "ignore")] + inner: Box, +} + +/// Constructs a filter with a set of parameters that will be filtered on +#[derive(Debug, Default)] +pub struct FilteredJournalBuilder { + config: FilteredJournalConfig, +} + +impl FilteredJournalBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn build(self, inner: J) -> FilteredJournal + where + J: Journal, + { + FilteredJournal::new(inner, self.config) + } + + pub fn with_ignore_memory(mut self, val: bool) -> Self { + self.config.filter_memory = val; + self + } + + pub fn with_ignore_threads(mut self, val: bool) -> Self { + self.config.filter_threads = val; + self + } + + pub fn with_ignore_fs(mut self, val: bool) -> Self { + self.config.filter_fs = val; + self + } + + pub fn with_ignore_stdio(mut self, val: bool) -> Self { + self.config.filter_stdio = val; + self + } + + pub fn with_ignore_core(mut self, val: bool) -> Self { + self.config.filter_core = val; + self + } + + pub fn with_ignore_snapshots(mut self, val: bool) -> Self { + self.config.filter_snapshots = val; + self + } + + pub fn with_ignore_networking(mut self, val: bool) -> Self { + self.config.filter_net = val; + self + } + + pub fn with_filter_events(mut self, events: HashSet) -> Self { + self.config.filter_events = Some(events); + self + } + + pub fn add_event_to_whitelist(&mut self, event_index: usize) { + if let Some(filter) = self.config.filter_events.as_mut() { + filter.insert(event_index); + } + } + + pub fn set_ignore_memory(&mut self, val: bool) -> &mut Self { + self.config.filter_memory = val; + self + } + + pub fn set_ignore_threads(&mut self, val: bool) -> &mut Self { + self.config.filter_threads = val; + self + } + + pub fn set_ignore_fs(&mut self, val: bool) -> &mut Self { + self.config.filter_fs = val; + self + } + + pub fn set_ignore_stdio(&mut self, val: bool) -> &mut Self { + self.config.filter_stdio = val; + self + } + + pub fn set_ignore_core(&mut self, val: bool) -> &mut Self { + self.config.filter_core = val; + self + } + + pub fn set_ignore_snapshots(&mut self, val: bool) -> &mut Self { + self.config.filter_snapshots = val; + self + } + + pub fn set_ignore_networking(&mut self, val: bool) -> &mut Self { + self.config.filter_net = val; + self + } +} + +impl FilteredJournal { + fn new(inner: J, config: FilteredJournalConfig) -> Self + where + J: Journal, + { + let (tx, rx) = inner.split(); + Self { + tx: FilteredJournalTx { inner: tx, config }, + rx: FilteredJournalRx { inner: rx }, + } + } + + pub fn clone_with_inner(&self, inner: J) -> Self + where + J: Journal, + { + let config = self.tx.config.clone(); + let (tx, rx) = inner.split(); + Self { + tx: FilteredJournalTx { config, inner: tx }, + rx: FilteredJournalRx { inner: rx }, + } + } + + pub fn into_inner(self) -> RecombinedJournal { + RecombinedJournal::new(self.tx.inner, self.rx.inner) + } +} + +impl WritableJournal for FilteredJournalTx { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + let event_index = self.config.event_index.fetch_add(1, Ordering::SeqCst); + if let Some(events) = self.config.filter_events.as_ref() { + if !events.contains(&event_index) { + return Ok(0); + } + } + + let evt = match entry { + JournalEntry::SetClockTimeV1 { .. } + | JournalEntry::InitModuleV1 { .. } + | JournalEntry::ProcessExitV1 { .. } + | JournalEntry::EpollCreateV1 { .. } + | JournalEntry::EpollCtlV1 { .. } + | JournalEntry::TtySetV1 { .. } => { + if self.config.filter_core { + return Ok(0); + } + entry + } + JournalEntry::SetThreadV1 { .. } | JournalEntry::CloseThreadV1 { .. } => { + if self.config.filter_threads { + return Ok(0); + } + entry + } + JournalEntry::UpdateMemoryRegionV1 { .. } => { + if self.config.filter_memory { + return Ok(0); + } + entry + } + JournalEntry::FileDescriptorSeekV1 { fd, .. } + | JournalEntry::FileDescriptorWriteV1 { fd, .. } + | JournalEntry::OpenFileDescriptorV1 { fd, .. } + | JournalEntry::CloseFileDescriptorV1 { fd, .. } + | JournalEntry::RenumberFileDescriptorV1 { old_fd: fd, .. } + | JournalEntry::DuplicateFileDescriptorV1 { + original_fd: fd, .. + } + | JournalEntry::FileDescriptorSetFlagsV1 { fd, .. } + | JournalEntry::FileDescriptorAdviseV1 { fd, .. } + | JournalEntry::FileDescriptorAllocateV1 { fd, .. } + | JournalEntry::FileDescriptorSetRightsV1 { fd, .. } + | JournalEntry::FileDescriptorSetTimesV1 { fd, .. } + | JournalEntry::FileDescriptorSetSizeV1 { fd, .. } => { + if self.config.filter_stdio && fd <= 2 { + return Ok(0); + } + if self.config.filter_fs { + return Ok(0); + } + entry + } + JournalEntry::RemoveDirectoryV1 { .. } + | JournalEntry::UnlinkFileV1 { .. } + | JournalEntry::PathRenameV1 { .. } + | JournalEntry::CreateDirectoryV1 { .. } + | JournalEntry::PathSetTimesV1 { .. } + | JournalEntry::CreateHardLinkV1 { .. } + | JournalEntry::CreateSymbolicLinkV1 { .. } + | JournalEntry::ChangeDirectoryV1 { .. } + | JournalEntry::CreatePipeV1 { .. } + | JournalEntry::CreateEventV1 { .. } => { + if self.config.filter_fs { + return Ok(0); + } + entry + } + JournalEntry::SnapshotV1 { .. } => { + if self.config.filter_snapshots { + return Ok(0); + } + entry + } + JournalEntry::PortAddAddrV1 { .. } + | JournalEntry::PortDelAddrV1 { .. } + | JournalEntry::PortAddrClearV1 + | JournalEntry::PortBridgeV1 { .. } + | JournalEntry::PortUnbridgeV1 + | JournalEntry::PortDhcpAcquireV1 + | JournalEntry::PortGatewaySetV1 { .. } + | JournalEntry::PortRouteAddV1 { .. } + | JournalEntry::PortRouteClearV1 + | JournalEntry::PortRouteDelV1 { .. } + | JournalEntry::SocketOpenV1 { .. } + | JournalEntry::SocketListenV1 { .. } + | JournalEntry::SocketBindV1 { .. } + | JournalEntry::SocketConnectedV1 { .. } + | JournalEntry::SocketAcceptedV1 { .. } + | JournalEntry::SocketJoinIpv4MulticastV1 { .. } + | JournalEntry::SocketJoinIpv6MulticastV1 { .. } + | JournalEntry::SocketLeaveIpv4MulticastV1 { .. } + | JournalEntry::SocketLeaveIpv6MulticastV1 { .. } + | JournalEntry::SocketSendFileV1 { .. } + | JournalEntry::SocketSendToV1 { .. } + | JournalEntry::SocketSendV1 { .. } + | JournalEntry::SocketSetOptFlagV1 { .. } + | JournalEntry::SocketSetOptSizeV1 { .. } + | JournalEntry::SocketSetOptTimeV1 { .. } + | JournalEntry::SocketShutdownV1 { .. } => { + if self.config.filter_net { + return Ok(0); + } + entry + } + }; + self.inner.write(evt) + } +} + +impl ReadableJournal for FilteredJournalRx { + fn read(&self) -> anyhow::Result>> { + self.inner.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + Ok(Box::new(FilteredJournalRx { + inner: self.inner.as_restarted()?, + })) + } +} + +impl WritableJournal for FilteredJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl ReadableJournal for FilteredJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.rx.as_restarted() + } +} + +impl Journal for FilteredJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.tx), Box::new(self.rx)) + } +} diff --git a/lib/journal/src/concrete/log_file.rs b/lib/journal/src/concrete/log_file.rs new file mode 100644 index 00000000000..a254b7e7e11 --- /dev/null +++ b/lib/journal/src/concrete/log_file.rs @@ -0,0 +1,405 @@ +use bytes::Buf; +use rkyv::ser::serializers::{ + AllocScratch, CompositeSerializer, SharedSerializeMap, WriteSerializer, +}; +use shared_buffer::OwnedBuffer; +use std::{ + fs::File, + io::{Seek, SeekFrom, Write}, + path::Path, + sync::{Arc, Mutex}, +}; + +use super::*; + +/// The LogFile snapshot capturer will write its snapshots to a linear journal +/// and read them when restoring. It uses the `bincode` serializer which +/// means that forwards and backwards compatibility must be dealt with +/// carefully. +/// +/// When opening an existing journal file that was previously saved +/// then new entries will be added to the end regardless of if +/// its been read. +/// +/// The logfile snapshot capturer uses a 64bit number as a entry encoding +/// delimiter. +pub struct LogFileJournal { + tx: LogFileJournalTx, + rx: LogFileJournalRx, +} + +#[derive(Debug)] +struct TxState { + file: File, + serializer: CompositeSerializer, AllocScratch, SharedSerializeMap>, +} + +#[derive(Debug, Clone)] +pub struct LogFileJournalTx { + state: Arc>, +} + +#[derive(Debug)] +pub struct LogFileJournalRx { + tx: LogFileJournalTx, + buffer_pos: Mutex, + buffer: OwnedBuffer, +} + +impl LogFileJournalTx { + pub fn as_rx(&self) -> anyhow::Result { + let state = self.state.lock().unwrap(); + let file = state.file.try_clone()?; + + let buffer = OwnedBuffer::from_file(&file)?; + + // If the buffer exists we valid the magic number + let mut buffer_pos = 0; + let mut buffer_ptr = buffer.as_ref(); + if buffer_ptr.len() >= 8 { + let magic = u64::from_be_bytes(buffer_ptr[0..8].try_into().unwrap()); + if magic != JOURNAL_MAGIC_NUMBER { + return Err(anyhow::format_err!( + "invalid magic number of journal ({} vs {})", + magic, + JOURNAL_MAGIC_NUMBER + )); + } + buffer_ptr.advance(8); + buffer_pos += 8; + } else { + tracing::trace!("journal has no magic (could be empty?)"); + } + + Ok(LogFileJournalRx { + tx: self.clone(), + buffer_pos: Mutex::new(buffer_pos), + buffer, + }) + } +} + +impl LogFileJournal { + pub fn new(path: impl AsRef) -> anyhow::Result { + let file = std::fs::File::options() + .read(true) + .write(true) + .create(true) + .open(path)?; + Self::from_file(file) + } + + pub fn from_file(mut file: std::fs::File) -> anyhow::Result { + // Move to the end of the file and write the + // magic if one is needed + if file.seek(SeekFrom::End(0)).unwrap() == 0 { + let magic = JOURNAL_MAGIC_NUMBER; + let magic = magic.to_be_bytes(); + file.write_all(&magic)?; + } + + // Create the tx + let tx = LogFileJournalTx { + state: Arc::new(Mutex::new(TxState { + file: file.try_clone()?, + serializer: CompositeSerializer::new( + WriteSerializer::new(file), + AllocScratch::default(), + SharedSerializeMap::default(), + ), + })), + }; + + // First we create the readable journal + let rx = tx.as_rx()?; + + Ok(Self { rx, tx }) + } +} + +impl WritableJournal for LogFileJournalTx { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + tracing::debug!("journal event: {:?}", entry); + + let mut state = self.state.lock().unwrap(); + + // Write the header (with a record size of zero) + let record_type: JournalEntryRecordType = entry.archive_record_type(); + state.file.write_all(&(record_type as u16).to_be_bytes())?; + let offset_size = state.file.stream_position()?; + state.file.write_all(&[0u8; 6])?; // record and pad size (48 bits) + + // Now serialize the actual data to the log + let offset_start = state.file.stream_position()?; + entry.serialize_archive(&mut state.serializer)?; + let offset_end = state.file.stream_position()?; + let record_size = offset_end - offset_start; + + // If the alightment is out then fail + if record_size % 8 != 0 { + tracing::error!( + "alignment is out for journal event (type={:?}, record_size={}, alignment={})", + record_type, + record_size, + record_size % 8 + ); + } + + // Write the record and then move back to the end again + state.file.seek(SeekFrom::Start(offset_size))?; + state.file.write_all(&record_size.to_be_bytes()[2..8])?; + state.file.seek(SeekFrom::Start(offset_end))?; + + // Now write the actual data and update the offsets + Ok(record_size) + } +} + +impl ReadableJournal for LogFileJournalRx { + /// UNSAFE: This method uses unsafe operations to remove the need to zero + /// the buffer before its read the log entries into it + fn read(&self) -> anyhow::Result>> { + let mut buffer_pos = self.buffer_pos.lock().unwrap(); + + // Get a memory reference to the data on the disk at + // the current read location + let mut buffer_ptr = self.buffer.as_ref(); + buffer_ptr.advance(*buffer_pos); + loop { + // Read the headers and advance + if buffer_ptr.len() < 8 { + return Ok(None); + } + let header = { + let b = buffer_ptr; + + // If the next header is the magic itself then skip it. + // You may be wondering how a magic could appear later + // in the journal itself. This can happen if someone + // concat's multiple journals together to make a combined + // journal + if b[0..8] == JOURNAL_MAGIC_NUMBER_BYTES[0..8] { + buffer_ptr.advance(8); + *buffer_pos += 8; + continue; + } + + // Otherwise we decode the header + let header = JournalEntryHeader { + record_type: u16::from_be_bytes([b[0], b[1]]), + record_size: u64::from_be_bytes([0u8, 0u8, b[2], b[3], b[4], b[5], b[6], b[7]]), + }; + buffer_ptr.advance(8); + *buffer_pos += 8; + header + }; + + if header.record_size as usize > buffer_ptr.len() { + *buffer_pos += buffer_ptr.len(); + tracing::trace!( + "journal is corrupt (record_size={} vs remaining={})", + header.record_size, + buffer_ptr.len() + ); + return Ok(None); + } + + // Move the buffer position forward past the record + let entry = &buffer_ptr[..(header.record_size as usize)]; + buffer_ptr.advance(header.record_size as usize); + *buffer_pos += header.record_size as usize; + + // Now we read the entry + let record_type: JournalEntryRecordType = match header.record_type.try_into() { + Ok(t) => t, + Err(_) => { + tracing::debug!( + "unknown journal entry type ({}) - skipping", + header.record_type + ); + continue; + } + }; + + let record = unsafe { record_type.deserialize_archive(entry)? }; + return Ok(Some(record)); + } + } + + fn as_restarted(&self) -> anyhow::Result> { + let ret = self.tx.as_rx()?; + Ok(Box::new(ret)) + } +} + +impl WritableJournal for LogFileJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl ReadableJournal for LogFileJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.rx.as_restarted() + } +} + +impl Journal for LogFileJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.tx), Box::new(self.rx)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tracing_test::traced_test] + #[test] + pub fn test_save_and_load_journal_events() { + // Get a random file path + let file = tempfile::NamedTempFile::new().unwrap(); + + // Write some events to it + let journal = LogFileJournal::from_file(file.as_file().try_clone().unwrap()).unwrap(); + journal + .write(JournalEntry::CreatePipeV1 { fd1: 1, fd2: 2 }) + .unwrap(); + journal + .write(JournalEntry::SetThreadV1 { + id: 1, + call_stack: vec![11; 116].into(), + memory_stack: vec![22; 16].into(), + store_data: vec![33; 136].into(), + is_64bit: false, + }) + .unwrap(); + journal.write(JournalEntry::PortAddrClearV1).unwrap(); + drop(journal); + + // Read the events and validate + let journal = LogFileJournal::new(file.path()).unwrap(); + let event1 = journal.read().unwrap(); + let event2 = journal.read().unwrap(); + let event3 = journal.read().unwrap(); + let event4 = journal.read().unwrap(); + + // Check the events + assert_eq!(event1, Some(JournalEntry::CreatePipeV1 { fd1: 1, fd2: 2 })); + assert_eq!( + event2, + Some(JournalEntry::SetThreadV1 { + id: 1, + call_stack: vec![11; 116].into(), + memory_stack: vec![22; 16].into(), + store_data: vec![33; 136].into(), + is_64bit: false, + }) + ); + assert_eq!(event3, Some(JournalEntry::PortAddrClearV1)); + assert_eq!(event4, None); + + // Now write another event + journal + .write(JournalEntry::SocketSendV1 { + fd: 1234, + data: [12; 1024].to_vec().into(), + flags: 123, + is_64bit: true, + }) + .unwrap(); + + // The event should not be visible yet unless we reload the log file + assert_eq!(journal.read().unwrap(), None); + + // Reload the load file + let journal = LogFileJournal::new(file.path()).unwrap(); + + // Before we read it, we will throw in another event + journal + .write(JournalEntry::CreatePipeV1 { + fd1: 1234, + fd2: 5432, + }) + .unwrap(); + + let event1 = journal.read().unwrap(); + let event2 = journal.read().unwrap(); + let event3 = journal.read().unwrap(); + let event4 = journal.read().unwrap(); + let event5 = journal.read().unwrap(); + assert_eq!(event1, Some(JournalEntry::CreatePipeV1 { fd1: 1, fd2: 2 })); + assert_eq!( + event2, + Some(JournalEntry::SetThreadV1 { + id: 1, + call_stack: vec![11; 116].into(), + memory_stack: vec![22; 16].into(), + store_data: vec![33; 136].into(), + is_64bit: false, + }) + ); + assert_eq!(event3, Some(JournalEntry::PortAddrClearV1)); + assert_eq!( + event4, + Some(JournalEntry::SocketSendV1 { + fd: 1234, + data: [12; 1024].to_vec().into(), + flags: 123, + is_64bit: true, + }) + ); + assert_eq!(event5, None); + + // Load it again + let journal = LogFileJournal::new(file.path()).unwrap(); + + let event1 = journal.read().unwrap(); + let event2 = journal.read().unwrap(); + let event3 = journal.read().unwrap(); + let event4 = journal.read().unwrap(); + let event5 = journal.read().unwrap(); + let event6 = journal.read().unwrap(); + + tracing::info!("event1 {:?}", event1); + tracing::info!("event2 {:?}", event2); + tracing::info!("event3 {:?}", event3); + tracing::info!("event4 {:?}", event4); + tracing::info!("event5 {:?}", event5); + tracing::info!("event6 {:?}", event6); + + assert_eq!(event1, Some(JournalEntry::CreatePipeV1 { fd1: 1, fd2: 2 })); + assert_eq!( + event2, + Some(JournalEntry::SetThreadV1 { + id: 1, + call_stack: vec![11; 116].into(), + memory_stack: vec![22; 16].into(), + store_data: vec![33; 136].into(), + is_64bit: false, + }) + ); + assert_eq!(event3, Some(JournalEntry::PortAddrClearV1)); + assert_eq!( + event4, + Some(JournalEntry::SocketSendV1 { + fd: 1234, + data: [12; 1024].to_vec().into(), + flags: 123, + is_64bit: true, + }) + ); + assert_eq!( + event5, + Some(JournalEntry::CreatePipeV1 { + fd1: 1234, + fd2: 5432, + }) + ); + assert_eq!(event6, None); + } +} diff --git a/lib/journal/src/concrete/mod.rs b/lib/journal/src/concrete/mod.rs new file mode 100644 index 00000000000..9cc1abe2224 --- /dev/null +++ b/lib/journal/src/concrete/mod.rs @@ -0,0 +1,38 @@ +mod arc; +mod archived; +mod archived_from; +mod boxed; +mod buffered; +mod compacting; +#[cfg(feature = "log-file")] +mod compacting_log_file; +mod counting; +mod filter; +#[cfg(feature = "log-file")] +mod log_file; +mod null; +mod pipe; +mod printing; +mod recombined; +#[cfg(test)] +mod tests; +mod unsupported; + +pub(super) use super::*; + +pub use arc::*; +pub use archived::*; +pub use boxed::*; +pub use buffered::*; +pub use compacting::*; +#[cfg(feature = "log-file")] +pub use compacting_log_file::*; +pub use counting::*; +pub use filter::*; +#[cfg(feature = "log-file")] +pub use log_file::*; +pub use null::*; +pub use pipe::*; +pub use printing::*; +pub use recombined::*; +pub use unsupported::*; diff --git a/lib/journal/src/concrete/null.rs b/lib/journal/src/concrete/null.rs new file mode 100644 index 00000000000..779e9d8ce28 --- /dev/null +++ b/lib/journal/src/concrete/null.rs @@ -0,0 +1,34 @@ +use super::*; + +pub static NULL_JOURNAL: NullJournal = NullJournal { debug_print: false }; + +/// The null journal sends all the records into the abyss +#[derive(Debug, Default)] +pub struct NullJournal { + debug_print: bool, +} + +impl ReadableJournal for NullJournal { + fn read(&self) -> anyhow::Result>> { + Ok(None) + } + + fn as_restarted(&self) -> anyhow::Result> { + Ok(Box::::default()) + } +} + +impl WritableJournal for NullJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + if self.debug_print { + tracing::debug!("journal event: {:?}", entry); + } + Ok(entry.estimate_size() as u64) + } +} + +impl Journal for NullJournal { + fn split(self) -> (Box, Box) { + (Box::::default(), Box::::default()) + } +} diff --git a/lib/journal/src/concrete/pipe.rs b/lib/journal/src/concrete/pipe.rs new file mode 100644 index 00000000000..1f9f7d15a31 --- /dev/null +++ b/lib/journal/src/concrete/pipe.rs @@ -0,0 +1,104 @@ +use std::sync::mpsc::TryRecvError; +use std::sync::Mutex; +use std::sync::{mpsc, Arc}; + +use super::*; + +// The pipe journal will feed journal entries between two bi-directional ends +// of a pipe. +#[derive(Debug)] +pub struct PipeJournal { + tx: PipeJournalTx, + rx: PipeJournalRx, +} + +#[derive(Debug)] +pub struct PipeJournalRx { + receiver: Arc>>>, +} + +#[derive(Debug)] +pub struct PipeJournalTx { + sender: Arc>>>, +} + +impl PipeJournal { + pub fn channel() -> (Self, Self) { + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + + let end1 = PipeJournal { + tx: PipeJournalTx { + sender: Arc::new(Mutex::new(tx1)), + }, + rx: PipeJournalRx { + receiver: Arc::new(Mutex::new(rx2)), + }, + }; + + let end2 = PipeJournal { + tx: PipeJournalTx { + sender: Arc::new(Mutex::new(tx2)), + }, + rx: PipeJournalRx { + receiver: Arc::new(Mutex::new(rx1)), + }, + }; + + (end1, end2) + } +} + +impl WritableJournal for PipeJournalTx { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + let entry = entry.into_owned(); + let entry_size = entry.estimate_size(); + + let sender = self.sender.lock().unwrap(); + sender.send(entry).map_err(|err| { + anyhow::format_err!("failed to send journal event through the pipe - {}", err) + })?; + Ok(entry_size as u64) + } +} + +impl ReadableJournal for PipeJournalRx { + fn read(&self) -> anyhow::Result>> { + let rx = self.receiver.lock().unwrap(); + match rx.try_recv() { + Ok(e) => Ok(Some(e)), + Err(TryRecvError::Empty) => Ok(None), + Err(TryRecvError::Disconnected) => Err(anyhow::format_err!( + "failed to receive journal event from the pipe as its disconnected" + )), + } + } + + fn as_restarted(&self) -> anyhow::Result> { + Ok(Box::new(PipeJournalRx { + receiver: self.receiver.clone(), + })) + } +} + +impl WritableJournal for PipeJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl ReadableJournal for PipeJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.rx.as_restarted() + } +} + +impl Journal for PipeJournal { + fn split(self) -> (Box, Box) { + (Box::new(self.tx), Box::new(self.rx)) + } +} diff --git a/lib/journal/src/concrete/printing.rs b/lib/journal/src/concrete/printing.rs new file mode 100644 index 00000000000..c12ea6e2fac --- /dev/null +++ b/lib/journal/src/concrete/printing.rs @@ -0,0 +1,335 @@ +use std::fmt; + +use super::*; +use wasmer_wasix_types::wasi; + +/// Type of printing mode to use +#[derive(Debug)] +pub enum JournalPrintingMode { + Text, + Json, +} +impl Default for JournalPrintingMode { + fn default() -> Self { + Self::Text + } +} + +/// The default for runtime is to use the unsupported journal +/// which will fail to write journal entries if one attempts to do so. +#[derive(Debug, Default)] +pub struct PrintingJournal { + mode: JournalPrintingMode, +} + +impl PrintingJournal { + pub fn new(mode: JournalPrintingMode) -> Self { + Self { mode } + } +} + +impl ReadableJournal for PrintingJournal { + fn read(&self) -> anyhow::Result>> { + Ok(None) + } + + fn as_restarted(&self) -> anyhow::Result> { + Ok(Box::::default()) + } +} + +impl WritableJournal for PrintingJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + match self.mode { + JournalPrintingMode::Text => println!("{}", entry), + JournalPrintingMode::Json => { + println!("{}", serde_json::to_string_pretty(&entry)?) + } + } + Ok(entry.estimate_size() as u64) + } +} + +impl Journal for PrintingJournal { + fn split(self) -> (Box, Box) { + ( + Box::::default(), + Box::::default(), + ) + } +} + +impl<'a> fmt::Display for JournalEntry<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JournalEntry::InitModuleV1 { wasm_hash } => { + write!(f, "init-module (hash={:x?})", wasm_hash) + } + JournalEntry::UpdateMemoryRegionV1 { region, data } => write!( + f, + "memory-update (start={}, end={}, data.len={})", + region.start, + region.end, + data.len() + ), + JournalEntry::ProcessExitV1 { exit_code } => { + write!(f, "process-exit (code={:?})", exit_code) + } + JournalEntry::SetThreadV1 { + id, + call_stack, + memory_stack, + store_data, + .. + } => write!( + f, + "thread-update (id={}, call-stack.len={}, mem-stack.len={}, store-size={}", + id, + call_stack.len(), + memory_stack.len(), + store_data.len(), + ), + JournalEntry::CloseThreadV1 { id, exit_code } => { + write!(f, "thread-close (id={}, code={:?})", id, exit_code) + } + JournalEntry::FileDescriptorSeekV1 { fd, offset, whence } => write!( + f, + "fd-seek (fd={}, offset={}, whence={:?})", + fd, offset, whence + ), + JournalEntry::FileDescriptorWriteV1 { + fd, offset, data, .. + } => write!( + f, + "fd-write (fd={}, offset={}, data.len={})", + fd, + offset, + data.len() + ), + JournalEntry::SetClockTimeV1 { clock_id, time } => { + write!(f, "set-clock-time (id={:?}, time={})", clock_id, time) + } + JournalEntry::CloseFileDescriptorV1 { fd } => write!(f, "fd-close (fd={})", fd), + JournalEntry::OpenFileDescriptorV1 { + fd, path, o_flags, .. + } => { + if o_flags.contains(wasi::Oflags::CREATE) { + if o_flags.contains(wasi::Oflags::TRUNC) { + write!(f, "fd-create-new (fd={}, path={})", fd, path) + } else { + write!(f, "fd-create (fd={}, path={})", fd, path) + } + } else if o_flags.contains(wasi::Oflags::TRUNC) { + write!(f, "fd-open-new (fd={}, path={})", fd, path) + } else { + write!(f, "fd-open (fd={}, path={})", fd, path) + } + } + JournalEntry::RenumberFileDescriptorV1 { old_fd, new_fd } => { + write!(f, "fd-renumber (old={}, new={})", old_fd, new_fd) + } + JournalEntry::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + } => write!( + f, + "fd-duplicate (original={}, copied={})", + original_fd, copied_fd + ), + JournalEntry::CreateDirectoryV1 { path, .. } => { + write!(f, "path-create-dir (path={})", path) + } + JournalEntry::RemoveDirectoryV1 { path, .. } => { + write!(f, "path-remove-dir (path={})", path) + } + JournalEntry::PathSetTimesV1 { + path, + st_atim, + st_mtim, + .. + } => write!( + f, + "path-set-times (path={}, atime={}, mtime={}))", + path, st_atim, st_mtim + ), + JournalEntry::FileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + .. + } => write!( + f, + "fd-set-times (fd={}, atime={}, mtime={})", + fd, st_atim, st_mtim + ), + JournalEntry::FileDescriptorSetFlagsV1 { fd, flags } => { + write!(f, "fd-set-flags (fd={}, flags={:?})", fd, flags) + } + JournalEntry::FileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + } => write!( + f, + "fd-set-rights (fd={}, base={:?}, inherited={:?})", + fd, fs_rights_base, fs_rights_inheriting + ), + JournalEntry::FileDescriptorSetSizeV1 { fd, st_size } => { + write!(f, "fd-set-size (fd={}, size={})", fd, st_size) + } + JournalEntry::FileDescriptorAdviseV1 { + fd, offset, len, .. + } => write!(f, "fd-advise (fd={}, offset={}, len={})", fd, offset, len), + JournalEntry::FileDescriptorAllocateV1 { fd, offset, len } => { + write!(f, "fd-allocate (fd={}, offset={}, len={})", fd, offset, len) + } + JournalEntry::CreateHardLinkV1 { + old_path, new_path, .. + } => write!(f, "path-link (from={}, to={})", old_path, new_path), + JournalEntry::CreateSymbolicLinkV1 { + old_path, new_path, .. + } => write!(f, "path-symlink (from={}, to={})", old_path, new_path), + JournalEntry::UnlinkFileV1 { path, .. } => write!(f, "path-unlink (path={})", path), + JournalEntry::PathRenameV1 { + old_path, new_path, .. + } => write!( + f, + "path-rename (old-path={}, new-path={})", + old_path, new_path + ), + JournalEntry::ChangeDirectoryV1 { path } => write!(f, "chdir (path={})", path), + JournalEntry::EpollCreateV1 { fd } => write!(f, "epoll-create (fd={})", fd), + JournalEntry::EpollCtlV1 { epfd, op, fd, .. } => { + write!(f, "epoll-ctl (epfd={}, op={:?}, fd={})", epfd, op, fd) + } + JournalEntry::TtySetV1 { tty, line_feeds } => write!( + f, + "tty-set (echo={}, buffering={}, feeds={})", + tty.echo, tty.line_buffered, line_feeds + ), + JournalEntry::CreatePipeV1 { fd1, fd2 } => { + write!(f, "fd-pipe (fd1={}, fd2={})", fd1, fd2) + } + JournalEntry::CreateEventV1 { + initial_val, fd, .. + } => write!(f, "fd-event (fd={}, initial={})", fd, initial_val), + JournalEntry::PortAddAddrV1 { cidr } => { + write!(f, "port-addr-add (ip={}, prefix={})", cidr.ip, cidr.prefix) + } + JournalEntry::PortDelAddrV1 { addr } => write!(f, "port-addr-del (addr={})", addr), + JournalEntry::PortAddrClearV1 => write!(f, "port-addr-clear"), + JournalEntry::PortBridgeV1 { network, .. } => { + write!(f, "port-bridge (network={})", network) + } + JournalEntry::PortUnbridgeV1 => write!(f, "port-unbridge"), + JournalEntry::PortDhcpAcquireV1 => write!(f, "port-dhcp-acquire"), + JournalEntry::PortGatewaySetV1 { ip } => write!(f, "port-gateway-set (ip={})", ip), + JournalEntry::PortRouteAddV1 { + cidr, via_router, .. + } => write!( + f, + "port-route-add (ip={}, prefix={}, via_router={})", + cidr.ip, cidr.prefix, via_router + ), + JournalEntry::PortRouteClearV1 => write!(f, "port-route-clear"), + JournalEntry::PortRouteDelV1 { ip } => write!(f, "port-route-del (ip={})", ip), + JournalEntry::SocketOpenV1 { af, ty, pt, fd } => { + write!( + f, + "sock-open (fd={}, af={:?}, ty={:?}, pt={:?})", + fd, af, ty, pt + ) + } + JournalEntry::SocketListenV1 { fd, backlog } => { + write!(f, "sock-listen (fd={}, backlog={})", fd, backlog) + } + JournalEntry::SocketBindV1 { fd, addr } => { + write!(f, "sock-bind (fd={}, addr={})", fd, addr) + } + JournalEntry::SocketConnectedV1 { fd, addr } => { + write!(f, "sock-connect (fd={}, addr={})", fd, addr) + } + JournalEntry::SocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + .. + } => write!( + f, + "sock-accept (listen-fd={}, sock_fd={}, peer={})", + listen_fd, fd, peer_addr + ), + JournalEntry::SocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + } => write!( + f, + "sock-join-mcast-ipv4 (fd={}, addr={}, iface={})", + fd, multiaddr, iface + ), + JournalEntry::SocketJoinIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => write!( + f, + "sock-join-mcast-ipv6 (fd={}, addr={}, iface={})", + fd, multiaddr, iface + ), + JournalEntry::SocketLeaveIpv4MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => write!( + f, + "sock-leave-mcast-ipv4 (fd={}, addr={}, iface={})", + fd, multiaddr, iface + ), + JournalEntry::SocketLeaveIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => write!( + f, + "sock-leave-mcast-ipv6 (fd={}, addr={}, iface={})", + fd, multiaddr, iface + ), + JournalEntry::SocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + } => write!( + f, + "sock-send-file (sock-fd={}, file-fd={}, offset={}, count={})", + socket_fd, file_fd, offset, count + ), + JournalEntry::SocketSendToV1 { fd, data, addr, .. } => write!( + f, + "sock-send-to (fd={}, data.len={}, addr={})", + fd, + data.len(), + addr + ), + JournalEntry::SocketSendV1 { fd, data, .. } => { + write!(f, "sock-send (fd={}, data.len={}", fd, data.len()) + } + JournalEntry::SocketSetOptFlagV1 { fd, opt, flag } => { + write!(f, "sock-set-opt (fd={}, opt={:?}, flag={})", fd, opt, flag) + } + JournalEntry::SocketSetOptSizeV1 { fd, opt, size } => { + write!(f, "sock-set-opt (fd={}, opt={:?}, size={})", fd, opt, size) + } + JournalEntry::SocketSetOptTimeV1 { fd, ty, time } => { + write!(f, "sock-set-opt (fd={}, opt={:?}, time={:?})", fd, ty, time) + } + JournalEntry::SocketShutdownV1 { fd, how } => { + write!(f, "sock-shutdown (fd={}, how={:?})", fd, how) + } + JournalEntry::SnapshotV1 { when, trigger } => { + write!(f, "snapshot (when={:?}, trigger={:?})", when, trigger) + } + } + } +} diff --git a/lib/journal/src/concrete/recombined.rs b/lib/journal/src/concrete/recombined.rs new file mode 100644 index 00000000000..05ae9e53cb9 --- /dev/null +++ b/lib/journal/src/concrete/recombined.rs @@ -0,0 +1,34 @@ +use super::*; + +pub struct RecombinedJournal { + tx: Box, + rx: Box, +} + +impl RecombinedJournal { + pub fn new(tx: Box, rx: Box) -> Self { + Self { tx, rx } + } +} + +impl WritableJournal for RecombinedJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + self.tx.write(entry) + } +} + +impl ReadableJournal for RecombinedJournal { + fn read(&self) -> anyhow::Result>> { + self.rx.read() + } + + fn as_restarted(&self) -> anyhow::Result> { + self.rx.as_restarted() + } +} + +impl Journal for RecombinedJournal { + fn split(self) -> (Box, Box) { + (self.tx, self.rx) + } +} diff --git a/lib/journal/src/concrete/tests.rs b/lib/journal/src/concrete/tests.rs new file mode 100644 index 00000000000..cc48dedb4af --- /dev/null +++ b/lib/journal/src/concrete/tests.rs @@ -0,0 +1,715 @@ +use std::{ + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, + time::{Duration, SystemTime}, +}; + +use super::*; +use rkyv::ser::serializers::{ + AllocScratch, CompositeSerializer, SharedSerializeMap, WriteSerializer, +}; +use wasmer_wasix_types::wasi; + +pub fn run_test<'a>(record: JournalEntry<'a>) { + tracing::info!("record: {:?}", record); + + // Determine the record type + let record_type = record.archive_record_type(); + tracing::info!("record_type: {:?}", record_type); + + // Serialize it + let mut buffer = Vec::new(); + let mut serializer = CompositeSerializer::new( + WriteSerializer::new(&mut buffer), + AllocScratch::default(), + SharedSerializeMap::default(), + ); + + record.clone().serialize_archive(&mut serializer).unwrap(); + let buffer = &buffer[..]; + if buffer.len() < 20 { + tracing::info!("buffer: {:x?}", buffer); + } else { + tracing::info!("buffer_len: {}", buffer.len()); + } + + // Deserialize it + let record2 = unsafe { record_type.deserialize_archive(buffer).unwrap() }; + tracing::info!("record2: {:?}", record2); + + // Check it + assert_eq!(record, record2); + + // Now make it static and check it again + let record3 = record2.into_owned(); + tracing::info!("record3: {:?}", record3); + assert_eq!(record, record3); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_init_module() { + run_test(JournalEntry::InitModuleV1 { + wasm_hash: [13u8; 8], + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_process_exit() { + run_test(JournalEntry::ProcessExitV1 { + exit_code: Some(wasi::ExitCode::Errno(wasi::Errno::Fault)), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_set_thread() { + run_test(JournalEntry::SetThreadV1 { + id: 1234u32, + call_stack: vec![1, 2, 3].into(), + memory_stack: vec![4, 5, 6, 7].into(), + store_data: vec![10, 11].into(), + is_64bit: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_close_thread() { + run_test(JournalEntry::CloseThreadV1 { + id: 987u32, + exit_code: Some(wasi::ExitCode::Errno(wasi::Errno::Fault)), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_descriptor_seek() { + run_test(JournalEntry::FileDescriptorSeekV1 { + fd: 765u32, + offset: 9183722450971234i64, + whence: wasi::Whence::End, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_descriptor_write() { + run_test(JournalEntry::FileDescriptorWriteV1 { + fd: 54321u32, + offset: 13897412934u64, + data: vec![74u8, 98u8, 36u8].into(), + is_64bit: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_update_memory() { + run_test(JournalEntry::UpdateMemoryRegionV1 { + region: 76u64..8237453u64, + data: [74u8; 40960].to_vec().into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_set_clock_time() { + run_test(JournalEntry::SetClockTimeV1 { + clock_id: wasi::Snapshot0Clockid::Realtime, + time: 7912837412934u64, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_open_file_descriptor() { + run_test(JournalEntry::OpenFileDescriptorV1 { + fd: 298745u32, + dirfd: 23458922u32, + dirflags: 134512345, + path: "/blah".into(), + o_flags: wasi::Oflags::all(), + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + fs_flags: wasi::Fdflags::all(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_close_descriptor() { + run_test(JournalEntry::CloseFileDescriptorV1 { fd: 23845732u32 }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_renumber_file_descriptor() { + run_test(JournalEntry::RenumberFileDescriptorV1 { + old_fd: 27834u32, + new_fd: 398452345u32, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_duplicate_file_descriptor() { + run_test(JournalEntry::DuplicateFileDescriptorV1 { + original_fd: 23482934u32, + copied_fd: 9384529u32, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_create_directory() { + run_test(JournalEntry::CreateDirectoryV1 { + fd: 238472u32, + path: "/joasjdf/asdfn".into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_remove_directory() { + run_test(JournalEntry::RemoveDirectoryV1 { + fd: 23894952u32, + path: "/blahblah".into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_path_set_times() { + run_test(JournalEntry::PathSetTimesV1 { + fd: 1238934u32, + flags: 234523, + path: "/".into(), + st_atim: 923452345, + st_mtim: 350, + fst_flags: wasi::Fstflags::all(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_file_descriptor_set_times() { + run_test(JournalEntry::FileDescriptorSetTimesV1 { + fd: 898785u32, + st_atim: 29834952345, + st_mtim: 239845892345, + fst_flags: wasi::Fstflags::all(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_file_descriptor_set_size() { + run_test(JournalEntry::FileDescriptorSetSizeV1 { + fd: 34958234u32, + st_size: 234958293845u64, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_file_descriptor_set_flags() { + run_test(JournalEntry::FileDescriptorSetFlagsV1 { + fd: 982348752u32, + flags: wasi::Fdflags::all(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_file_descriptor_set_rights() { + run_test(JournalEntry::FileDescriptorSetRightsV1 { + fd: 872345u32, + fs_rights_base: wasi::Rights::all(), + fs_rights_inheriting: wasi::Rights::all(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_file_descriptor_advise() { + run_test(JournalEntry::FileDescriptorAdviseV1 { + fd: 298434u32, + offset: 92834529092345, + len: 23485928345, + advice: wasi::Advice::Random, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_file_descriptor_allocate() { + run_test(JournalEntry::FileDescriptorAllocateV1 { + fd: 2934852, + offset: 23489582934523, + len: 9845982345, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_create_hard_link() { + run_test(JournalEntry::CreateHardLinkV1 { + old_fd: 324983845, + old_path: "/asjdfiasidfasdf".into(), + old_flags: 234857, + new_fd: 34958345, + new_path: "/ashdufnasd".into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_create_symbolic_link() { + run_test(JournalEntry::CreateSymbolicLinkV1 { + old_path: "/asjbndfjasdf/asdafasdf".into(), + fd: 235422345, + new_path: "/asdf".into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_unlink_file() { + run_test(JournalEntry::UnlinkFileV1 { + fd: 32452345, + path: "/asdfasd".into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_path_rename() { + run_test(JournalEntry::PathRenameV1 { + old_fd: 32451345, + old_path: "/asdfasdfas/asdfasdf".into(), + new_fd: 23452345, + new_path: "/ahgfdfghdfghdfgh".into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_change_directory() { + run_test(JournalEntry::ChangeDirectoryV1 { + path: "/etc".to_string().into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_epoll_create() { + run_test(JournalEntry::EpollCreateV1 { fd: 45384752 }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_epoll_ctl() { + run_test(JournalEntry::EpollCtlV1 { + epfd: 34523455, + op: wasi::EpollCtl::Unknown, + fd: 23452345, + event: Some(wasi::EpollEventCtl { + events: wasi::EpollType::all(), + ptr: 32452345, + fd: 23452345, + data1: 1235245756, + data2: 23452345, + }), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_tty_set() { + run_test(JournalEntry::TtySetV1 { + tty: wasi::Tty { + cols: 1234, + rows: 6754, + width: 4563456, + height: 345, + stdin_tty: true, + stdout_tty: false, + stderr_tty: true, + echo: true, + line_buffered: true, + }, + line_feeds: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_create_pipe() { + run_test(JournalEntry::CreatePipeV1 { + fd1: 3452345, + fd2: 2345163, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_create_event() { + run_test(JournalEntry::CreateEventV1 { + initial_val: 13451345, + flags: 2343, + fd: 5836544, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_port_add_addr() { + run_test(JournalEntry::PortAddAddrV1 { + cidr: JournalIpCidrV1 { + ip: Ipv4Addr::LOCALHOST.into(), + prefix: 24, + } + .into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_del_addr() { + run_test(JournalEntry::PortDelAddrV1 { + addr: Ipv6Addr::LOCALHOST.into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_addr_clear() { + run_test(JournalEntry::PortAddrClearV1); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_port_bridge() { + run_test(JournalEntry::PortBridgeV1 { + network: "mynetwork".into(), + token: format!("blh blah").into(), + security: JournalStreamSecurityV1::ClassicEncryption.into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_unbridge() { + run_test(JournalEntry::PortUnbridgeV1); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_dhcp_acquire() { + run_test(JournalEntry::PortDhcpAcquireV1); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_gateway_set() { + run_test(JournalEntry::PortGatewaySetV1 { + ip: Ipv4Addr::new(12, 34, 136, 220).into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_route_add() { + run_test(JournalEntry::PortRouteAddV1 { + cidr: JournalIpCidrV1 { + ip: Ipv4Addr::LOCALHOST.into(), + prefix: 24, + } + .into(), + via_router: Ipv4Addr::LOCALHOST.into(), + preferred_until: Some(Duration::MAX), + expires_at: Some(Duration::ZERO), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_route_clear() { + run_test(JournalEntry::PortRouteClearV1); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_route_del() { + run_test(JournalEntry::PortRouteDelV1 { + ip: Ipv4Addr::BROADCAST.into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_open() { + run_test(JournalEntry::SocketOpenV1 { + af: wasi::Addressfamily::Inet6, + ty: wasi::Socktype::Stream, + pt: wasi::SockProto::Tcp, + fd: 23452345, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_listen() { + run_test(JournalEntry::SocketListenV1 { + fd: 12341234, + backlog: 123, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_bind() { + run_test(JournalEntry::SocketBindV1 { + fd: 2341234, + addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 1234), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_connected() { + run_test(JournalEntry::SocketConnectedV1 { + fd: 12341, + addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 1234), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_accepted() { + run_test(JournalEntry::SocketAcceptedV1 { + listen_fd: 21234, + fd: 1, + peer_addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 3452), + fd_flags: wasi::Fdflags::all(), + non_blocking: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_join_ipv4_multicast() { + run_test(JournalEntry::SocketJoinIpv4MulticastV1 { + fd: 12, + multiaddr: Ipv4Addr::new(123, 123, 123, 123).into(), + iface: Ipv4Addr::new(128, 0, 0, 1).into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_join_ipv6_multicast() { + run_test(JournalEntry::SocketJoinIpv6MulticastV1 { + fd: 12, + multi_addr: Ipv6Addr::new(123, 123, 123, 123, 1234, 12663, 31, 1324).into(), + iface: 23541, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_leave_ipv4_multicast() { + run_test(JournalEntry::SocketLeaveIpv4MulticastV1 { + fd: 12, + multi_addr: Ipv4Addr::new(123, 123, 123, 123).into(), + iface: Ipv4Addr::new(128, 0, 0, 1).into(), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_leave_ipv6_multicast() { + run_test(JournalEntry::SocketLeaveIpv6MulticastV1 { + fd: 12, + multi_addr: Ipv6Addr::new(123, 123, 123, 123, 1234, 12663, 31, 1324).into(), + iface: 23541, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_send_file() { + run_test(JournalEntry::SocketSendFileV1 { + socket_fd: 22234, + file_fd: 989, + offset: 124, + count: 345673456234651234, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_send_to() { + run_test(JournalEntry::SocketSendToV1 { + fd: 123, + data: [98u8; 102400].to_vec().into(), + flags: 1234, + addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 3452), + is_64bit: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_send() { + run_test(JournalEntry::SocketSendV1 { + fd: 123, + data: [98u8; 102400].to_vec().into(), + flags: 1234, + is_64bit: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_set_opt_flag() { + run_test(JournalEntry::SocketSetOptFlagV1 { + fd: 0, + opt: wasi::Sockoption::Linger, + flag: true, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_set_opt_size() { + run_test(JournalEntry::SocketSetOptSizeV1 { + fd: 15, + opt: wasi::Sockoption::Linger, + size: 234234, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_set_opt_time() { + run_test(JournalEntry::SocketSetOptTimeV1 { + fd: 0, + ty: SocketOptTimeType::AcceptTimeout, + time: Some(Duration::ZERO), + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_socket_shutdown() { + run_test(JournalEntry::SocketShutdownV1 { + fd: 123, + how: SocketShutdownHow::Both, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_snapshot() { + run_test(JournalEntry::SnapshotV1 { + when: SystemTime::now(), + trigger: SnapshotTrigger::Idle, + }); +} + +#[tracing_test::traced_test] +#[test] +pub fn test_record_alignment() { + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!( + std::mem::align_of::(), + 8 + ); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); + assert_eq!(std::mem::align_of::(), 8); +} diff --git a/lib/journal/src/concrete/unsupported.rs b/lib/journal/src/concrete/unsupported.rs new file mode 100644 index 00000000000..a7e52a78f21 --- /dev/null +++ b/lib/journal/src/concrete/unsupported.rs @@ -0,0 +1,34 @@ +use super::*; + +pub static UNSUPPORTED_JOURNAL: UnsupportedJournal = UnsupportedJournal {}; + +/// The default for runtime is to use the unsupported journal +/// which will fail to write journal entries if one attempts to do so. +#[derive(Debug, Default)] +pub struct UnsupportedJournal {} + +impl ReadableJournal for UnsupportedJournal { + fn read(&self) -> anyhow::Result>> { + Ok(None) + } + + fn as_restarted(&self) -> anyhow::Result> { + Ok(Box::::default()) + } +} + +impl WritableJournal for UnsupportedJournal { + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result { + tracing::debug!("journal event: {:?}", entry); + Err(anyhow::format_err!("unsupported")) + } +} + +impl Journal for UnsupportedJournal { + fn split(self) -> (Box, Box) { + ( + Box::::default(), + Box::::default(), + ) + } +} diff --git a/lib/journal/src/entry.rs b/lib/journal/src/entry.rs new file mode 100644 index 00000000000..aace436d22d --- /dev/null +++ b/lib/journal/src/entry.rs @@ -0,0 +1,770 @@ +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::net::{Shutdown, SocketAddr}; +use std::time::{Duration, SystemTime}; +use std::{borrow::Cow, ops::Range}; +use virtual_net::{IpCidr, StreamSecurity}; +use wasmer_wasix_types::wasi::{ + Addressfamily, Advice, EpollCtl, EpollEventCtl, EventFdFlags, ExitCode, Fdflags, FileDelta, + Filesize, Fstflags, LookupFlags, Oflags, Rights, SiFlags, Snapshot0Clockid, SockProto, + Sockoption, Socktype, Timestamp, Tty, Whence, +}; + +use crate::{base64, SnapshotTrigger}; + +type Fd = u32; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SocketJournalEvent { + TcpListen { + listen_addr: SocketAddr, + reuse_port: bool, + reuse_addr: bool, + }, + TcpStream { + local_addr: SocketAddr, + peer_addr: SocketAddr, + }, + UdpSocket { + local_addr: SocketAddr, + peer_addr: SocketAddr, + reuse_port: bool, + reuse_addr: bool, + }, + Icmp { + addr: SocketAddr, + }, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash)] +#[serde(rename_all = "snake_case")] +pub enum SocketShutdownHow { + Read, + Write, + Both, +} +impl From for SocketShutdownHow { + fn from(value: Shutdown) -> Self { + match value { + Shutdown::Read => Self::Read, + Shutdown::Write => Self::Write, + Shutdown::Both => Self::Both, + } + } +} +impl From for Shutdown { + fn from(value: SocketShutdownHow) -> Self { + match value { + SocketShutdownHow::Read => Self::Read, + SocketShutdownHow::Write => Self::Write, + SocketShutdownHow::Both => Self::Both, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq, Hash)] +#[serde(rename_all = "snake_case")] +pub enum SocketOptTimeType { + ReadTimeout, + WriteTimeout, + AcceptTimeout, + ConnectTimeout, + BindTimeout, + Linger, +} + +/// Represents a log entry in a snapshot log stream that represents the total +/// state of a WASM process at a point in time. +#[allow(clippy::large_enum_variant)] +#[derive(Derivative, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derivative(Debug)] +#[serde(rename_all = "snake_case")] +pub enum JournalEntry<'a> { + InitModuleV1 { + wasm_hash: [u8; 8], + }, + UpdateMemoryRegionV1 { + region: Range, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + data: Cow<'a, [u8]>, + }, + ProcessExitV1 { + exit_code: Option, + }, + SetThreadV1 { + id: u32, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + call_stack: Cow<'a, [u8]>, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + memory_stack: Cow<'a, [u8]>, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + store_data: Cow<'a, [u8]>, + is_64bit: bool, + }, + CloseThreadV1 { + id: u32, + exit_code: Option, + }, + FileDescriptorSeekV1 { + fd: Fd, + offset: FileDelta, + whence: Whence, + }, + FileDescriptorWriteV1 { + fd: Fd, + offset: u64, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + data: Cow<'a, [u8]>, + is_64bit: bool, + }, + SetClockTimeV1 { + clock_id: Snapshot0Clockid, + time: Timestamp, + }, + CloseFileDescriptorV1 { + fd: Fd, + }, + OpenFileDescriptorV1 { + fd: Fd, + dirfd: Fd, + dirflags: LookupFlags, + path: Cow<'a, str>, + o_flags: Oflags, + #[derivative(Debug = "ignore")] + fs_rights_base: Rights, + #[derivative(Debug = "ignore")] + fs_rights_inheriting: Rights, + fs_flags: Fdflags, + }, + RenumberFileDescriptorV1 { + old_fd: Fd, + new_fd: Fd, + }, + DuplicateFileDescriptorV1 { + original_fd: Fd, + copied_fd: Fd, + }, + CreateDirectoryV1 { + fd: Fd, + path: Cow<'a, str>, + }, + RemoveDirectoryV1 { + fd: Fd, + path: Cow<'a, str>, + }, + PathSetTimesV1 { + fd: Fd, + flags: LookupFlags, + path: Cow<'a, str>, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, + }, + FileDescriptorSetTimesV1 { + fd: Fd, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, + }, + FileDescriptorSetFlagsV1 { + fd: Fd, + flags: Fdflags, + }, + FileDescriptorSetRightsV1 { + fd: Fd, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + }, + FileDescriptorSetSizeV1 { + fd: Fd, + st_size: Filesize, + }, + FileDescriptorAdviseV1 { + fd: Fd, + offset: Filesize, + len: Filesize, + advice: Advice, + }, + FileDescriptorAllocateV1 { + fd: Fd, + offset: Filesize, + len: Filesize, + }, + CreateHardLinkV1 { + old_fd: Fd, + old_path: Cow<'a, str>, + old_flags: LookupFlags, + new_fd: Fd, + new_path: Cow<'a, str>, + }, + CreateSymbolicLinkV1 { + old_path: Cow<'a, str>, + fd: Fd, + new_path: Cow<'a, str>, + }, + UnlinkFileV1 { + fd: Fd, + path: Cow<'a, str>, + }, + PathRenameV1 { + old_fd: Fd, + old_path: Cow<'a, str>, + new_fd: Fd, + new_path: Cow<'a, str>, + }, + ChangeDirectoryV1 { + path: Cow<'a, str>, + }, + EpollCreateV1 { + fd: Fd, + }, + EpollCtlV1 { + epfd: Fd, + op: EpollCtl, + fd: Fd, + event: Option, + }, + TtySetV1 { + tty: Tty, + line_feeds: bool, + }, + CreatePipeV1 { + fd1: Fd, + fd2: Fd, + }, + CreateEventV1 { + initial_val: u64, + flags: EventFdFlags, + fd: Fd, + }, + PortAddAddrV1 { + cidr: IpCidr, + }, + PortDelAddrV1 { + addr: IpAddr, + }, + PortAddrClearV1, + PortBridgeV1 { + network: Cow<'a, str>, + token: Cow<'a, str>, + security: StreamSecurity, + }, + PortUnbridgeV1, + PortDhcpAcquireV1, + PortGatewaySetV1 { + ip: IpAddr, + }, + PortRouteAddV1 { + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, + }, + PortRouteClearV1, + PortRouteDelV1 { + ip: IpAddr, + }, + SocketOpenV1 { + af: Addressfamily, + ty: Socktype, + pt: SockProto, + fd: Fd, + }, + SocketListenV1 { + fd: Fd, + backlog: u32, + }, + SocketBindV1 { + fd: Fd, + addr: SocketAddr, + }, + SocketConnectedV1 { + fd: Fd, + addr: SocketAddr, + }, + SocketAcceptedV1 { + listen_fd: Fd, + fd: Fd, + peer_addr: SocketAddr, + fd_flags: Fdflags, + non_blocking: bool, + }, + SocketJoinIpv4MulticastV1 { + fd: Fd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + }, + SocketJoinIpv6MulticastV1 { + fd: Fd, + multi_addr: Ipv6Addr, + iface: u32, + }, + SocketLeaveIpv4MulticastV1 { + fd: Fd, + multi_addr: Ipv4Addr, + iface: Ipv4Addr, + }, + SocketLeaveIpv6MulticastV1 { + fd: Fd, + multi_addr: Ipv6Addr, + iface: u32, + }, + SocketSendFileV1 { + socket_fd: Fd, + file_fd: Fd, + offset: Filesize, + count: Filesize, + }, + SocketSendToV1 { + fd: Fd, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + data: Cow<'a, [u8]>, + flags: SiFlags, + addr: SocketAddr, + is_64bit: bool, + }, + SocketSendV1 { + fd: Fd, + #[derivative(Debug = "ignore")] + #[serde(with = "base64")] + data: Cow<'a, [u8]>, + flags: SiFlags, + is_64bit: bool, + }, + SocketSetOptFlagV1 { + fd: Fd, + opt: Sockoption, + flag: bool, + }, + SocketSetOptSizeV1 { + fd: Fd, + opt: Sockoption, + size: u64, + }, + SocketSetOptTimeV1 { + fd: Fd, + ty: SocketOptTimeType, + time: Option, + }, + SocketShutdownV1 { + fd: Fd, + how: SocketShutdownHow, + }, + /// Represents the marker for the end of a snapshot + SnapshotV1 { + when: SystemTime, + trigger: SnapshotTrigger, + }, +} + +impl<'a> JournalEntry<'a> { + pub fn into_owned(self) -> JournalEntry<'static> { + match self { + Self::InitModuleV1 { wasm_hash } => JournalEntry::InitModuleV1 { wasm_hash }, + Self::UpdateMemoryRegionV1 { region, data } => JournalEntry::UpdateMemoryRegionV1 { + region, + data: data.into_owned().into(), + }, + Self::ProcessExitV1 { exit_code } => JournalEntry::ProcessExitV1 { exit_code }, + Self::SetThreadV1 { + id, + call_stack, + memory_stack, + store_data, + is_64bit, + } => JournalEntry::SetThreadV1 { + id, + call_stack: call_stack.into_owned().into(), + memory_stack: memory_stack.into_owned().into(), + store_data: store_data.into_owned().into(), + is_64bit, + }, + Self::CloseThreadV1 { id, exit_code } => JournalEntry::CloseThreadV1 { id, exit_code }, + Self::FileDescriptorSeekV1 { fd, offset, whence } => { + JournalEntry::FileDescriptorSeekV1 { fd, offset, whence } + } + Self::FileDescriptorWriteV1 { + fd, + offset, + data, + is_64bit, + } => JournalEntry::FileDescriptorWriteV1 { + fd, + offset, + data: data.into_owned().into(), + is_64bit, + }, + Self::SetClockTimeV1 { clock_id, time } => { + JournalEntry::SetClockTimeV1 { clock_id, time } + } + Self::CloseFileDescriptorV1 { fd } => JournalEntry::CloseFileDescriptorV1 { fd }, + Self::OpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + path, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + } => JournalEntry::OpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + path: path.into_owned().into(), + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + }, + Self::RenumberFileDescriptorV1 { old_fd, new_fd } => { + JournalEntry::RenumberFileDescriptorV1 { old_fd, new_fd } + } + Self::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + } => JournalEntry::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + }, + Self::CreateDirectoryV1 { fd, path } => JournalEntry::CreateDirectoryV1 { + fd, + path: path.into_owned().into(), + }, + Self::RemoveDirectoryV1 { fd, path } => JournalEntry::RemoveDirectoryV1 { + fd, + path: path.into_owned().into(), + }, + Self::PathSetTimesV1 { + fd, + flags, + path, + st_atim, + st_mtim, + fst_flags, + } => JournalEntry::PathSetTimesV1 { + fd, + flags, + path: path.into_owned().into(), + st_atim, + st_mtim, + fst_flags, + }, + Self::FileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags, + } => JournalEntry::FileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags, + }, + Self::FileDescriptorSetFlagsV1 { fd, flags } => { + JournalEntry::FileDescriptorSetFlagsV1 { fd, flags } + } + Self::FileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + } => JournalEntry::FileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + }, + Self::FileDescriptorSetSizeV1 { fd, st_size } => { + JournalEntry::FileDescriptorSetSizeV1 { fd, st_size } + } + Self::FileDescriptorAdviseV1 { + fd, + offset, + len, + advice, + } => JournalEntry::FileDescriptorAdviseV1 { + fd, + offset, + len, + advice, + }, + Self::FileDescriptorAllocateV1 { fd, offset, len } => { + JournalEntry::FileDescriptorAllocateV1 { fd, offset, len } + } + Self::CreateHardLinkV1 { + old_fd, + old_path, + old_flags, + new_fd, + new_path, + } => JournalEntry::CreateHardLinkV1 { + old_fd, + old_path: old_path.into_owned().into(), + old_flags, + new_fd, + new_path: new_path.into_owned().into(), + }, + Self::CreateSymbolicLinkV1 { + old_path, + fd, + new_path, + } => JournalEntry::CreateSymbolicLinkV1 { + old_path: old_path.into_owned().into(), + fd, + new_path: new_path.into_owned().into(), + }, + Self::UnlinkFileV1 { fd, path } => JournalEntry::UnlinkFileV1 { + fd, + path: path.into_owned().into(), + }, + Self::PathRenameV1 { + old_fd, + old_path, + new_fd, + new_path, + } => JournalEntry::PathRenameV1 { + old_fd, + old_path: old_path.into_owned().into(), + new_fd, + new_path: new_path.into_owned().into(), + }, + Self::ChangeDirectoryV1 { path } => JournalEntry::ChangeDirectoryV1 { + path: path.into_owned().into(), + }, + Self::EpollCreateV1 { fd } => JournalEntry::EpollCreateV1 { fd }, + Self::EpollCtlV1 { + epfd, + op, + fd, + event, + } => JournalEntry::EpollCtlV1 { + epfd, + op, + fd, + event, + }, + Self::TtySetV1 { tty, line_feeds } => JournalEntry::TtySetV1 { tty, line_feeds }, + Self::CreatePipeV1 { fd1, fd2 } => JournalEntry::CreatePipeV1 { fd1, fd2 }, + Self::CreateEventV1 { + initial_val, + flags, + fd, + } => JournalEntry::CreateEventV1 { + initial_val, + flags, + fd, + }, + Self::PortAddAddrV1 { cidr } => JournalEntry::PortAddAddrV1 { cidr }, + Self::PortDelAddrV1 { addr } => JournalEntry::PortDelAddrV1 { addr }, + Self::PortAddrClearV1 => JournalEntry::PortAddrClearV1, + Self::PortBridgeV1 { + network, + token, + security, + } => JournalEntry::PortBridgeV1 { + network: network.into_owned().into(), + token: token.into_owned().into(), + security, + }, + Self::PortUnbridgeV1 => JournalEntry::PortUnbridgeV1, + Self::PortDhcpAcquireV1 => JournalEntry::PortDhcpAcquireV1, + Self::PortGatewaySetV1 { ip } => JournalEntry::PortGatewaySetV1 { ip }, + Self::PortRouteAddV1 { + cidr, + via_router, + preferred_until, + expires_at, + } => JournalEntry::PortRouteAddV1 { + cidr, + via_router, + preferred_until, + expires_at, + }, + Self::PortRouteClearV1 => JournalEntry::PortRouteClearV1, + Self::PortRouteDelV1 { ip } => JournalEntry::PortRouteDelV1 { ip }, + Self::SocketOpenV1 { af, ty, pt, fd } => JournalEntry::SocketOpenV1 { af, ty, pt, fd }, + Self::SocketListenV1 { fd, backlog } => JournalEntry::SocketListenV1 { fd, backlog }, + Self::SocketBindV1 { fd, addr } => JournalEntry::SocketBindV1 { fd, addr }, + Self::SocketConnectedV1 { fd, addr } => JournalEntry::SocketConnectedV1 { fd, addr }, + Self::SocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags, + non_blocking: nonblocking, + } => JournalEntry::SocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags, + non_blocking: nonblocking, + }, + Self::SocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + } => JournalEntry::SocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + }, + Self::SocketJoinIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => JournalEntry::SocketJoinIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + }, + Self::SocketLeaveIpv4MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => JournalEntry::SocketLeaveIpv4MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + }, + Self::SocketLeaveIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => JournalEntry::SocketLeaveIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + }, + Self::SocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + } => JournalEntry::SocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + }, + Self::SocketSendToV1 { + fd, + data, + flags, + addr, + is_64bit, + } => JournalEntry::SocketSendToV1 { + fd, + data: data.into_owned().into(), + flags, + addr, + is_64bit, + }, + Self::SocketSendV1 { + fd, + data, + flags, + is_64bit, + } => JournalEntry::SocketSendV1 { + fd, + data: data.into_owned().into(), + flags, + is_64bit, + }, + Self::SocketSetOptFlagV1 { fd, opt, flag } => { + JournalEntry::SocketSetOptFlagV1 { fd, opt, flag } + } + Self::SocketSetOptSizeV1 { fd, opt, size } => { + JournalEntry::SocketSetOptSizeV1 { fd, opt, size } + } + Self::SocketSetOptTimeV1 { fd, ty, time } => { + JournalEntry::SocketSetOptTimeV1 { fd, ty, time } + } + Self::SocketShutdownV1 { fd, how } => JournalEntry::SocketShutdownV1 { fd, how }, + Self::SnapshotV1 { when, trigger } => JournalEntry::SnapshotV1 { when, trigger }, + } + } + + pub fn estimate_size(&self) -> usize { + let base_size = std::mem::size_of_val(self); + match self { + JournalEntry::InitModuleV1 { .. } => base_size, + JournalEntry::UpdateMemoryRegionV1 { data, .. } => base_size + data.len(), + JournalEntry::ProcessExitV1 { .. } => base_size, + JournalEntry::SetThreadV1 { + call_stack, + memory_stack, + store_data, + .. + } => base_size + call_stack.len() + memory_stack.len() + store_data.len(), + JournalEntry::CloseThreadV1 { .. } => base_size, + JournalEntry::FileDescriptorSeekV1 { .. } => base_size, + JournalEntry::FileDescriptorWriteV1 { data, .. } => base_size + data.len(), + JournalEntry::SetClockTimeV1 { .. } => base_size, + JournalEntry::CloseFileDescriptorV1 { .. } => base_size, + JournalEntry::OpenFileDescriptorV1 { path, .. } => base_size + path.as_bytes().len(), + JournalEntry::RenumberFileDescriptorV1 { .. } => base_size, + JournalEntry::DuplicateFileDescriptorV1 { .. } => base_size, + JournalEntry::CreateDirectoryV1 { path, .. } => base_size + path.as_bytes().len(), + JournalEntry::RemoveDirectoryV1 { path, .. } => base_size + path.as_bytes().len(), + JournalEntry::PathSetTimesV1 { path, .. } => base_size + path.as_bytes().len(), + JournalEntry::FileDescriptorSetTimesV1 { .. } => base_size, + JournalEntry::FileDescriptorSetFlagsV1 { .. } => base_size, + JournalEntry::FileDescriptorSetRightsV1 { .. } => base_size, + JournalEntry::FileDescriptorSetSizeV1 { .. } => base_size, + JournalEntry::FileDescriptorAdviseV1 { .. } => base_size, + JournalEntry::FileDescriptorAllocateV1 { .. } => base_size, + JournalEntry::CreateHardLinkV1 { + old_path, new_path, .. + } => base_size + old_path.as_bytes().len() + new_path.as_bytes().len(), + JournalEntry::CreateSymbolicLinkV1 { + old_path, new_path, .. + } => base_size + old_path.as_bytes().len() + new_path.as_bytes().len(), + JournalEntry::UnlinkFileV1 { path, .. } => base_size + path.as_bytes().len(), + JournalEntry::PathRenameV1 { + old_path, new_path, .. + } => base_size + old_path.as_bytes().len() + new_path.as_bytes().len(), + JournalEntry::ChangeDirectoryV1 { path } => base_size + path.as_bytes().len(), + JournalEntry::EpollCreateV1 { .. } => base_size, + JournalEntry::EpollCtlV1 { .. } => base_size, + JournalEntry::TtySetV1 { .. } => base_size, + JournalEntry::CreatePipeV1 { .. } => base_size, + JournalEntry::CreateEventV1 { .. } => base_size, + JournalEntry::PortAddAddrV1 { .. } => base_size, + JournalEntry::PortDelAddrV1 { .. } => base_size, + JournalEntry::PortAddrClearV1 => base_size, + JournalEntry::PortBridgeV1 { network, token, .. } => { + base_size + network.as_bytes().len() + token.as_bytes().len() + } + JournalEntry::PortUnbridgeV1 => base_size, + JournalEntry::PortDhcpAcquireV1 => base_size, + JournalEntry::PortGatewaySetV1 { .. } => base_size, + JournalEntry::PortRouteAddV1 { .. } => base_size, + JournalEntry::PortRouteClearV1 => base_size, + JournalEntry::PortRouteDelV1 { .. } => base_size, + JournalEntry::SocketOpenV1 { .. } => base_size, + JournalEntry::SocketListenV1 { .. } => base_size, + JournalEntry::SocketBindV1 { .. } => base_size, + JournalEntry::SocketConnectedV1 { .. } => base_size, + JournalEntry::SocketAcceptedV1 { .. } => base_size, + JournalEntry::SocketJoinIpv4MulticastV1 { .. } => base_size, + JournalEntry::SocketJoinIpv6MulticastV1 { .. } => base_size, + JournalEntry::SocketLeaveIpv4MulticastV1 { .. } => base_size, + JournalEntry::SocketLeaveIpv6MulticastV1 { .. } => base_size, + JournalEntry::SocketSendFileV1 { .. } => base_size, + JournalEntry::SocketSendToV1 { data, .. } => base_size + data.len(), + JournalEntry::SocketSendV1 { data, .. } => base_size + data.len(), + JournalEntry::SocketSetOptFlagV1 { .. } => base_size, + JournalEntry::SocketSetOptSizeV1 { .. } => base_size, + JournalEntry::SocketSetOptTimeV1 { .. } => base_size, + JournalEntry::SocketShutdownV1 { .. } => base_size, + JournalEntry::SnapshotV1 { .. } => base_size, + } + } +} diff --git a/lib/journal/src/lib.rs b/lib/journal/src/lib.rs new file mode 100644 index 00000000000..d92fa4cf8cf --- /dev/null +++ b/lib/journal/src/lib.rs @@ -0,0 +1,50 @@ +mod base64; +mod concrete; +mod entry; +mod snapshot; +mod util; + +pub use concrete::*; +pub use entry::*; +pub use snapshot::*; +pub use util::*; + +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +/// The snapshot capturer will take a series of objects that represents the state of +/// a WASM process at a point in time and saves it so that it can be restored. +/// It also allows for the restoration of that state at a later moment +#[allow(unused_variables)] +pub trait WritableJournal { + /// Takes in a stream of snapshot log entries and saves them so that they + /// may be restored at a later moment + fn write<'a>(&'a self, entry: JournalEntry<'a>) -> anyhow::Result; +} + +/// The snapshot capturer will take a series of objects that represents the state of +/// a WASM process at a point in time and saves it so that it can be restored. +/// It also allows for the restoration of that state at a later moment +#[allow(unused_variables)] +pub trait ReadableJournal { + /// Returns a stream of snapshot objects that the runtime will use + /// to restore the state of a WASM process to a previous moment in time + fn read(&self) -> anyhow::Result>>; + + /// Resets the journal so that reads will start from the + /// beginning again + fn as_restarted(&self) -> anyhow::Result>; +} + +/// The snapshot capturer will take a series of objects that represents the state of +/// a WASM process at a point in time and saves it so that it can be restored. +/// It also allows for the restoration of that state at a later moment +#[allow(unused_variables)] +pub trait Journal: WritableJournal + ReadableJournal { + /// Splits the journal into a read and write side + fn split(self) -> (Box, Box); +} + +pub type DynJournal = dyn Journal + Send + Sync; +pub type DynWritableJournal = dyn WritableJournal + Send + Sync; +pub type DynReadableJournal = dyn ReadableJournal + Send + Sync; diff --git a/lib/journal/src/snapshot.rs b/lib/journal/src/snapshot.rs new file mode 100644 index 00000000000..15afcb6ae92 --- /dev/null +++ b/lib/journal/src/snapshot.rs @@ -0,0 +1,64 @@ +use super::*; + +/// Various triggers that will cause the runtime to take snapshot +/// of the WASM state and store it in the snapshot file. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum SnapshotTrigger { + /// Triggered when all the threads in the process goes idle + Idle, + /// Triggered when a listen syscall is invoked on a socket for the first time + FirstListen, + /// Triggered on reading the environment variables for the first time + FirstEnviron, + /// Triggered when the process reads stdin for the first time + FirstStdin, + /// Triggered periodically based on a interval (default 10 seconds) which can be specified using the `snapshot-interval` option + PeriodicInterval, + /// Issued if the user sends an interrupt signal (Ctrl + C). + Sigint, + /// Alarm clock signal (used for timers) + Sigalrm, + /// The SIGTSTP signal is sent to a process by its controlling terminal to request it to stop temporarily. It is commonly initiated by the user pressing Ctrl-Z. + Sigtstp, + /// The SIGSTOP signal instructs the operating system to stop a process for later resumption. + Sigstop, + /// When a non-determinstic call is made + NonDeterministicCall, +} + +impl SnapshotTrigger { + pub fn only_once(&self) -> bool { + matches!( + self, + Self::FirstListen | Self::FirstEnviron | Self::FirstStdin + ) + } +} + +pub const DEFAULT_SNAPSHOT_TRIGGERS: [SnapshotTrigger; 4] = [ + SnapshotTrigger::Idle, + SnapshotTrigger::FirstEnviron, + SnapshotTrigger::FirstListen, + SnapshotTrigger::FirstStdin, +]; + +impl FromStr for SnapshotTrigger { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + let s = s.trim().to_lowercase(); + Ok(match s.as_str() { + "idle" => Self::Idle, + "first-listen" => Self::FirstListen, + "first-stdin" => Self::FirstStdin, + "first-environ" => Self::FirstEnviron, + "periodic-interval" => Self::PeriodicInterval, + "intr" | "sigint" | "ctrlc" | "ctrl-c" => Self::Sigint, + "alarm" | "timer" | "sigalrm" => Self::Sigalrm, + "sigtstp" | "ctrlz" | "ctrl-z" => Self::Sigtstp, + "stop" | "sigstop" => Self::Sigstop, + "non-deterministic-call" => Self::NonDeterministicCall, + a => return Err(anyhow::format_err!("invalid or unknown trigger ({a})")), + }) + } +} diff --git a/lib/journal/src/util.rs b/lib/journal/src/util.rs new file mode 100644 index 00000000000..528c131fb2c --- /dev/null +++ b/lib/journal/src/util.rs @@ -0,0 +1,11 @@ +use super::*; + +pub fn copy_journal( + from: &R, + to: &W, +) -> anyhow::Result<()> { + while let Some(record) = from.read()? { + to.write(record)?; + } + Ok(()) +} diff --git a/lib/sys-utils/src/memory/fd_memory/memories.rs b/lib/sys-utils/src/memory/fd_memory/memories.rs index 4b71a13d3aa..a499652c265 100644 --- a/lib/sys-utils/src/memory/fd_memory/memories.rs +++ b/lib/sys-utils/src/memory/fd_memory/memories.rs @@ -12,7 +12,7 @@ use std::{ }; use wasmer::{Bytes, MemoryError, MemoryType, Pages}; -use wasmer_types::MemoryStyle; +use wasmer_types::{MemoryStyle, WASM_PAGE_SIZE}; use wasmer_vm::{ LinearMemory, MaybeInstanceOwned, ThreadConditions, Trap, VMMemoryDefinition, WaiterError, }; @@ -131,6 +131,24 @@ impl WasmMmap { Ok(prev_pages) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64, conf: VMMemoryConfig) -> Result<(), MemoryError> { + let cur_size = self.size.bytes().0 as u64; + if cur_size < min_size { + let growth = min_size - cur_size; + let growth_pages = ((growth - 1) / WASM_PAGE_SIZE as u64) + 1; + self.grow(Pages(growth_pages as u32), conf)?; + } + + Ok(()) + } + + fn reset(&mut self) -> Result<(), MemoryError> { + self.size.0 = 0; + Ok(()) + } + /// Copies the memory /// (in this case it performs a copy-on-write to save memory) pub fn copy(&mut self) -> Result { @@ -337,6 +355,17 @@ impl LinearMemory for VMOwnedMemory { self.mmap.grow(delta, self.config.clone()) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + self.mmap.grow_at_least(min_size, self.config.clone()) + } + + fn reset(&mut self) -> Result<(), MemoryError> { + self.mmap.reset()?; + Ok(()) + } + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. fn vmmemory(&self) -> NonNull { self.mmap.vm_memory_definition.as_ptr() @@ -418,6 +447,13 @@ impl LinearMemory for VMSharedMemory { guard.size() } + /// Resets the memory back down to zero size + fn reset(&mut self) -> Result<(), MemoryError> { + let mut guard = self.mmap.write().unwrap(); + guard.reset()?; + Ok(()) + } + /// Returns the memory style for this memory. fn style(&self) -> MemoryStyle { self.config.style() @@ -432,6 +468,13 @@ impl LinearMemory for VMSharedMemory { guard.grow(delta, self.config.clone()) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + let mut guard = self.mmap.write().unwrap(); + guard.grow_at_least(min_size, self.config.clone()) + } + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. fn vmmemory(&self) -> NonNull { let guard = self.mmap.read().unwrap(); @@ -503,6 +546,18 @@ impl LinearMemory for VMMemory { self.0.grow(delta) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + self.0.grow_at_least(min_size) + } + + /// Resets the memory down to a zero size + fn reset(&mut self) -> Result<(), MemoryError> { + self.0.reset()?; + Ok(()) + } + /// Returns the memory style for this memory. fn style(&self) -> MemoryStyle { self.0.style() diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 1ae02dda022..36b2c93ba30 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -95,7 +95,7 @@ pub enum MemoryError { /// /// Note: this error is not standard to WebAssembly, but it's /// useful to determine the import issue on the API side. -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum ImportError { /// Incompatible Import Type. /// This error occurs when the import types mismatch. diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs index 66c09654317..5999f0c11c3 100644 --- a/lib/types/src/memory.rs +++ b/lib/types/src/memory.rs @@ -1,4 +1,5 @@ use crate::{Pages, ValueType}; +use core::ops::SubAssign; use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; @@ -93,6 +94,7 @@ pub unsafe trait MemorySize: Copy { + Add + Sum + AddAssign + + SubAssign + 'static; /// Type used to pass this value as an argument or return value for a Wasm function. diff --git a/lib/virtual-fs/Cargo.toml b/lib/virtual-fs/Cargo.toml index 6b334b279ae..6b86ab92519 100644 --- a/lib/virtual-fs/Cargo.toml +++ b/lib/virtual-fs/Cargo.toml @@ -29,6 +29,7 @@ tokio = { version = "1", features = ["io-util", "sync", "macros"], default_featu tracing = { version = "0.1" } typetag = { version = "0.1", optional = true } webc = { version = "5.0", optional = true } +serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] getrandom = { version = "0.2" } @@ -46,7 +47,7 @@ default = ["host-fs", "webc-fs", "static-fs"] host-fs = ["libc", "fs_extra", "filetime", "tokio/fs", "tokio/io-std", "tokio/rt"] webc-fs = ["webc", "anyhow"] static-fs = ["webc", "anyhow"] -enable-serde = ["typetag"] +enable-serde = ["typetag", "serde"] no-time = [] # Enables memory tracking/limiting functionality for the in-memory filesystem. tracking = [] diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index da7b845eb1b..5ccd9b5f9ee 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -20,16 +20,25 @@ use tokio::runtime::Handle; #[derive(Debug, Clone)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub struct FileSystem(Handle); +pub struct FileSystem { + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))] + handle: Handle, +} +#[allow(dead_code)] +fn default_handle() -> Handle { + Handle::current() +} impl Default for FileSystem { fn default() -> Self { - Self(Handle::current()) + Self { + handle: Handle::current(), + } } } impl FileSystem { pub fn new(handle: Handle) -> Self { - FileSystem(handle) + FileSystem { handle } } } @@ -225,7 +234,7 @@ impl crate::FileOpener for FileSystem { .map_err(Into::into) .map(|file| { Box::new(File::new( - self.0.clone(), + self.handle.clone(), file, path.to_owned(), read, @@ -240,9 +249,11 @@ impl crate::FileOpener for FileSystem { #[derive(Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize))] pub struct File { + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))] handle: Handle, #[cfg_attr(feature = "enable-serde", serde(skip_serializing))] inner_std: fs::File, + #[cfg_attr(feature = "enable-serde", serde(skip))] inner: tfs::File, pub host_path: PathBuf, #[cfg(feature = "enable-serde")] @@ -288,7 +299,9 @@ impl<'de> Deserialize<'de> for File { .open(&host_path) .map_err(|_| de::Error::custom("Could not open file on this system"))?; Ok(File { - inner, + handle: Handle::current(), + inner: tokio::fs::File::from_std(inner.try_clone().unwrap()), + inner_std: inner, host_path, flags, }) @@ -325,7 +338,9 @@ impl<'de> Deserialize<'de> for File { .open(&host_path) .map_err(|_| de::Error::custom("Could not open file on this system"))?; Ok(File { - inner, + handle: Handle::current(), + inner: tokio::fs::File::from_std(inner.try_clone().unwrap()), + inner_std: inner, host_path, flags, }) @@ -517,10 +532,15 @@ impl AsyncSeek for File { #[derive(Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Stdout { + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))] handle: Handle, + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_stdout"))] inner: tokio::io::Stdout, } - +#[allow(dead_code)] +fn default_stdout() -> tokio::io::Stdout { + tokio::io::stdout() +} impl Default for Stdout { fn default() -> Self { Self { @@ -647,9 +667,15 @@ impl AsyncSeek for Stdout { #[derive(Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Stderr { + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))] handle: Handle, + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_stderr"))] inner: tokio::io::Stderr, } +#[allow(dead_code)] +fn default_stderr() -> tokio::io::Stderr { + tokio::io::stderr() +} impl Default for Stderr { fn default() -> Self { Self { @@ -768,9 +794,15 @@ impl VirtualFile for Stderr { #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Stdin { read_buffer: Arc>>, + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))] handle: Handle, + #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_stdin"))] inner: tokio::io::Stdin, } +#[allow(dead_code)] +fn default_stdin() -> tokio::io::Stdin { + tokio::io::stdin() +} impl Default for Stdin { fn default() -> Self { Self { diff --git a/lib/virtual-net/Cargo.toml b/lib/virtual-net/Cargo.toml index cfaf6e2f0e2..db23e664d3d 100644 --- a/lib/virtual-net/Cargo.toml +++ b/lib/virtual-net/Cargo.toml @@ -31,10 +31,13 @@ tokio-util = { version = "0.6", features = ["codec"], optional = true } hyper-tungstenite = { version = "0.11", optional = true } hyper = { version = "0.14", optional = true } tokio-tungstenite = { version = "0.20", optional = true } +rkyv = { version = "0.7.40", features = ["indexmap", "validation", "strict"], optional = true } +bytecheck = { version = "0.6.8", optional = true } [dev-dependencies] tokio = { version = "1", default_features = false, features = [ "macros", "rt-multi-thread" ] } tracing-test = { version = "0.2" } +serial_test = "2.0.0" [features] default = [ "host-net", "remote", "json", "messagepack", "cbor", "hyper", "tokio-tungstenite" ] @@ -45,6 +48,7 @@ messagepack = [ "tokio-serde/messagepack" ] cbor = [ "tokio-serde/cbor" ] hyper = [ "hyper-tungstenite", "dep:hyper" ] tokio-tungstenite = [ "dep:tokio-tungstenite" ] +rkyv = [ "dep:rkyv", "dep:bytecheck" ] [package.metadata.docs.rs] features = ["host-net", "remote"] diff --git a/lib/virtual-net/src/client.rs b/lib/virtual-net/src/client.rs index 5777c338810..879b99244ab 100644 --- a/lib/virtual-net/src/client.rs +++ b/lib/virtual-net/src/client.rs @@ -335,7 +335,9 @@ impl Future for RemoteNetworkingClientDriver { let guard = self.common.recv_tx.lock().unwrap(); match guard.get(&socket_id) { Some(tx) => tx.clone(), - None => continue, + None => { + continue; + } } }; let common = self.common.clone(); @@ -409,7 +411,6 @@ impl Future for RemoteNetworkingClientDriver { } } }, - MessageResponse::FinishAccept { socket_id, child_id, @@ -872,7 +873,7 @@ struct RemoteSocket { tx_waker: Waker, rx_accept: mpsc::Receiver, rx_sent: mpsc::Receiver, - pending_accept: Option, + pending_accept: Option<(SocketId, mpsc::Receiver>)>, buffer_recv_with_addr: VecDeque, buffer_accept: VecDeque, send_available: u64, @@ -930,7 +931,11 @@ impl RemoteSocket { .fetch_add(1, Ordering::SeqCst) .into(); self.io_socket_fire_and_forget(RequestType::BeginAccept(child_id))?; - self.pending_accept.replace(child_id); + + let (tx, rx_recv) = tokio::sync::mpsc::channel(100); + self.common.recv_tx.lock().unwrap().insert(child_id, tx); + + self.pending_accept.replace((child_id, rx_recv)); Ok(()) } } @@ -1059,17 +1064,26 @@ impl VirtualTcpListener for RemoteSocket { // This placed here will mean there is always an accept request pending at the // server as the constructor invokes this method and we invoke it here after // receiving a child connection. - self.pending_accept.take(); + let mut rx_recv = None; + if let Some((rx_socket, existing_rx_recv)) = self.pending_accept.take() { + if accepted.socket == rx_socket { + rx_recv.replace(existing_rx_recv); + } + } + let rx_recv = match rx_recv { + Some(rx_recv) => rx_recv, + None => { + let (tx, rx_recv) = tokio::sync::mpsc::channel(100); + self.common + .recv_tx + .lock() + .unwrap() + .insert(accepted.socket, tx); + rx_recv + } + }; self.touch_begin_accept().ok(); - // Now we construct the child - let (tx, rx_recv) = tokio::sync::mpsc::channel(100); - self.common - .recv_tx - .lock() - .unwrap() - .insert(accepted.socket, tx); - let (tx, rx_recv_with_addr) = tokio::sync::mpsc::channel(100); self.common .recv_with_addr_tx diff --git a/lib/virtual-net/src/lib.rs b/lib/virtual-net/src/lib.rs index e8ba8d97e75..fd0ff3b0a00 100644 --- a/lib/virtual-net/src/lib.rs +++ b/lib/virtual-net/src/lib.rs @@ -16,20 +16,22 @@ mod tests; #[cfg(any(feature = "remote"))] pub use client::{RemoteNetworkingClient, RemoteNetworkingClientDriver}; use pin_project_lite::pin_project; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, CheckBytes, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(any(feature = "remote"))] pub use server::{RemoteNetworkingServer, RemoteNetworkingServerDriver}; use std::fmt; use std::mem::MaybeUninit; -pub use std::net::IpAddr; -pub use std::net::Ipv4Addr; -pub use std::net::Ipv6Addr; +use std::net::IpAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; use std::net::Shutdown; -pub use std::net::SocketAddr; +use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; use std::task::Context; use std::task::Poll; -pub use std::time::Duration; +use std::time::Duration; use thiserror::Error; #[cfg(feature = "tokio")] use tokio::io::AsyncRead; @@ -47,6 +49,8 @@ pub type Result = std::result::Result; /// Represents an IP address and its netmask #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +#[cfg_attr(feature = "rkyv", derive(RkyvSerialize, RkyvDeserialize, Archive))] +#[cfg_attr(feature = "rkyv", archive_attr(derive(CheckBytes)))] pub struct IpCidr { pub ip: IpAddr, pub prefix: u8, @@ -54,6 +58,8 @@ pub struct IpCidr { /// Represents a routing entry in the routing table of the interface #[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "rkyv", derive(RkyvSerialize, RkyvDeserialize, Archive))] +#[cfg_attr(feature = "rkyv", archive_attr(derive(CheckBytes)))] pub struct IpRoute { pub cidr: IpCidr, pub via_router: IpAddr, diff --git a/lib/virtual-net/src/meta.rs b/lib/virtual-net/src/meta.rs index 8744f0af9ca..c8875c9c81b 100644 --- a/lib/virtual-net/src/meta.rs +++ b/lib/virtual-net/src/meta.rs @@ -1,13 +1,14 @@ +use std::net::IpAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; +use std::net::SocketAddr; +use std::time::Duration; + use serde::{Deserialize, Serialize}; -pub use super::Duration; -pub use super::IpAddr; pub use super::IpCidr; pub use super::IpRoute; -pub use super::Ipv4Addr; -pub use super::Ipv6Addr; pub use super::NetworkError; -pub use super::SocketAddr; pub use super::SocketStatus; pub use super::StreamSecurity; diff --git a/lib/virtual-net/src/tests.rs b/lib/virtual-net/src/tests.rs index 576c9cba029..1b0149a1b76 100644 --- a/lib/virtual-net/src/tests.rs +++ b/lib/virtual-net/src/tests.rs @@ -80,7 +80,7 @@ async fn test_tcp(client: RemoteNetworkingClient, _server: RemoteNetworkingServe ) .await .unwrap(); - let addr = listener.addr_local().unwrap(); + let addr: SocketAddr = listener.addr_local().unwrap(); tracing::info!("listening on {addr}"); const TEST1: &str = "the cat ran up the wall!"; @@ -126,6 +126,7 @@ async fn test_tcp(client: RemoteNetworkingClient, _server: RemoteNetworkingServe #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_mpsc() { let (client, server) = setup_mpsc().await; test_tcp(client, server).await @@ -135,6 +136,7 @@ async fn test_tcp_with_mpsc() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_small_pipe_using_bincode() { let (client, server) = setup_pipe(10, FrameSerializationFormat::Bincode).await; test_tcp(client, server).await @@ -144,6 +146,7 @@ async fn test_tcp_with_small_pipe_using_bincode() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_large_pipe_using_bincode() { let (client, server) = setup_pipe(1024000, FrameSerializationFormat::Bincode).await; test_tcp(client, server).await @@ -154,6 +157,7 @@ async fn test_tcp_with_large_pipe_using_bincode() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_small_pipe_using_json() { let (client, server) = setup_pipe(10, FrameSerializationFormat::Json).await; test_tcp(client, server).await @@ -164,6 +168,7 @@ async fn test_tcp_with_small_pipe_using_json() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_large_pipe_json_using_json() { let (client, server) = setup_pipe(1024000, FrameSerializationFormat::Json).await; test_tcp(client, server).await @@ -174,6 +179,7 @@ async fn test_tcp_with_large_pipe_json_using_json() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_small_pipe_using_messagepack() { let (client, server) = setup_pipe(10, FrameSerializationFormat::MessagePack).await; test_tcp(client, server).await @@ -184,6 +190,7 @@ async fn test_tcp_with_small_pipe_using_messagepack() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_large_pipe_json_using_messagepack() { let (client, server) = setup_pipe(1024000, FrameSerializationFormat::MessagePack).await; test_tcp(client, server).await @@ -194,6 +201,7 @@ async fn test_tcp_with_large_pipe_json_using_messagepack() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_small_pipe_using_cbor() { let (client, server) = setup_pipe(10, FrameSerializationFormat::Cbor).await; test_tcp(client, server).await @@ -204,6 +212,7 @@ async fn test_tcp_with_small_pipe_using_cbor() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] async fn test_tcp_with_large_pipe_json_using_cbor() { let (client, server) = setup_pipe(1024000, FrameSerializationFormat::Cbor).await; test_tcp(client, server).await @@ -212,6 +221,7 @@ async fn test_tcp_with_large_pipe_json_using_cbor() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test] +#[serial_test::serial] async fn test_google_poll() { use futures_util::Future; @@ -303,6 +313,7 @@ async fn test_google_poll() { #[cfg_attr(windows, ignore)] #[traced_test] #[tokio::test] +#[serial_test::serial] async fn test_google_epoll() { use futures_util::Future; use virtual_mio::SharedWakerInterestHandler; diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index 37a6551b886..ca600d67512 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -16,7 +16,7 @@ use std::ptr::NonNull; use std::slice; use std::sync::{Arc, RwLock}; use std::time::Duration; -use wasmer_types::{Bytes, MemoryError, MemoryStyle, MemoryType, Pages}; +use wasmer_types::{Bytes, MemoryError, MemoryStyle, MemoryType, Pages, WASM_PAGE_SIZE}; // The memory mapped area #[derive(Debug)] @@ -120,6 +120,25 @@ impl WasmMmap { Ok(prev_pages) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64, conf: VMMemoryConfig) -> Result<(), MemoryError> { + let cur_size = self.size.bytes().0 as u64; + if cur_size < min_size { + let growth = min_size - cur_size; + let growth_pages = ((growth - 1) / WASM_PAGE_SIZE as u64) + 1; + self.grow(Pages(growth_pages as u32), conf)?; + } + + Ok(()) + } + + /// Resets the memory down to a zero size + fn reset(&mut self) -> Result<(), MemoryError> { + self.size.0 = 0; + Ok(()) + } + /// Copies the memory /// (in this case it performs a copy-on-write to save memory) pub fn copy(&mut self) -> Result { @@ -326,6 +345,18 @@ impl LinearMemory for VMOwnedMemory { self.mmap.grow(delta, self.config.clone()) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + self.mmap.grow_at_least(min_size, self.config.clone()) + } + + /// Resets the memory down to a zero size + fn reset(&mut self) -> Result<(), MemoryError> { + self.mmap.reset()?; + Ok(()) + } + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. fn vmmemory(&self) -> NonNull { self.mmap.vm_memory_definition.as_ptr() @@ -422,6 +453,20 @@ impl LinearMemory for VMSharedMemory { guard.grow(delta, self.config.clone()) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + let mut guard = self.mmap.write().unwrap(); + guard.grow_at_least(min_size, self.config.clone()) + } + + /// Resets the memory down to a zero size + fn reset(&mut self) -> Result<(), MemoryError> { + let mut guard = self.mmap.write().unwrap(); + guard.reset()?; + Ok(()) + } + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. fn vmmemory(&self) -> NonNull { let guard = self.mmap.read().unwrap(); @@ -495,6 +540,18 @@ impl LinearMemory for VMMemory { self.0.grow(delta) } + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> { + self.0.grow_at_least(min_size) + } + + /// Resets the memory down to a zero size + fn reset(&mut self) -> Result<(), MemoryError> { + self.0.reset()?; + Ok(()) + } + /// Returns the memory style for this memory. fn style(&self) -> MemoryStyle { self.0.style() @@ -633,6 +690,13 @@ where /// of wasm pages. fn grow(&mut self, delta: Pages) -> Result; + /// Grows the memory to at least a minimum size. If the memory is already big enough + /// for the min size then this function does nothing + fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError>; + + /// Resets the memory back to zero length + fn reset(&mut self) -> Result<(), MemoryError>; + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. fn vmmemory(&self) -> NonNull; diff --git a/lib/wasi-types/src/wasi/bindings.rs b/lib/wasi-types/src/wasi/bindings.rs index 68204e07d9d..8b07961d637 100644 --- a/lib/wasi-types/src/wasi/bindings.rs +++ b/lib/wasi-types/src/wasi/bindings.rs @@ -1,3 +1,4 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use std::mem::MaybeUninit; @@ -34,6 +35,7 @@ pub type Pid = u32; #[doc = " Identifiers for clocks, snapshot0 version."] #[repr(u32)] #[derive(Clone, Copy, PartialEq, Eq, num_enum :: TryFromPrimitive, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Snapshot0Clockid { #[doc = " The clock measuring real time. Time value zero corresponds with"] #[doc = " 1970-01-01T00:00:00Z."] @@ -100,7 +102,7 @@ impl core::fmt::Debug for Clockid { #[doc = " API; some are used in higher-level library layers, and others are provided"] #[doc = " merely for alignment with POSIX."] #[repr(u16)] -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Errno { #[doc = " No error occurred. System call completed successfully."] @@ -447,6 +449,7 @@ impl core::fmt::Display for Errno { impl std::error::Error for Errno {} wai_bindgen_rust::bitflags::bitflags! { #[doc = " File descriptor rights, determining which actions may be performed."] + #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Rights : u64 { #[doc = " The right to invoke `fd_datasync`."] #[doc = " "] @@ -640,6 +643,7 @@ impl core::fmt::Debug for Dirent { #[doc = " File or memory access pattern advisory information."] #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Advice { #[doc = " The application has no advice to give on its behavior with respect to the specified data."] Normal, @@ -671,6 +675,7 @@ impl core::fmt::Debug for Advice { } wai_bindgen_rust::bitflags::bitflags! { #[doc = " File descriptor flags."] + #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Fdflags : u16 { #[doc = " Append mode: Data written to the file is always appended to the file's end."] const APPEND = 1 << 0; @@ -721,6 +726,7 @@ wai_bindgen_rust::bitflags::bitflags! { #[doc = " Which file time attributes to adjust."] #[doc = " TODO: wit appears to not have support for flags repr"] #[doc = " (@witx repr u16)"] + #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Fstflags : u16 { #[doc = " Adjust the last data access timestamp to the value stored in `filestat::atim`."] const SET_ATIM = 1 << 0; @@ -759,6 +765,7 @@ wai_bindgen_rust::bitflags::bitflags! { #[doc = " Open flags used by `path_open`."] #[doc = " TODO: wit appears to not have support for flags repr"] #[doc = " (@witx repr u16)"] + #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Oflags : u16 { #[doc = " Create file if it does not exist."] const CREATE = 1 << 0; @@ -951,6 +958,7 @@ impl core::fmt::Debug for SubscriptionFsReadwrite { } #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Socktype { Unknown, Stream, @@ -991,6 +999,7 @@ impl core::fmt::Debug for Sockstatus { } #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Sockoption { Noop, ReusePort, @@ -1081,6 +1090,7 @@ impl core::fmt::Debug for Streamsecurity { } #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Addressfamily { Unspec, Inet4, @@ -1169,6 +1179,7 @@ impl core::fmt::Debug for Snapshot0Whence { } #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum Whence { Set, Cur, @@ -1186,7 +1197,8 @@ impl core::fmt::Debug for Whence { } } #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Tty { pub cols: u32, pub rows: u32, @@ -1348,7 +1360,8 @@ impl core::fmt::Debug for StdioMode { } } #[repr(u16)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum SockProto { Ip, Icmp, diff --git a/lib/wasi-types/src/wasi/wasix_manual.rs b/lib/wasi-types/src/wasi/wasix_manual.rs index b160ade1e14..b8ee2e53d8b 100644 --- a/lib/wasi-types/src/wasi/wasix_manual.rs +++ b/lib/wasi-types/src/wasi/wasix_manual.rs @@ -377,6 +377,7 @@ wai_bindgen_rust::bitflags::bitflags! { #[doc = " Epoll operation."] #[repr(u32)] #[derive(Clone, Copy, PartialEq, Eq, num_enum :: TryFromPrimitive, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum EpollCtl { #[doc = " Add an entry to the interest list of the epoll file descriptor, epfd."] Add, @@ -428,6 +429,16 @@ unsafe impl wasmer::FromToNativeWasmType for EpollCtl { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct EpollEventCtl { + pub events: EpollType, + pub ptr: u64, + pub fd: Fd, + pub data1: u32, + pub data2: u64, +} + /// An event that can be triggered #[repr(C)] #[derive(Copy, Clone)] @@ -459,7 +470,7 @@ where #[repr(C)] #[derive(Copy, Clone)] pub struct EpollEvent { - /// Pointer to the dataa + /// Pointer to the data pub events: EpollType, /// File descriptor pub data: EpollData, diff --git a/lib/wasix/Cargo.toml b/lib/wasix/Cargo.toml index 9e9bcc368c5..f623e774bd4 100644 --- a/lib/wasix/Cargo.toml +++ b/lib/wasix/Cargo.toml @@ -24,7 +24,8 @@ wasmer-types = { path = "../types", version = "=4.2.5", default-features = false wasmer = { path = "../api", version = "=4.2.5", default-features = false, features = ["wat", "js-serializable-module"] } virtual-mio = { path = "../virtual-io", version = "0.3.0", default-features = false } virtual-fs = { path = "../virtual-fs", version = "0.11.0", default-features = false, features = ["webc-fs"] } -virtual-net = { path = "../virtual-net", version = "0.6.2", default-features = false } +virtual-net = { path = "../virtual-net", version = "0.6.2", default-features = false, features = ["rkyv"] } +wasmer-journal = { path = "../journal", version = "0.1.0", default-features = false } wasmer-emscripten = { path = "../emscripten", version = "=4.2.5", optional = true } typetag = { version = "0.1", optional = true } serde = { version = "1.0", default-features = false, features = ["derive"] } @@ -40,7 +41,12 @@ sha2 = { version = "0.10" } waker-fn = { version = "1.1" } cooked-waker = "^5" rand = "0.8" -tokio = { version = "1", features = ["sync", "macros", "time", "rt"], default_features = false } +tokio = { version = "1", features = [ + "sync", + "macros", + "time", + "rt", +], default_features = false } futures = { version = "0.3" } # used by feature='os' async-trait = { version = "^0.1" } @@ -60,18 +66,38 @@ pin-project = "1.0.12" semver = "1.0.17" dashmap = "5.4.0" tempfile = "3.6.0" +num_enum = "0.5.7" # Used by the WCGI runner hyper = { version = "0.14", features = ["server", "stream"], optional = true } wcgi = { version = "0.1.2", optional = true } wcgi-host = { version = "0.1.2", optional = true } -tower-http = { version = "0.4.0", features = ["trace", "util", "catch-panic", "cors"], optional = true } +tower-http = { version = "0.4.0", features = [ + "trace", + "util", + "catch-panic", + "cors", +], optional = true } tower = { version = "0.4.13", features = ["make", "util"], optional = true } url = "2.3.1" +rkyv = { version = "0.7.40", features = ["indexmap", "validation", "strict"] } +bytecheck = "0.6.8" +shared-buffer = "0.1" petgraph = "0.6.3" +base64 = "0.21" +lz4_flex = { version = "0.11" } +rayon = { version = "1.7.0", optional = true } wasm-bindgen = { version = "0.2.87", optional = true } js-sys = { version = "0.3.64", optional = true } wasm-bindgen-futures = { version = "0.4.37", optional = true } -web-sys = { version = "0.3.64", features = ["Request", "RequestInit", "Window", "WorkerGlobalScope", "RequestMode", "Response", "Headers"], optional = true } +web-sys = { version = "0.3.64", features = [ + "Request", + "RequestInit", + "Window", + "WorkerGlobalScope", + "RequestMode", + "Response", + "Headers", +], optional = true } [target.'cfg(not(target_arch = "riscv64"))'.dependencies.reqwest] version = "0.11" @@ -98,6 +124,7 @@ winapi = "0.3" wasmer = { path = "../api", version = "=4.2.5", default-features = false, features = ["wat", "js-serializable-module"] } tokio = { version = "1", features = [ "sync", "macros", "rt" ], default_features = false } pretty_assertions = "1.3.0" +tracing-test = "0.2.4" wasm-bindgen-test = "0.3.0" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] @@ -114,17 +141,37 @@ default = ["sys-default"] time = ["tokio/time"] webc_runner_rt_wcgi = ["hyper", "wcgi", "wcgi-host", "tower", "tower-http"] +webc_runner_rt_dcgi = ["webc_runner_rt_wcgi", "journal"] webc_runner_rt_emscripten = ["wasmer-emscripten"] sys = ["webc/mmap", "time", "virtual-mio/sys"] -sys-default = ["sys", "logging", "host-fs", "sys-poll", "sys-thread", "host-vnet", "host-threads", "host-reqwest"] +sys-default = [ + "sys", + "logging", + "host-fs", + "journal", + "sys-poll", + "sys-thread", + "host-vnet", + "host-threads", + "host-reqwest", +] sys-poll = [] sys-thread = ["tokio/rt", "tokio/time", "tokio/rt-multi-thread", "rusty_pool"] +journal = ["tokio/fs", "wasmer-journal/log-file"] # Deprecated. Kept it for compatibility compiler = [] -js = ["virtual-fs/no-time", "getrandom/js", "chrono", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys"] +js = [ + "virtual-fs/no-time", + "getrandom/js", + "chrono", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] js-default = ["js"] test-js = ["js", "wasmer/wat"] @@ -136,11 +183,18 @@ remote-vnet = ["virtual-net/remote"] logging = ["tracing/log"] disable-all-logging = ["tracing/release_max_level_off", "tracing/max_level_off"] -enable-serde = ["typetag", "virtual-fs/enable-serde", "wasmer-wasix-types/enable-serde"] +enable-serde = [ + "typetag", + "virtual-fs/enable-serde", + "wasmer-wasix-types/enable-serde", +] [package.metadata.docs.rs] features = [ - "wasmer/sys", "webc_runner_rt_wcgi", - "webc_runner_rt_emscripten", "sys-default", + "wasmer/sys", + "webc_runner_rt_wcgi", + "webc_runner_rt_dcgi", + "webc_runner_rt_emscripten", + "sys-default", ] rustc-args = ["--cfg", "docsrs"] diff --git a/lib/wasix/src/bin_factory/exec.rs b/lib/wasix/src/bin_factory/exec.rs index 25da5f1ec2b..1f82d365c1b 100644 --- a/lib/wasix/src/bin_factory/exec.rs +++ b/lib/wasix/src/bin_factory/exec.rs @@ -2,7 +2,9 @@ use std::{pin::Pin, sync::Arc}; use crate::{ os::task::{thread::WasiThreadRunGuard, TaskJoinHandle}, - runtime::task_manager::{TaskWasm, TaskWasmRunProperties}, + runtime::task_manager::{ + TaskWasm, TaskWasmRecycle, TaskWasmRecycleProperties, TaskWasmRunProperties, + }, syscalls::rewind_ext, RewindState, SpawnError, WasiError, WasiRuntimeError, }; @@ -13,7 +15,7 @@ use wasmer::{Function, FunctionEnvMut, Memory32, Memory64, Module, Store}; use wasmer_wasix_types::wasi::Errno; use super::{BinFactory, BinaryPackage}; -use crate::{runtime::SpawnMemoryType, Runtime, WasiEnv, WasiFunctionEnv}; +use crate::{Runtime, WasiEnv, WasiFunctionEnv}; #[tracing::instrument(level = "trace", skip_all, fields(%name, %binary.package_name))] pub async fn spawn_exec( @@ -34,7 +36,7 @@ pub async fn spawn_exec( pkg.version=%binary.version, "Unable to spawn a command because its package has no entrypoint", ); - env.cleanup(Some(Errno::Noexec.into())).await; + env.on_exit(Some(Errno::Noexec.into())).await; return Err(SpawnError::CompileError); }; @@ -46,7 +48,7 @@ pub async fn spawn_exec( error = &*err, "Failed to compile the module", ); - env.cleanup(Some(Errno::Noexec.into())).await; + env.on_exit(Some(Errno::Noexec.into())).await; return Err(SpawnError::CompileError); } }; @@ -79,57 +81,11 @@ pub fn spawn_exec_module( let join_handle = env.thread.join_handle(); { - // Determine if shared memory needs to be created and imported - let shared_memory = module.imports().memories().next().map(|a| *a.ty()); - - // Determine if we are going to create memory and import it or just rely on self creation of memory - let memory_spawn = match shared_memory { - Some(ty) => SpawnMemoryType::CreateMemoryOfType(ty), - None => SpawnMemoryType::CreateMemory, - }; - // Create a thread that will run this process let tasks_outer = tasks.clone(); - let run = { - move |props: TaskWasmRunProperties| { - let ctx = props.ctx; - let mut store = props.store; - - // Create the WasiFunctionEnv - let thread = WasiThreadRunGuard::new(ctx.data(&store).thread.clone()); - - // Perform the initialization - let ctx = { - // If this module exports an _initialize function, run that first. - if let Ok(initialize) = unsafe { ctx.data(&store).inner() } - .instance - .exports - .get_function("_initialize") - { - let initialize = initialize.clone(); - if let Err(err) = initialize.call(&mut store, &[]) { - thread.thread.set_status_finished(Err(err.into())); - ctx.data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); - return; - } - } - - WasiFunctionEnv { env: ctx.env } - }; - - // If there is a start function - debug!("wasi[{}]::called main()", pid); - // TODO: rewrite to use crate::run_wasi_func - - // Call the module - call_module(ctx, store, thread, None); - } - }; - tasks_outer - .task_wasm(TaskWasm::new(Box::new(run), env, module, true).with_memory(memory_spawn)) + .task_wasm(TaskWasm::new(Box::new(run_exec), env, module, true)) .map_err(|err| { error!("wasi[{}]::failed to launch module - {}", pid, err); SpawnError::UnknownError @@ -139,6 +95,78 @@ pub fn spawn_exec_module( Ok(join_handle) } +/// # SAFETY +/// This must be executed from the same thread that owns the instance as +/// otherwise it will cause a panic +unsafe fn run_recycle( + callback: Option>, + ctx: WasiFunctionEnv, + mut store: Store, +) { + if let Some(callback) = callback { + let env = ctx.data_mut(&mut store); + let memory = env.memory().clone(); + + let props = TaskWasmRecycleProperties { + env: env.clone(), + memory, + store, + }; + callback(props); + } +} + +pub fn run_exec(props: TaskWasmRunProperties) { + let ctx = props.ctx; + let mut store = props.store; + + // Create the WasiFunctionEnv + let thread = WasiThreadRunGuard::new(ctx.data(&store).thread.clone()); + let recycle = props.recycle; + + // Perform the initialization + let ctx = { + // If this module exports an _initialize function, run that first. + if let Ok(initialize) = unsafe { ctx.data(&store).inner() } + .instance + .exports + .get_function("_initialize") + { + let initialize = initialize.clone(); + if let Err(err) = initialize.call(&mut store, &[]) { + thread.thread.set_status_finished(Err(err.into())); + ctx.data(&store) + .blocking_on_exit(Some(Errno::Noexec.into())); + unsafe { run_recycle(recycle, ctx, store) }; + return; + } + } + + WasiFunctionEnv { env: ctx.env } + }; + + // Bootstrap the process + // Unsafe: The bootstrap must be executed in the same thread that runs the + // actual WASM code + let rewind_state = match unsafe { ctx.bootstrap(&mut store) } { + Ok(r) => r, + Err(err) => { + thread.thread.set_status_finished(Err(err)); + ctx.data(&store) + .blocking_on_exit(Some(Errno::Noexec.into())); + unsafe { run_recycle(recycle, ctx, store) }; + return; + } + }; + + // If there is a start function + debug!("wasi[{}]::called main()", ctx.data(&store).pid()); + // TODO: rewrite to use crate::run_wasi_func + + // Call the module + call_module(ctx, store, thread, rewind_state, recycle); +} + fn get_start(ctx: &WasiFunctionEnv, store: &Store) -> Option { unsafe { ctx.data(store).inner() } .instance @@ -153,7 +181,8 @@ fn call_module( ctx: WasiFunctionEnv, mut store: Store, handle: WasiThreadRunGuard, - rewind_state: Option<(RewindState, Bytes)>, + rewind_state: Option<(RewindState, Option)>, + recycle: Option>, ) { let env = ctx.data(&store); let pid = env.pid(); @@ -162,28 +191,31 @@ fn call_module( // If we need to rewind then do so if let Some((rewind_state, rewind_result)) = rewind_state { + let mut ctx = ctx.env.clone().into_mut(&mut store); if rewind_state.is_64bit { let res = rewind_ext::( - ctx.env.clone().into_mut(&mut store), + &mut ctx, rewind_state.memory_stack, rewind_state.rewind_stack, rewind_state.store_data, rewind_result, ); if res != Errno::Success { - ctx.data(&store).blocking_cleanup(Some(res.into())); + ctx.data().blocking_on_exit(Some(res.into())); + unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) }; return; } } else { let res = rewind_ext::( - ctx.env.clone().into_mut(&mut store), + &mut ctx, rewind_state.memory_stack, rewind_state.rewind_stack, rewind_state.store_data, rewind_result, ); if res != Errno::Success { - ctx.data(&store).blocking_cleanup(Some(res.into())); + ctx.data().blocking_on_exit(Some(res.into())); + unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) }; return; } }; @@ -197,7 +229,8 @@ fn call_module( } else { debug!("wasi[{}]::exec-failed: missing _start function", pid); ctx.data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_on_exit(Some(Errno::Noexec.into())); + unsafe { run_recycle(recycle, ctx, store) }; return; }; @@ -211,7 +244,13 @@ fn call_module( let respawn = { move |ctx, store, rewind_result| { // Call the thread - call_module(ctx, store, handle, Some((rewind, rewind_result))); + call_module( + ctx, + store, + handle, + Some((rewind, Some(rewind_result))), + recycle, + ); } }; @@ -241,7 +280,8 @@ fn call_module( }; // Cleanup the environment - ctx.data(&store).blocking_cleanup(Some(code)); + ctx.data(&store).blocking_on_exit(Some(code)); + unsafe { run_recycle(recycle, ctx, store) }; debug!("wasi[{pid}]::main() has exited with {code}"); handle.thread.set_status_finished(ret.map(|a| a.into())); @@ -261,7 +301,7 @@ impl BinFactory { .await .ok_or(SpawnError::NotFound); if binary.is_err() { - env.cleanup(Some(Errno::Noent.into())).await; + env.on_exit(Some(Errno::Noent.into())).await; } let binary = binary?; diff --git a/lib/wasix/src/bin_factory/mod.rs b/lib/wasix/src/bin_factory/mod.rs index 4fba6d838d6..9a08d7d4011 100644 --- a/lib/wasix/src/bin_factory/mod.rs +++ b/lib/wasix/src/bin_factory/mod.rs @@ -14,7 +14,7 @@ mod exec; pub use self::{ binary_package::*, - exec::{spawn_exec, spawn_exec_module}, + exec::{run_exec, spawn_exec, spawn_exec_module}, }; use crate::{os::command::Commands, Runtime}; diff --git a/lib/wasix/src/fs/mod.rs b/lib/wasix/src/fs/mod.rs index 4dcbacfe5ee..965377b3cff 100644 --- a/lib/wasix/src/fs/mod.rs +++ b/lib/wasix/src/fs/mod.rs @@ -429,6 +429,11 @@ pub struct WasiFs { // but it shouldn't be necessary // It should not be necessary at all. is_wasix: AtomicBool, + + // The preopens when this was initialized + pub(crate) init_preopens: Vec, + // The virtual file system preopens when this was initialized + pub(crate) init_vfs_preopens: Vec, } impl WasiFs { @@ -454,6 +459,8 @@ impl WasiFs { root_fs: self.root_fs.clone(), root_inode: self.root_inode.clone(), has_unioned: Arc::new(Mutex::new(HashSet::new())), + init_preopens: self.init_preopens.clone(), + init_vfs_preopens: self.init_vfs_preopens.clone(), } } @@ -515,175 +522,10 @@ impl WasiFs { vfs_preopens: &[String], fs_backing: WasiFsRoot, ) -> Result { - let (wasi_fs, root_inode) = Self::new_init(fs_backing, inodes)?; - - for preopen_name in vfs_preopens { - let kind = Kind::Dir { - parent: root_inode.downgrade(), - path: PathBuf::from(preopen_name), - entries: Default::default(), - }; - let rights = Rights::FD_ADVISE - | Rights::FD_TELL - | Rights::FD_SEEK - | Rights::FD_READ - | Rights::PATH_OPEN - | Rights::FD_READDIR - | Rights::PATH_READLINK - | Rights::PATH_FILESTAT_GET - | Rights::FD_FILESTAT_GET - | Rights::PATH_LINK_SOURCE - | Rights::PATH_RENAME_SOURCE - | Rights::POLL_FD_READWRITE - | Rights::SOCK_SHUTDOWN; - let inode = wasi_fs - .create_inode(inodes, kind, true, preopen_name.clone()) - .map_err(|e| { - format!( - "Failed to create inode for preopened dir (name `{}`): WASI error code: {}", - preopen_name, e - ) - })?; - let fd_flags = Fd::READ; - let fd = wasi_fs - .create_fd(rights, rights, Fdflags::empty(), fd_flags, inode.clone()) - .map_err(|e| format!("Could not open fd for file {:?}: {}", preopen_name, e))?; - { - let mut guard = root_inode.write(); - if let Kind::Root { entries } = guard.deref_mut() { - let existing_entry = entries.insert(preopen_name.clone(), inode); - if existing_entry.is_some() { - return Err(format!( - "Found duplicate entry for alias `{}`", - preopen_name - )); - } - assert!(existing_entry.is_none()) - } - } - wasi_fs.preopen_fds.write().unwrap().push(fd); - } - - for PreopenedDir { - path, - alias, - read, - write, - create, - } in preopens - { - debug!( - "Attempting to preopen {} with alias {:?}", - &path.to_string_lossy(), - &alias - ); - let cur_dir_metadata = wasi_fs - .root_fs - .metadata(path) - .map_err(|e| format!("Could not get metadata for file {:?}: {}", path, e))?; - - let kind = if cur_dir_metadata.is_dir() { - Kind::Dir { - parent: root_inode.downgrade(), - path: path.clone(), - entries: Default::default(), - } - } else { - return Err(format!( - "WASI only supports pre-opened directories right now; found \"{}\"", - &path.to_string_lossy() - )); - }; - - let rights = { - // TODO: review tell' and fd_readwrite - let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK; - if *read { - rights |= Rights::FD_READ - | Rights::PATH_OPEN - | Rights::FD_READDIR - | Rights::PATH_READLINK - | Rights::PATH_FILESTAT_GET - | Rights::FD_FILESTAT_GET - | Rights::PATH_LINK_SOURCE - | Rights::PATH_RENAME_SOURCE - | Rights::POLL_FD_READWRITE - | Rights::SOCK_SHUTDOWN; - } - if *write { - rights |= Rights::FD_DATASYNC - | Rights::FD_FDSTAT_SET_FLAGS - | Rights::FD_WRITE - | Rights::FD_SYNC - | Rights::FD_ALLOCATE - | Rights::PATH_OPEN - | Rights::PATH_RENAME_TARGET - | Rights::PATH_FILESTAT_SET_SIZE - | Rights::PATH_FILESTAT_SET_TIMES - | Rights::FD_FILESTAT_SET_SIZE - | Rights::FD_FILESTAT_SET_TIMES - | Rights::PATH_REMOVE_DIRECTORY - | Rights::PATH_UNLINK_FILE - | Rights::POLL_FD_READWRITE - | Rights::SOCK_SHUTDOWN; - } - if *create { - rights |= Rights::PATH_CREATE_DIRECTORY - | Rights::PATH_CREATE_FILE - | Rights::PATH_LINK_TARGET - | Rights::PATH_OPEN - | Rights::PATH_RENAME_TARGET - | Rights::PATH_SYMLINK; - } - - rights - }; - let inode = if let Some(alias) = &alias { - wasi_fs.create_inode(inodes, kind, true, alias.clone()) - } else { - wasi_fs.create_inode(inodes, kind, true, path.to_string_lossy().into_owned()) - } - .map_err(|e| { - format!( - "Failed to create inode for preopened dir: WASI error code: {}", - e - ) - })?; - let fd_flags = { - let mut fd_flags = 0; - if *read { - fd_flags |= Fd::READ; - } - if *write { - // TODO: introduce API for finer grained control - fd_flags |= Fd::WRITE | Fd::APPEND | Fd::TRUNCATE; - } - if *create { - fd_flags |= Fd::CREATE; - } - fd_flags - }; - let fd = wasi_fs - .create_fd(rights, rights, Fdflags::empty(), fd_flags, inode.clone()) - .map_err(|e| format!("Could not open fd for file {:?}: {}", path, e))?; - { - let mut guard = root_inode.write(); - if let Kind::Root { entries } = guard.deref_mut() { - let key = if let Some(alias) = &alias { - alias.clone() - } else { - path.to_string_lossy().into_owned() - }; - let existing_entry = entries.insert(key.clone(), inode); - if existing_entry.is_some() { - return Err(format!("Found duplicate entry for alias `{}`", key)); - } - assert!(existing_entry.is_none()) - } - } - wasi_fs.preopen_fds.write().unwrap().push(fd); - } - + let mut wasi_fs = Self::new_init(fs_backing, inodes)?; + wasi_fs.init_preopens = preopens.to_vec(); + wasi_fs.init_vfs_preopens = vfs_preopens.to_vec(); + wasi_fs.create_preopens(inodes, false)?; Ok(wasi_fs) } @@ -701,7 +543,7 @@ impl WasiFs { /// Private helper function to init the filesystem, called in `new` and /// `new_with_preopen` - fn new_init(fs_backing: WasiFsRoot, inodes: &WasiInodes) -> Result<(Self, InodeGuard), String> { + fn new_init(fs_backing: WasiFsRoot, inodes: &WasiInodes) -> Result { debug!("Initializing WASI filesystem"); let stat = Filestat { @@ -725,46 +567,17 @@ impl WasiFs { current_dir: Mutex::new("/".to_string()), is_wasix: AtomicBool::new(false), root_fs: fs_backing, - root_inode: root_inode.clone(), + root_inode, has_unioned: Arc::new(Mutex::new(HashSet::new())), + init_preopens: Default::default(), + init_vfs_preopens: Default::default(), }; wasi_fs.create_stdin(inodes); wasi_fs.create_stdout(inodes); wasi_fs.create_stderr(inodes); + wasi_fs.create_rootfd()?; - // create virtual root - let all_rights = ALL_RIGHTS; - // TODO: make this a list of positive rigths instead of negative ones - // root gets all right for now - let root_rights = all_rights - /* - & (!Rights::FD_WRITE) - & (!Rights::FD_ALLOCATE) - & (!Rights::PATH_CREATE_DIRECTORY) - & (!Rights::PATH_CREATE_FILE) - & (!Rights::PATH_LINK_SOURCE) - & (!Rights::PATH_RENAME_SOURCE) - & (!Rights::PATH_RENAME_TARGET) - & (!Rights::PATH_FILESTAT_SET_SIZE) - & (!Rights::PATH_FILESTAT_SET_TIMES) - & (!Rights::FD_FILESTAT_SET_SIZE) - & (!Rights::FD_FILESTAT_SET_TIMES) - & (!Rights::PATH_SYMLINK) - & (!Rights::PATH_UNLINK_FILE) - & (!Rights::PATH_REMOVE_DIRECTORY) - */; - let fd = wasi_fs - .create_fd( - root_rights, - root_rights, - Fdflags::empty(), - Fd::READ, - root_inode.clone(), - ) - .map_err(|e| format!("Could not create root fd: {}", e))?; - wasi_fs.preopen_fds.write().unwrap().push(fd); - - Ok((wasi_fs, root_inode)) + Ok(wasi_fs) } /// This function is like create dir all, but it also opens it. @@ -1663,6 +1476,22 @@ impl WasiFs { Ok(idx) } + pub fn make_max_fd(&self, fd: u32) { + loop { + let existing = self.next_fd.load(Ordering::SeqCst); + if existing >= fd { + return; + } + if self + .next_fd + .compare_exchange(existing, fd, Ordering::SeqCst, Ordering::Relaxed) + .is_ok() + { + break; + } + } + } + pub fn create_fd_ext( &self, rights: Rights, @@ -1722,7 +1551,7 @@ impl WasiFs { guard.lookup.remove(&ino).and_then(|a| Weak::upgrade(&a)) } - fn create_stdout(&self, inodes: &WasiInodes) { + pub(crate) fn create_stdout(&self, inodes: &WasiInodes) { self.create_std_dev_inner( inodes, Box::::default(), @@ -1733,7 +1562,7 @@ impl WasiFs { ); } - fn create_stdin(&self, inodes: &WasiInodes) { + pub(crate) fn create_stdin(&self, inodes: &WasiInodes) { self.create_std_dev_inner( inodes, Box::::default(), @@ -1744,7 +1573,7 @@ impl WasiFs { ); } - fn create_stderr(&self, inodes: &WasiInodes) { + pub(crate) fn create_stderr(&self, inodes: &WasiInodes) { self.create_std_dev_inner( inodes, Box::::default(), @@ -1755,7 +1584,215 @@ impl WasiFs { ); } - fn create_std_dev_inner( + pub(crate) fn create_rootfd(&self) -> Result<(), String> { + // create virtual root + let all_rights = ALL_RIGHTS; + // TODO: make this a list of positive rigths instead of negative ones + // root gets all right for now + let root_rights = all_rights + /* + & (!Rights::FD_WRITE) + & (!Rights::FD_ALLOCATE) + & (!Rights::PATH_CREATE_DIRECTORY) + & (!Rights::PATH_CREATE_FILE) + & (!Rights::PATH_LINK_SOURCE) + & (!Rights::PATH_RENAME_SOURCE) + & (!Rights::PATH_RENAME_TARGET) + & (!Rights::PATH_FILESTAT_SET_SIZE) + & (!Rights::PATH_FILESTAT_SET_TIMES) + & (!Rights::FD_FILESTAT_SET_SIZE) + & (!Rights::FD_FILESTAT_SET_TIMES) + & (!Rights::PATH_SYMLINK) + & (!Rights::PATH_UNLINK_FILE) + & (!Rights::PATH_REMOVE_DIRECTORY) + */; + let fd = self + .create_fd( + root_rights, + root_rights, + Fdflags::empty(), + Fd::READ, + self.root_inode.clone(), + ) + .map_err(|e| format!("Could not create root fd: {}", e))?; + self.preopen_fds.write().unwrap().push(fd); + Ok(()) + } + + pub(crate) fn create_preopens( + &self, + inodes: &WasiInodes, + ignore_duplicates: bool, + ) -> Result<(), String> { + for preopen_name in self.init_vfs_preopens.iter() { + let kind = Kind::Dir { + parent: self.root_inode.downgrade(), + path: PathBuf::from(preopen_name), + entries: Default::default(), + }; + let rights = Rights::FD_ADVISE + | Rights::FD_TELL + | Rights::FD_SEEK + | Rights::FD_READ + | Rights::PATH_OPEN + | Rights::FD_READDIR + | Rights::PATH_READLINK + | Rights::PATH_FILESTAT_GET + | Rights::FD_FILESTAT_GET + | Rights::PATH_LINK_SOURCE + | Rights::PATH_RENAME_SOURCE + | Rights::POLL_FD_READWRITE + | Rights::SOCK_SHUTDOWN; + let inode = self + .create_inode(inodes, kind, true, preopen_name.clone()) + .map_err(|e| { + format!( + "Failed to create inode for preopened dir (name `{}`): WASI error code: {}", + preopen_name, e + ) + })?; + let fd_flags = Fd::READ; + let fd = self + .create_fd(rights, rights, Fdflags::empty(), fd_flags, inode.clone()) + .map_err(|e| format!("Could not open fd for file {:?}: {}", preopen_name, e))?; + { + let mut guard = self.root_inode.write(); + if let Kind::Root { entries } = guard.deref_mut() { + let existing_entry = entries.insert(preopen_name.clone(), inode); + if existing_entry.is_some() && !ignore_duplicates { + return Err(format!( + "Found duplicate entry for alias `{}`", + preopen_name + )); + } + } + } + self.preopen_fds.write().unwrap().push(fd); + } + + for PreopenedDir { + path, + alias, + read, + write, + create, + } in self.init_preopens.iter() + { + debug!( + "Attempting to preopen {} with alias {:?}", + &path.to_string_lossy(), + &alias + ); + let cur_dir_metadata = self + .root_fs + .metadata(path) + .map_err(|e| format!("Could not get metadata for file {:?}: {}", path, e))?; + + let kind = if cur_dir_metadata.is_dir() { + Kind::Dir { + parent: self.root_inode.downgrade(), + path: path.clone(), + entries: Default::default(), + } + } else { + return Err(format!( + "WASI only supports pre-opened directories right now; found \"{}\"", + &path.to_string_lossy() + )); + }; + + let rights = { + // TODO: review tell' and fd_readwrite + let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK; + if *read { + rights |= Rights::FD_READ + | Rights::PATH_OPEN + | Rights::FD_READDIR + | Rights::PATH_READLINK + | Rights::PATH_FILESTAT_GET + | Rights::FD_FILESTAT_GET + | Rights::PATH_LINK_SOURCE + | Rights::PATH_RENAME_SOURCE + | Rights::POLL_FD_READWRITE + | Rights::SOCK_SHUTDOWN; + } + if *write { + rights |= Rights::FD_DATASYNC + | Rights::FD_FDSTAT_SET_FLAGS + | Rights::FD_WRITE + | Rights::FD_SYNC + | Rights::FD_ALLOCATE + | Rights::PATH_OPEN + | Rights::PATH_RENAME_TARGET + | Rights::PATH_FILESTAT_SET_SIZE + | Rights::PATH_FILESTAT_SET_TIMES + | Rights::FD_FILESTAT_SET_SIZE + | Rights::FD_FILESTAT_SET_TIMES + | Rights::PATH_REMOVE_DIRECTORY + | Rights::PATH_UNLINK_FILE + | Rights::POLL_FD_READWRITE + | Rights::SOCK_SHUTDOWN; + } + if *create { + rights |= Rights::PATH_CREATE_DIRECTORY + | Rights::PATH_CREATE_FILE + | Rights::PATH_LINK_TARGET + | Rights::PATH_OPEN + | Rights::PATH_RENAME_TARGET + | Rights::PATH_SYMLINK; + } + + rights + }; + let inode = if let Some(alias) = &alias { + self.create_inode(inodes, kind, true, alias.clone()) + } else { + self.create_inode(inodes, kind, true, path.to_string_lossy().into_owned()) + } + .map_err(|e| { + format!( + "Failed to create inode for preopened dir: WASI error code: {}", + e + ) + })?; + let fd_flags = { + let mut fd_flags = 0; + if *read { + fd_flags |= Fd::READ; + } + if *write { + // TODO: introduce API for finer grained control + fd_flags |= Fd::WRITE | Fd::APPEND | Fd::TRUNCATE; + } + if *create { + fd_flags |= Fd::CREATE; + } + fd_flags + }; + let fd = self + .create_fd(rights, rights, Fdflags::empty(), fd_flags, inode.clone()) + .map_err(|e| format!("Could not open fd for file {:?}: {}", path, e))?; + { + let mut guard = self.root_inode.write(); + if let Kind::Root { entries } = guard.deref_mut() { + let key = if let Some(alias) = &alias { + alias.clone() + } else { + path.to_string_lossy().into_owned() + }; + let existing_entry = entries.insert(key.clone(), inode); + if existing_entry.is_some() && !ignore_duplicates { + return Err(format!("Found duplicate entry for alias `{}`", key)); + } + } + } + self.preopen_fds.write().unwrap().push(fd); + } + + Ok(()) + } + + pub(crate) fn create_std_dev_inner( &self, inodes: &WasiInodes, handle: Box, diff --git a/lib/wasix/src/journal/effector/memory_and_snapshot.rs b/lib/wasix/src/journal/effector/memory_and_snapshot.rs new file mode 100644 index 00000000000..a53ec4465d2 --- /dev/null +++ b/lib/wasix/src/journal/effector/memory_and_snapshot.rs @@ -0,0 +1,94 @@ +use super::*; + +impl JournalEffector { + pub fn save_memory_and_snapshot( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + process: &mut MutexGuard<'_, WasiProcessInner>, + trigger: SnapshotTrigger, + ) -> anyhow::Result<()> { + let env = ctx.data(); + let memory = unsafe { env.memory_view(ctx) }; + + // Compute all the regions that we need to save which is basically + // everything in the memory except for the memory stacks. + // + // We do not want the regions to be greater than 64KB as this will + // otherwise create too much inefficiency. We choose 64KB as its + // aligned with the standard WASM page size. + let mut cur = 0u64; + let mut regions = LinkedList::>::new(); + while cur < memory.data_size() { + let mut again = false; + let mut end = memory.data_size().min(cur + 65536); + for (_, thread) in process.threads.iter() { + let layout = thread.memory_layout(); + if cur >= layout.stack_lower && cur < layout.stack_upper { + cur = layout.stack_upper; + again = true; + break; + } + if end > layout.stack_lower && end < layout.stack_upper { + end = end.min(layout.stack_lower); + } + } + if again { + continue; + } + regions.push_back(cur..end); + cur = end; + } + + // Now that we know all the regions that need to be saved we + // enter a processing loop that dumps all the data to the log + // file in an orderly manner. + let memory = unsafe { env.memory_view(ctx) }; + let journal = ctx.data().active_journal()?; + + for region in regions { + // We grab this region of memory as a vector and hash + // it, which allows us to make some logging efficiency + // gains. + let data = memory + .copy_range_to_vec(region.clone()) + .map_err(mem_error_to_wasi)?; + + // Now we write it to the snap snapshot capturer + journal + .write(JournalEntry::UpdateMemoryRegionV1 { + region, + data: data.into(), + }) + .map_err(map_snapshot_err)?; + } + + // Finally we mark the end of the snapshot so that + // it can act as a restoration point + let when = SystemTime::now(); + journal + .write(JournalEntry::SnapshotV1 { when, trigger }) + .map_err(map_snapshot_err)?; + Ok(()) + } + + /// # Safety + /// + /// This function manipulates the memory of the process and thus must be executed + /// by the WASM process thread itself. + /// + pub unsafe fn apply_memory( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + region: Range, + data: &[u8], + ) -> anyhow::Result<()> { + let (env, mut store) = ctx.data_and_store_mut(); + + let memory = unsafe { env.memory() }; + memory.grow_at_least(&mut store, region.end + data.len() as u64)?; + + let memory = unsafe { env.memory_view(&store) }; + memory + .write(region.start, data.as_ref()) + .map_err(|err| WasiRuntimeError::Runtime(RuntimeError::user(err.into())))?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/mod.rs b/lib/wasix/src/journal/effector/mod.rs new file mode 100644 index 00000000000..bf9e464da14 --- /dev/null +++ b/lib/wasix/src/journal/effector/mod.rs @@ -0,0 +1,105 @@ +pub(super) use std::{ + borrow::Cow, collections::LinkedList, ops::Range, sync::MutexGuard, time::SystemTime, +}; + +pub(super) use anyhow::bail; +pub(super) use bytes::Bytes; +pub(super) use wasmer::{FunctionEnvMut, RuntimeError, WasmPtr}; +pub(super) use wasmer_types::MemorySize; +pub(super) use wasmer_wasix_types::{ + types::__wasi_ciovec_t, + wasi::{ + Advice, EpollCtl, EpollEventCtl, Errno, ExitCode, Fd, Fdflags, Filesize, Fstflags, + LookupFlags, Oflags, Rights, Snapshot0Clockid, Timestamp, Whence, + }, +}; + +pub(super) use crate::{ + mem_error_to_wasi, + os::task::process::WasiProcessInner, + syscalls::{fd_write_internal, FdWriteSource}, + utils::map_snapshot_err, + WasiEnv, WasiRuntimeError, WasiThreadId, +}; + +use super::*; + +#[cfg(feature = "journal")] +mod syscalls { + pub(super) use super::*; + mod chdir; + mod clock_time; + mod epoll_create; + mod epoll_ctl; + mod fd_advise; + mod fd_allocate; + mod fd_close; + mod fd_duplicate; + mod fd_event; + mod fd_pipe; + mod fd_renumber; + mod fd_seek; + mod fd_set_flags; + mod fd_set_rights; + mod fd_set_size; + mod fd_set_times; + mod fd_write; + mod path_create_directory; + mod path_link; + mod path_open; + mod path_remove_directory; + mod path_rename; + mod path_set_times; + mod path_symlink; + mod path_unlink; + mod port_addr_add; + mod port_addr_clear; + mod port_addr_remove; + mod port_bridge; + mod port_dhcp_acquire; + mod port_gateway_set; + mod port_route_add; + mod port_route_clear; + mod port_route_remove; + mod port_unbridge; + mod sock_accept; + mod sock_bind; + mod sock_connect; + mod sock_join_ipv4_multicast; + mod sock_join_ipv6_multicast; + mod sock_leave_ipv4_multicast; + mod sock_leave_ipv6_multicast; + mod sock_listen; + mod sock_open; + mod sock_send; + mod sock_send_file; + mod sock_send_to; + mod sock_set_opt_flag; + mod sock_set_opt_size; + mod sock_set_opt_time; + mod sock_shutdown; + mod tty_set; +} +#[cfg(feature = "journal")] +mod memory_and_snapshot; +#[cfg(feature = "journal")] +mod process_exit; +#[cfg(feature = "journal")] +mod save_event; +#[cfg(feature = "journal")] +mod thread_exit; +#[cfg(feature = "journal")] +mod thread_state; + +/// The journal effector is an adapter that will be removed in a future refactor. +/// Its purpose is to put the code that does mappings from WASM memory through its +/// abstractions into concrete journal objects that can be stored. Instead of this +/// what should be done is that the syscalls themselves can be represented as a +/// strongly typed object that can be passed directly to the journal but in order +/// to do this we require an extensive refactoring of the WASIX syscalls which +/// is not in scope at this time. +/// +/// Separating this out now makes it easier to eliminate later without hurting the +/// journal event abstraction through leaking abstraction layers. +#[derive(Debug, Clone)] +pub struct JournalEffector {} diff --git a/lib/wasix/src/journal/effector/process_exit.rs b/lib/wasix/src/journal/effector/process_exit.rs new file mode 100644 index 00000000000..d57264e50fa --- /dev/null +++ b/lib/wasix/src/journal/effector/process_exit.rs @@ -0,0 +1,38 @@ +use virtual_mio::InlineWaker; + +use super::*; + +impl JournalEffector { + pub fn save_process_exit(env: &WasiEnv, exit_code: Option) -> anyhow::Result<()> { + env.active_journal()? + .write(JournalEntry::ProcessExitV1 { exit_code }) + .map_err(map_snapshot_err)?; + Ok(()) + } + + /// # Safety + /// + /// This function manipulates the memory of the process and thus must be executed + /// by the WASM process thread itself. + /// + pub unsafe fn apply_process_exit( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + exit_code: Option, + ) -> anyhow::Result<()> { + let env = ctx.data(); + // If we are in the phase of replaying journals then we + // close all the file descriptors but we don't actually send + // any signals + if env.replaying_journal { + let state = env.state.clone(); + InlineWaker::block_on(state.fs.close_all()); + } else { + env.blocking_on_exit(exit_code); + } + + // Reset the memory back to a zero size + let memory = ctx.data_mut().inner().memory().clone(); + memory.reset(ctx)?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/save_event.rs b/lib/wasix/src/journal/effector/save_event.rs new file mode 100644 index 00000000000..415b61c434f --- /dev/null +++ b/lib/wasix/src/journal/effector/save_event.rs @@ -0,0 +1,19 @@ +use super::*; + +impl JournalEffector { + pub(crate) fn save_event( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + event: JournalEntry, + ) -> anyhow::Result<()> { + let env = ctx.data(); + if !env.should_journal() { + return Ok(()); + } + + ctx.data() + .active_journal()? + .write(event) + .map_err(map_snapshot_err)?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/chdir.rs b/lib/wasix/src/journal/effector/syscalls/chdir.rs new file mode 100644 index 00000000000..aa3041a1562 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/chdir.rs @@ -0,0 +1,18 @@ +use super::*; + +impl JournalEffector { + pub fn save_chdir(ctx: &mut FunctionEnvMut<'_, WasiEnv>, path: String) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::ChangeDirectoryV1 { path: path.into() }) + } + + pub fn apply_chdir(ctx: &mut FunctionEnvMut<'_, WasiEnv>, path: &str) -> anyhow::Result<()> { + crate::syscalls::chdir_internal(ctx, path).map_err(|err| { + anyhow::format_err!( + "snapshot restore error: failed to change directory (path={}) - {}", + path, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/clock_time.rs b/lib/wasix/src/journal/effector/syscalls/clock_time.rs new file mode 100644 index 00000000000..c196732dfe2 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/clock_time.rs @@ -0,0 +1,28 @@ +use super::*; + +impl JournalEffector { + pub fn save_clock_time_set( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + clock_id: Snapshot0Clockid, + time: Timestamp, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::SetClockTimeV1 { clock_id, time }) + } + + pub fn apply_clock_time_set( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + clock_id: Snapshot0Clockid, + time: Timestamp, + ) -> anyhow::Result<()> { + let ret = crate::syscalls::clock_time_set_internal(ctx, clock_id, time); + if ret != Errno::Success { + bail!( + "journal restore error: failed to set clock time (clock_id={:?}, time={}) - {}", + clock_id, + time, + ret + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/epoll_create.rs b/lib/wasix/src/journal/effector/syscalls/epoll_create.rs new file mode 100644 index 00000000000..7fbe2ae227a --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/epoll_create.rs @@ -0,0 +1,29 @@ +use super::*; + +impl JournalEffector { + pub fn save_epoll_create(ctx: &mut FunctionEnvMut<'_, WasiEnv>, fd: Fd) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::EpollCreateV1 { fd }) + } + + pub fn apply_epoll_create(ctx: &mut FunctionEnvMut<'_, WasiEnv>, fd: Fd) -> anyhow::Result<()> { + let ret_fd = crate::syscalls::epoll_create_internal(ctx) + .map_err(|err| { + anyhow::format_err!("journal restore error: failed to create epoll - {}", err) + })? + .map_err(|err| { + anyhow::format_err!("journal restore error: failed to create epoll - {}", err) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after epoll create (from={}, to={}) - {}", + ret_fd, + fd, + ret + ); + } + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/epoll_ctl.rs b/lib/wasix/src/journal/effector/syscalls/epoll_ctl.rs new file mode 100644 index 00000000000..e4719999db4 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/epoll_ctl.rs @@ -0,0 +1,51 @@ +use super::*; + +impl JournalEffector { + pub fn save_epoll_ctl( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + epfd: Fd, + op: EpollCtl, + fd: Fd, + event: Option, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::EpollCtlV1 { + epfd, + op, + fd, + event, + }, + ) + } + + pub fn apply_epoll_ctl( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + pfd: Fd, + op: EpollCtl, + fd: Fd, + event: Option, + ) -> anyhow::Result<()> { + crate::syscalls::epoll_ctl_internal(ctx, pfd, op, fd, event.as_ref()) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to epoll ctl (pfd={}, op={:?}, fd={}) - {}", + pfd, + op, + fd, + err + ) + })? + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to epoll ctl (pfd={}, op={:?}, fd={}) - {}", + pfd, + op, + fd, + err + ) + })?; + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_advise.rs b/lib/wasix/src/journal/effector/syscalls/fd_advise.rs new file mode 100644 index 00000000000..12cd5e17802 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_advise.rs @@ -0,0 +1,41 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_advise( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: Filesize, + len: Filesize, + advice: Advice, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::FileDescriptorAdviseV1 { + fd, + offset, + len, + advice, + }, + ) + } + + pub fn apply_fd_advise( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: Filesize, + len: Filesize, + advice: Advice, + ) -> anyhow::Result<()> { + crate::syscalls::fd_advise_internal(ctx, fd, offset, len, advice).map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to advise file descriptor (fd={}, offset={}, len={}, advice={:?}) - {}", + fd, + offset, + len, + advice, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_allocate.rs b/lib/wasix/src/journal/effector/syscalls/fd_allocate.rs new file mode 100644 index 00000000000..6ecacb827eb --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_allocate.rs @@ -0,0 +1,34 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_allocate( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: Filesize, + len: Filesize, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::FileDescriptorAllocateV1 { fd, offset, len }, + ) + } + + pub fn apply_fd_allocate( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: Filesize, + len: Filesize, + ) -> anyhow::Result<()> { + crate::syscalls::fd_allocate_internal(ctx, fd, offset, len) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to allocate on file descriptor (fd={}, offset={}, len={}) - {}", + fd, + offset, + len, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_close.rs b/lib/wasix/src/journal/effector/syscalls/fd_close.rs new file mode 100644 index 00000000000..4ec66918699 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_close.rs @@ -0,0 +1,20 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_close(ctx: &mut FunctionEnvMut<'_, WasiEnv>, fd: Fd) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::CloseFileDescriptorV1 { fd }) + } + + pub fn apply_fd_close(ctx: &mut FunctionEnvMut<'_, WasiEnv>, fd: Fd) -> anyhow::Result<()> { + let env = ctx.data(); + let (_, state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; + if let Err(err) = state.fs.close_fd(fd) { + bail!( + "journal restore error: failed to close descriptor (fd={}) - {}", + fd, + err + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_duplicate.rs b/lib/wasix/src/journal/effector/syscalls/fd_duplicate.rs new file mode 100644 index 00000000000..a53be32d4c4 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_duplicate.rs @@ -0,0 +1,44 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_duplicate( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + original_fd: Fd, + copied_fd: Fd, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + }, + ) + } + + pub fn apply_fd_duplicate( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + original_fd: Fd, + copied_fd: Fd, + ) -> anyhow::Result<()> { + let ret_fd = crate::syscalls::fd_dup_internal(ctx, original_fd) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to duplicate file descriptor (original={}, copied={}) - {}", + original_fd, + copied_fd, + err + ) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, copied_fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after duplicate (from={}, to={}) - {}", + ret_fd, + copied_fd, + ret + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_event.rs b/lib/wasix/src/journal/effector/syscalls/fd_event.rs new file mode 100644 index 00000000000..a30738947a2 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_event.rs @@ -0,0 +1,47 @@ +use wasmer_wasix_types::wasi::EventFdFlags; + +use super::*; + +impl JournalEffector { + pub fn save_fd_event( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + initial_val: u64, + flags: EventFdFlags, + fd: Fd, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::CreateEventV1 { + initial_val, + flags, + fd, + }, + ) + } + + pub fn apply_fd_event( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + initial_val: u64, + flags: EventFdFlags, + fd: Fd, + ) -> anyhow::Result<()> { + let ret_fd = crate::syscalls::fd_event_internal(ctx, initial_val, flags) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!("journal restore error: failed to create event - {}", err) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after create event (from={}, to={}) - {}", + ret_fd, + fd, + ret + ); + } + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_pipe.rs b/lib/wasix/src/journal/effector/syscalls/fd_pipe.rs new file mode 100644 index 00000000000..21b32d8a402 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_pipe.rs @@ -0,0 +1,43 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_pipe( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd1: Fd, + fd2: Fd, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::CreatePipeV1 { fd1, fd2 }) + } + + pub fn apply_fd_pipe( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd1: Fd, + fd2: Fd, + ) -> anyhow::Result<()> { + let (ret_fd1, ret_fd2) = crate::syscalls::fd_pipe_internal(ctx).map_err(|err| { + anyhow::format_err!("journal restore error: failed to create pipe - {}", err) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd1, fd1); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after create pipe (from={}, to={}) - {}", + ret_fd1, + fd1, + ret + ); + } + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd2, fd2); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after create pipe (from={}, to={}) - {}", + ret_fd2, + fd2, + ret + ); + } + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_renumber.rs b/lib/wasix/src/journal/effector/syscalls/fd_renumber.rs new file mode 100644 index 00000000000..c439d753f23 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_renumber.rs @@ -0,0 +1,34 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_renumber( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + from: Fd, + to: Fd, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::RenumberFileDescriptorV1 { + old_fd: from, + new_fd: to, + }, + ) + } + + pub fn apply_fd_renumber( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + from: Fd, + to: Fd, + ) -> anyhow::Result<()> { + let ret = crate::syscalls::fd_renumber_internal(ctx, from, to); + if ret != Errno::Success { + bail!( + "journal restore error: failed to renumber descriptor (from={}, to={}) - {}", + from, + to, + ret + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_seek.rs b/lib/wasix/src/journal/effector/syscalls/fd_seek.rs new file mode 100644 index 00000000000..7cd7787ba69 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_seek.rs @@ -0,0 +1,33 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_seek( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: i64, + whence: Whence, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::FileDescriptorSeekV1 { fd, offset, whence }, + ) + } + + pub fn apply_fd_seek( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: i64, + whence: Whence, + ) -> anyhow::Result<()> { + crate::syscalls::fd_seek_internal(ctx, fd, offset, whence)?.map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to seek (fd={}, offset={}, whence={:?}) - {}", + fd, + offset, + whence, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_set_flags.rs b/lib/wasix/src/journal/effector/syscalls/fd_set_flags.rs new file mode 100644 index 00000000000..669041a88da --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_set_flags.rs @@ -0,0 +1,27 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_set_flags( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + flags: Fdflags, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::FileDescriptorSetFlagsV1 { fd, flags }) + } + + pub fn apply_fd_set_flags( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + flags: Fdflags, + ) -> anyhow::Result<()> { + crate::syscalls::fd_fdstat_set_flags_internal(ctx, fd, flags).map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set file flags (fd={}, flags={:?}) - {}", + fd, + flags, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_set_rights.rs b/lib/wasix/src/journal/effector/syscalls/fd_set_rights.rs new file mode 100644 index 00000000000..6d77752b17f --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_set_rights.rs @@ -0,0 +1,38 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_set_rights( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::FileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + }, + ) + } + + pub fn apply_fd_set_rights( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + ) -> anyhow::Result<()> { + crate::syscalls::fd_fdstat_set_rights_internal(ctx, fd, fs_rights_base, fs_rights_inheriting) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set file rights (fd={}, fs_rights_base={:?}, fs_rights_inheriting={:?}) - {}", + fd, + fs_rights_base, + fs_rights_inheriting, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_set_size.rs b/lib/wasix/src/journal/effector/syscalls/fd_set_size.rs new file mode 100644 index 00000000000..58980c12207 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_set_size.rs @@ -0,0 +1,27 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_set_size( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + st_size: Filesize, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::FileDescriptorSetSizeV1 { fd, st_size }) + } + + pub fn apply_fd_set_size( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + st_size: Filesize, + ) -> anyhow::Result<()> { + crate::syscalls::fd_filestat_set_size_internal(ctx, fd, st_size).map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set file size (fd={}, st_size={}) - {}", + fd, + st_size, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_set_times.rs b/lib/wasix/src/journal/effector/syscalls/fd_set_times.rs new file mode 100644 index 00000000000..633ba079c03 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_set_times.rs @@ -0,0 +1,42 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_set_times( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::FileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags, + }, + ) + } + + pub fn apply_fd_set_times( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, + ) -> anyhow::Result<()> { + crate::syscalls::fd_filestat_set_times_internal(ctx, fd, st_atim, st_mtim, fst_flags) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set file times (fd={}, st_atim={}, st_mtim={}, fst_flags={:?}) - {}", + fd, + st_atim, + st_mtim, + fst_flags, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/fd_write.rs b/lib/wasix/src/journal/effector/syscalls/fd_write.rs new file mode 100644 index 00000000000..689adc97a87 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/fd_write.rs @@ -0,0 +1,72 @@ +use super::*; + +impl JournalEffector { + pub fn save_fd_write( + ctx: &FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + mut offset: u64, + written: usize, + iovs: WasmPtr<__wasi_ciovec_t, M>, + iovs_len: M::Offset, + ) -> anyhow::Result<()> { + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let iovs_arr = iovs.slice(&memory, iovs_len)?; + + let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; + let mut remaining: M::Offset = TryFrom::::try_from(written).unwrap_or_default(); + for iovs in iovs_arr.iter() { + let sub = iovs.buf_len.min(remaining); + if sub == M::ZERO { + continue; + } + remaining -= sub; + + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, sub) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + let data = Cow::Borrowed(buf.as_ref()); + let data_len = data.len(); + + ctx.data() + .active_journal()? + .write(JournalEntry::FileDescriptorWriteV1 { + fd, + offset, + data, + is_64bit: M::is_64bit(), + }) + .map_err(map_snapshot_err)?; + + offset += data_len as u64; + } + Ok(()) + } + + pub fn apply_fd_write( + ctx: &FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + offset: u64, + data: Cow<'_, [u8]>, + ) -> anyhow::Result<()> { + fd_write_internal( + ctx, + fd, + FdWriteSource::<'_, M>::Buffer(data), + offset, + true, + false, + )? + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to write to descriptor (fd={}, offset={}) - {}", + fd, + offset, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_create_directory.rs b/lib/wasix/src/journal/effector/syscalls/path_create_directory.rs new file mode 100644 index 00000000000..447c60393af --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_create_directory.rs @@ -0,0 +1,33 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_create_directory( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + path: String, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::CreateDirectoryV1 { + fd, + path: path.into(), + }, + ) + } + + pub fn apply_path_create_directory( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + path: &str, + ) -> anyhow::Result<()> { + crate::syscalls::path_create_directory_internal(ctx, fd, path).map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to create directory path (fd={}, path={}) - {}", + fd, + path, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_link.rs b/lib/wasix/src/journal/effector/syscalls/path_link.rs new file mode 100644 index 00000000000..822f671d09c --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_link.rs @@ -0,0 +1,46 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_link( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_fd: Fd, + old_flags: LookupFlags, + old_path: String, + new_fd: Fd, + new_path: String, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::CreateHardLinkV1 { + old_fd, + old_flags, + old_path: old_path.into(), + new_fd, + new_path: new_path.into(), + }, + ) + } + + pub fn apply_path_link( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_fd: Fd, + old_flags: LookupFlags, + old_path: &str, + new_fd: Fd, + new_path: &str, + ) -> anyhow::Result<()> { + crate::syscalls::path_link_internal(ctx, old_fd, old_flags, old_path, new_fd, new_path) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to create hard link (old_fd={}, old_flags={}, old_path={}, new_fd={}, new_path={}) - {}", + old_fd, + old_flags, + old_path, + new_fd, + new_path, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_open.rs b/lib/wasix/src/journal/effector/syscalls/path_open.rs new file mode 100644 index 00000000000..f63e303bceb --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_open.rs @@ -0,0 +1,77 @@ +use super::*; + +impl JournalEffector { + #[allow(clippy::too_many_arguments)] + pub fn save_path_open( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + dirfd: Fd, + dirflags: LookupFlags, + path: String, + o_flags: Oflags, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + fs_flags: Fdflags, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::OpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + path: path.into(), + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + }, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn apply_path_open( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + dirfd: Fd, + dirflags: LookupFlags, + path: &str, + o_flags: Oflags, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + fs_flags: Fdflags, + ) -> anyhow::Result<()> { + let res = crate::syscalls::path_open_internal( + ctx, + dirfd, + dirflags, + path, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + ); + let ret_fd = match res? { + Ok(fd) => fd, + Err(err) => { + bail!( + "journal restore error: failed to open descriptor (fd={}, path={}) - {}", + fd, + path, + err + ); + } + }; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after open (from={}, to={}) - {}", + ret_fd, + fd, + ret + ); + } + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_remove_directory.rs b/lib/wasix/src/journal/effector/syscalls/path_remove_directory.rs new file mode 100644 index 00000000000..b45ec07d1a4 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_remove_directory.rs @@ -0,0 +1,31 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_remove_directory( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + path: String, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::RemoveDirectoryV1 { + fd, + path: Cow::Owned(path), + }, + ) + } + + pub fn apply_path_remove_directory( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + path: &str, + ) -> anyhow::Result<()> { + if let Err(err) = crate::syscalls::path_remove_directory_internal(ctx, fd, path) { + bail!( + "journal restore error: failed to remove directory - {}", + err + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_rename.rs b/lib/wasix/src/journal/effector/syscalls/path_rename.rs new file mode 100644 index 00000000000..41ca232f8b8 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_rename.rs @@ -0,0 +1,42 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_rename( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_fd: Fd, + old_path: String, + new_fd: Fd, + new_path: String, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::PathRenameV1 { + old_fd, + old_path: Cow::Owned(old_path), + new_fd, + new_path: Cow::Owned(new_path), + }, + ) + } + + pub fn apply_path_rename( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_fd: Fd, + old_path: &str, + new_fd: Fd, + new_path: &str, + ) -> anyhow::Result<()> { + let ret = crate::syscalls::path_rename_internal(ctx, old_fd, old_path, new_fd, new_path)?; + if ret != Errno::Success { + bail!( + "journal restore error: failed to rename path (old_fd={}, old_path={}, new_fd={}, new_path={}) - {}", + old_fd, + old_path, + new_fd, + new_path, + ret + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_set_times.rs b/lib/wasix/src/journal/effector/syscalls/path_set_times.rs new file mode 100644 index 00000000000..e40b3bae698 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_set_times.rs @@ -0,0 +1,50 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_set_times( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + flags: LookupFlags, + path: String, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::PathSetTimesV1 { + fd, + flags, + path: path.into(), + st_atim, + st_mtim, + fst_flags, + }, + ) + } + + pub fn apply_path_set_times( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + flags: LookupFlags, + path: &str, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, + ) -> anyhow::Result<()> { + crate::syscalls::path_filestat_set_times_internal(ctx, fd, flags, path, st_atim, st_mtim, fst_flags) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set path times (fd={}, flags={}, path={}, st_atim={}, st_mtim={}, fst_flags={:?}) - {}", + fd, + flags, + path, + st_atim, + st_mtim, + fst_flags, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_symlink.rs b/lib/wasix/src/journal/effector/syscalls/path_symlink.rs new file mode 100644 index 00000000000..60591cae7c4 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_symlink.rs @@ -0,0 +1,38 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_symlink( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_path: String, + fd: Fd, + new_path: String, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::CreateSymbolicLinkV1 { + old_path: old_path.into(), + fd, + new_path: new_path.into(), + }, + ) + } + + pub fn apply_path_symlink( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_path: &str, + fd: Fd, + new_path: &str, + ) -> anyhow::Result<()> { + crate::syscalls::path_symlink_internal(ctx, old_path, fd, new_path) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to create symlink (old_path={}, fd={}, new_path={}) - {}", + old_path, + fd, + new_path, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/path_unlink.rs b/lib/wasix/src/journal/effector/syscalls/path_unlink.rs new file mode 100644 index 00000000000..9141a7fe8cf --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/path_unlink.rs @@ -0,0 +1,34 @@ +use super::*; + +impl JournalEffector { + pub fn save_path_unlink( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + path: String, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::UnlinkFileV1 { + fd, + path: Cow::Owned(path), + }, + ) + } + + pub fn apply_path_unlink( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + path: &str, + ) -> anyhow::Result<()> { + let ret = crate::syscalls::path_unlink_file_internal(ctx, fd, path)?; + if ret != Errno::Success { + bail!( + "journal restore error: failed to remove file (fd={}, path={}) - {}", + fd, + path, + ret + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_addr_add.rs b/lib/wasix/src/journal/effector/syscalls/port_addr_add.rs new file mode 100644 index 00000000000..ae508e774ea --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_addr_add.rs @@ -0,0 +1,29 @@ +use virtual_net::IpCidr; + +use super::*; + +impl JournalEffector { + pub fn save_port_addr_add( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + cidr: IpCidr, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortAddAddrV1 { cidr }) + } + + pub fn apply_port_addr_add( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + cidr: IpCidr, + ) -> anyhow::Result<()> { + crate::syscalls::port_addr_add_internal(ctx, cidr) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to add address to port file descriptor (cidr={:?}) - {}", + cidr, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_addr_clear.rs b/lib/wasix/src/journal/effector/syscalls/port_addr_clear.rs new file mode 100644 index 00000000000..d68a2a8e4c6 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_addr_clear.rs @@ -0,0 +1,20 @@ +use super::*; + +impl JournalEffector { + pub fn save_port_addr_clear(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortAddrClearV1) + } + + pub fn apply_port_addr_clear(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + crate::syscalls::port_addr_clear_internal(ctx) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to clear port addresses - {}", + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_addr_remove.rs b/lib/wasix/src/journal/effector/syscalls/port_addr_remove.rs new file mode 100644 index 00000000000..b2d46f926db --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_addr_remove.rs @@ -0,0 +1,29 @@ +use std::net::IpAddr; + +use super::*; + +impl JournalEffector { + pub fn save_port_addr_remove( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + addr: IpAddr, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortDelAddrV1 { addr }) + } + + pub fn apply_port_addr_remove( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + addr: IpAddr, + ) -> anyhow::Result<()> { + crate::syscalls::port_addr_remove_internal(ctx, addr) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to remove address from port (ip={}) - {}", + addr, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_bridge.rs b/lib/wasix/src/journal/effector/syscalls/port_bridge.rs new file mode 100644 index 00000000000..bdfd5ce54dc --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_bridge.rs @@ -0,0 +1,41 @@ +use virtual_net::StreamSecurity; + +use super::*; + +impl JournalEffector { + pub fn save_port_bridge( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + network: String, + token: String, + security: StreamSecurity, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::PortBridgeV1 { + network: network.into(), + token: token.into(), + security, + }, + ) + } + + pub fn apply_port_bridge( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + network: &str, + token: &str, + security: StreamSecurity, + ) -> anyhow::Result<()> { + crate::syscalls::port_bridge_internal(ctx, network, token, security) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to bridge the network file descriptor (network={}, security={:?}) - {}", + network, + security, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_dhcp_acquire.rs b/lib/wasix/src/journal/effector/syscalls/port_dhcp_acquire.rs new file mode 100644 index 00000000000..98ef069b181 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_dhcp_acquire.rs @@ -0,0 +1,20 @@ +use super::*; + +impl JournalEffector { + pub fn save_port_dhcp_acquire(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortDhcpAcquireV1) + } + + pub fn apply_port_dhcp_acquire(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + crate::syscalls::port_dhcp_acquire_internal(ctx) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to acquire DHCP address - {}", + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_gateway_set.rs b/lib/wasix/src/journal/effector/syscalls/port_gateway_set.rs new file mode 100644 index 00000000000..a0251a02e88 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_gateway_set.rs @@ -0,0 +1,29 @@ +use std::net::IpAddr; + +use super::*; + +impl JournalEffector { + pub fn save_port_gateway_set( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortGatewaySetV1 { ip }) + } + + pub fn apply_port_gateway_set( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, + ) -> anyhow::Result<()> { + crate::syscalls::port_gateway_set_internal(ctx, ip) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set gateway address (ip={}) - {}", + ip, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_route_add.rs b/lib/wasix/src/journal/effector/syscalls/port_route_add.rs new file mode 100644 index 00000000000..e2f44eb84e1 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_route_add.rs @@ -0,0 +1,53 @@ +use std::{net::IpAddr, time::Duration}; +use virtual_net::IpCidr; + +use super::*; + +impl JournalEffector { + pub fn save_port_route_add( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::PortRouteAddV1 { + cidr, + via_router, + preferred_until, + expires_at, + }, + ) + } + + pub fn apply_port_route_add( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, + ) -> anyhow::Result<()> { + crate::syscalls::port_route_add_internal( + ctx, + cidr, + via_router, + preferred_until, + expires_at, + ) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to add route (cidr={:?}, via_router={}, preferred_until={:?}, expires_at={:?}) - {}", + cidr, + via_router, + preferred_until, + expires_at, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_route_clear.rs b/lib/wasix/src/journal/effector/syscalls/port_route_clear.rs new file mode 100644 index 00000000000..dfb1774d10c --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_route_clear.rs @@ -0,0 +1,20 @@ +use super::*; + +impl JournalEffector { + pub fn save_port_route_clear(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortRouteClearV1) + } + + pub fn apply_port_route_clear(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + crate::syscalls::port_route_clear_internal(ctx) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to clear routing table - {}", + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_route_remove.rs b/lib/wasix/src/journal/effector/syscalls/port_route_remove.rs new file mode 100644 index 00000000000..3a980bfff65 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_route_remove.rs @@ -0,0 +1,29 @@ +use std::net::IpAddr; + +use super::*; + +impl JournalEffector { + pub fn save_port_route_remove( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortRouteDelV1 { ip }) + } + + pub fn apply_port_route_remove( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, + ) -> anyhow::Result<()> { + crate::syscalls::port_route_remove_internal(ctx, ip) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to remove route (ip={}) - {}", + ip, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/port_unbridge.rs b/lib/wasix/src/journal/effector/syscalls/port_unbridge.rs new file mode 100644 index 00000000000..0fc1ae98d4e --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/port_unbridge.rs @@ -0,0 +1,20 @@ +use super::*; + +impl JournalEffector { + pub fn save_port_unbridge(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::PortUnbridgeV1) + } + + pub fn apply_port_unbridge(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> anyhow::Result<()> { + crate::syscalls::port_unbridge_internal(ctx) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to un-bridge network - {}", + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_accept.rs b/lib/wasix/src/journal/effector/syscalls/sock_accept.rs new file mode 100644 index 00000000000..3482074dcfe --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_accept.rs @@ -0,0 +1,83 @@ +use std::net::SocketAddr; + +use crate::{ + fs::Kind, + net::socket::{InodeSocket, InodeSocketKind}, +}; + +use super::*; + +impl JournalEffector { + pub fn save_sock_accepted( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + listen_fd: Fd, + fd: Fd, + peer_addr: SocketAddr, + fd_flags: Fdflags, + nonblocking: bool, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags, + non_blocking: nonblocking, + }, + ) + } + + pub fn apply_sock_accepted( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + _listen_fd: Fd, + fd: Fd, + peer_addr: SocketAddr, + fd_flags: Fdflags, + nonblocking: bool, + ) -> anyhow::Result<()> { + let kind = Kind::Socket { + socket: InodeSocket::new(InodeSocketKind::RemoteTcpStream { peer_addr }), + }; + + let env = ctx.data(); + let state = env.state(); + let inodes = &state.inodes; + let inode = state + .fs + .create_inode_with_default_stat(inodes, kind, false, "socket".into()); + + let mut new_flags = Fdflags::empty(); + if nonblocking { + new_flags.set(Fdflags::NONBLOCK, true); + } + + let mut new_flags = Fdflags::empty(); + if fd_flags.contains(Fdflags::NONBLOCK) { + new_flags.set(Fdflags::NONBLOCK, true); + } + + let rights = Rights::all_socket(); + let ret_fd = state + .fs + .create_fd(rights, rights, new_flags, 0, inode) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to create remote accepted socket - {}", + err + ) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after accepting socket (from={}, to={}) - {}", + ret_fd, + fd, + ret + ); + } + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_bind.rs b/lib/wasix/src/journal/effector/syscalls/sock_bind.rs new file mode 100644 index 00000000000..73c3e3a7b81 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_bind.rs @@ -0,0 +1,32 @@ +use std::net::SocketAddr; + +use super::*; + +impl JournalEffector { + pub fn save_sock_bind( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + addr: SocketAddr, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::SocketBindV1 { fd, addr }) + } + + pub fn apply_sock_bind( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + addr: SocketAddr, + ) -> anyhow::Result<()> { + crate::syscalls::sock_bind_internal(ctx, fd, addr) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to bind socket to address (fd={}, addr={}) - {}", + fd, + addr, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_connect.rs b/lib/wasix/src/journal/effector/syscalls/sock_connect.rs new file mode 100644 index 00000000000..3c7af982efd --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_connect.rs @@ -0,0 +1,58 @@ +use std::net::SocketAddr; + +use crate::{ + fs::Kind, + net::socket::{InodeSocket, InodeSocketKind}, +}; + +use super::*; + +impl JournalEffector { + pub fn save_sock_connect( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + addr: SocketAddr, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::SocketConnectedV1 { fd, addr }) + } + + pub fn apply_sock_connect( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + addr: SocketAddr, + ) -> anyhow::Result<()> { + let kind = Kind::Socket { + socket: InodeSocket::new(InodeSocketKind::RemoteTcpStream { peer_addr: addr }), + }; + + let env = ctx.data(); + let state = env.state(); + let inodes = &state.inodes; + let inode = state + .fs + .create_inode_with_default_stat(inodes, kind, false, "socket".into()); + + let rights = Rights::all_socket(); + let ret_fd = state + .fs + .create_fd(rights, rights, Fdflags::empty(), 0, inode) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to create remote connected socket - {}", + err + ) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after connecting the socket (from={}, to={}) - {}", + ret_fd, + fd, + ret + ); + } + + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_join_ipv4_multicast.rs b/lib/wasix/src/journal/effector/syscalls/sock_join_ipv4_multicast.rs new file mode 100644 index 00000000000..589bb08cafd --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_join_ipv4_multicast.rs @@ -0,0 +1,42 @@ +use std::net::Ipv4Addr; + +use super::*; + +impl JournalEffector { + pub fn save_sock_join_ipv4_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + }, + ) + } + + pub fn apply_sock_join_ipv4_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + ) -> anyhow::Result<()> { + crate::syscalls::sock_join_multicast_v4_internal(ctx, fd, multiaddr, iface) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to join ipv4 multicast (fd={}, multiaddr={:?}, iface={:?}) - {}", + fd, + multiaddr, + iface, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_join_ipv6_multicast.rs b/lib/wasix/src/journal/effector/syscalls/sock_join_ipv6_multicast.rs new file mode 100644 index 00000000000..17e7d875799 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_join_ipv6_multicast.rs @@ -0,0 +1,42 @@ +use std::net::Ipv6Addr; + +use super::*; + +impl JournalEffector { + pub fn save_sock_join_ipv6_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv6Addr, + iface: u32, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketJoinIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + }, + ) + } + + pub fn apply_sock_join_ipv6_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv6Addr, + iface: u32, + ) -> anyhow::Result<()> { + crate::syscalls::sock_join_multicast_v6_internal(ctx, fd, multiaddr, iface) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to join ipv6 multicast (fd={}, multiaddr={}, iface={}) - {}", + fd, + multiaddr, + iface, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_leave_ipv4_multicast.rs b/lib/wasix/src/journal/effector/syscalls/sock_leave_ipv4_multicast.rs new file mode 100644 index 00000000000..9fd3b78eba5 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_leave_ipv4_multicast.rs @@ -0,0 +1,42 @@ +use std::net::Ipv4Addr; + +use super::*; + +impl JournalEffector { + pub fn save_sock_leave_ipv4_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketLeaveIpv4MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + }, + ) + } + + pub fn apply_sock_leave_ipv4_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, + ) -> anyhow::Result<()> { + crate::syscalls::sock_leave_multicast_v4_internal(ctx, fd, multiaddr, iface) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to leave ipv4 multicast (fd={}, multiaddr={}, iface={}) - {}", + fd, + multiaddr, + iface, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_leave_ipv6_multicast.rs b/lib/wasix/src/journal/effector/syscalls/sock_leave_ipv6_multicast.rs new file mode 100644 index 00000000000..b91f71093bc --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_leave_ipv6_multicast.rs @@ -0,0 +1,42 @@ +use std::net::Ipv6Addr; + +use super::*; + +impl JournalEffector { + pub fn save_sock_leave_ipv6_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv6Addr, + iface: u32, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketLeaveIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + }, + ) + } + + pub fn apply_sock_leave_ipv6_multicast( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + multiaddr: Ipv6Addr, + iface: u32, + ) -> anyhow::Result<()> { + crate::syscalls::sock_leave_multicast_v6_internal(ctx, fd, multiaddr, iface) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to leave ipv6 multicast (fd={}, multiaddr={}, iface={}) - {}", + fd, + multiaddr, + iface, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_listen.rs b/lib/wasix/src/journal/effector/syscalls/sock_listen.rs new file mode 100644 index 00000000000..40a420a2013 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_listen.rs @@ -0,0 +1,36 @@ +use super::*; + +impl JournalEffector { + pub fn save_sock_listen( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + backlog: usize, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketListenV1 { + fd, + backlog: backlog as u32, + }, + ) + } + + pub fn apply_sock_listen( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + backlog: usize, + ) -> anyhow::Result<()> { + crate::syscalls::sock_listen_internal(ctx, fd, backlog) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to listen on socket (fd={}, backlog={}) - {}", + fd, + backlog, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_open.rs b/lib/wasix/src/journal/effector/syscalls/sock_open.rs new file mode 100644 index 00000000000..eaa07b40919 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_open.rs @@ -0,0 +1,54 @@ +use wasmer_wasix_types::wasi::{Addressfamily, SockProto, Socktype}; + +use super::*; + +impl JournalEffector { + pub fn save_sock_open( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + af: Addressfamily, + ty: Socktype, + pt: SockProto, + fd: Fd, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::SocketOpenV1 { af, ty, pt, fd }) + } + + pub fn apply_sock_open( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + af: Addressfamily, + ty: Socktype, + pt: SockProto, + fd: Fd, + ) -> anyhow::Result<()> { + let ret_fd = crate::syscalls::sock_open_internal(ctx, af, ty, pt) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to open socket (af={:?}, ty={:?}, pt={:?}) - {}", + af, + ty, + pt, + err + ) + })? + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to open socket (af={:?}, ty={:?}, pt={:?}) - {}", + af, + ty, + pt, + err + ) + })?; + + let ret = crate::syscalls::fd_renumber_internal(ctx, ret_fd, fd); + if ret != Errno::Success { + bail!( + "journal restore error: failed renumber file descriptor after opening socket (from={}, to={}) - {}", + ret_fd, + fd, + ret + ); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_send.rs b/lib/wasix/src/journal/effector/syscalls/sock_send.rs new file mode 100644 index 00000000000..710b9cc9224 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_send.rs @@ -0,0 +1,66 @@ +use wasmer_wasix_types::wasi::SiFlags; + +use crate::syscalls::sock_send_internal; + +use super::*; + +impl JournalEffector { + pub fn save_sock_send( + ctx: &FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + sent: usize, + iovs: WasmPtr<__wasi_ciovec_t, M>, + iovs_len: M::Offset, + si_flags: SiFlags, + ) -> anyhow::Result<()> { + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let iovs_arr = iovs.slice(&memory, iovs_len)?; + + let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; + let mut remaining: M::Offset = TryFrom::::try_from(sent).unwrap_or_default(); + for iovs in iovs_arr.iter() { + let sub = iovs.buf_len.min(remaining); + if sub == M::ZERO { + continue; + } + remaining -= sub; + + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, sub) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + ctx.data() + .active_journal()? + .write(JournalEntry::SocketSendV1 { + fd, + data: Cow::Borrowed(buf.as_ref()), + is_64bit: M::is_64bit(), + flags: si_flags, + }) + .map_err(map_snapshot_err)?; + } + Ok(()) + } + + pub fn apply_sock_send( + ctx: &FunctionEnvMut<'_, WasiEnv>, + sock: Fd, + si_data: Cow<'_, [u8]>, + si_flags: SiFlags, + ) -> anyhow::Result<()> { + let data_len = si_data.len(); + sock_send_internal(ctx, sock, FdWriteSource::<'_, M>::Buffer(si_data), si_flags)?.map_err( + |err| { + anyhow::format_err!( + "journal restore error: failed to send on socket (fd={}, data.len={}) - {}", + sock, + data_len, + err + ) + }, + )?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_send_file.rs b/lib/wasix/src/journal/effector/syscalls/sock_send_file.rs new file mode 100644 index 00000000000..abf21efb9ee --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_send_file.rs @@ -0,0 +1,43 @@ +use crate::syscalls::sock_send_file_internal; + +use super::*; + +impl JournalEffector { + pub fn save_sock_send_file( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + socket_fd: Fd, + file_fd: Fd, + offset: Filesize, + count: Filesize, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + }, + ) + } + + pub fn apply_sock_send_file( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + socket_fd: Fd, + file_fd: Fd, + offset: Filesize, + count: Filesize, + ) -> anyhow::Result<()> { + sock_send_file_internal(ctx, socket_fd, file_fd, offset, count)?.map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to send_file on socket (sock={}, in_fd={}, offset={}, count={}) - {}", + socket_fd, + file_fd, + offset, + count, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_send_to.rs b/lib/wasix/src/journal/effector/syscalls/sock_send_to.rs new file mode 100644 index 00000000000..f1e7608e5d6 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_send_to.rs @@ -0,0 +1,75 @@ +use std::net::SocketAddr; +use wasmer_wasix_types::wasi::SiFlags; + +use crate::syscalls::sock_send_to_internal; + +use super::*; + +impl JournalEffector { + pub fn save_sock_send_to( + ctx: &FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + sent: usize, + iovs: WasmPtr<__wasi_ciovec_t, M>, + iovs_len: M::Offset, + addr: SocketAddr, + si_flags: SiFlags, + ) -> anyhow::Result<()> { + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let iovs_arr = iovs.slice(&memory, iovs_len)?; + + let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; + let mut remaining: M::Offset = TryFrom::::try_from(sent).unwrap_or_default(); + for iovs in iovs_arr.iter() { + let sub = iovs.buf_len.min(remaining); + if sub == M::ZERO { + continue; + } + remaining -= sub; + + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, sub) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + ctx.data() + .active_journal()? + .write(JournalEntry::SocketSendToV1 { + fd, + data: Cow::Borrowed(buf.as_ref()), + addr, + is_64bit: M::is_64bit(), + flags: si_flags, + }) + .map_err(map_snapshot_err)?; + } + Ok(()) + } + + pub fn apply_sock_send_to( + ctx: &FunctionEnvMut<'_, WasiEnv>, + sock: Fd, + si_data: Cow<'_, [u8]>, + si_flags: SiFlags, + addr: SocketAddr, + ) -> anyhow::Result<()> { + let data_len = si_data.len(); + sock_send_to_internal::( + ctx, + sock, + FdWriteSource::<'_, M>::Buffer(si_data), + si_flags, + addr, + )? + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to send_to on socket (fd={}, data.len={}) - {}", + sock, + data_len, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_set_opt_flag.rs b/lib/wasix/src/journal/effector/syscalls/sock_set_opt_flag.rs new file mode 100644 index 00000000000..ead9f509332 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_set_opt_flag.rs @@ -0,0 +1,35 @@ +use wasmer_wasix_types::wasi::Sockoption; + +use super::*; + +impl JournalEffector { + pub fn save_sock_set_opt_flag( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + opt: Sockoption, + flag: bool, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::SocketSetOptFlagV1 { fd, opt, flag }) + } + + pub fn apply_sock_set_opt_flag( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + opt: Sockoption, + flag: bool, + ) -> anyhow::Result<()> { + crate::syscalls::sock_set_opt_flag_internal(ctx, fd, opt, flag) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set socket option (fd={}, opt={:?}, flag={}) - {}", + fd, + opt, + flag, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_set_opt_size.rs b/lib/wasix/src/journal/effector/syscalls/sock_set_opt_size.rs new file mode 100644 index 00000000000..867f3512250 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_set_opt_size.rs @@ -0,0 +1,35 @@ +use wasmer_wasix_types::wasi::Sockoption; + +use super::*; + +impl JournalEffector { + pub fn save_sock_set_opt_size( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + opt: Sockoption, + size: Filesize, + ) -> anyhow::Result<()> { + Self::save_event(ctx, JournalEntry::SocketSetOptSizeV1 { fd, opt, size }) + } + + pub fn apply_sock_set_opt_size( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + opt: Sockoption, + size: Filesize, + ) -> anyhow::Result<()> { + crate::syscalls::sock_set_opt_size_internal(ctx, fd, opt, size) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set socket option (fd={}, opt={:?}, size={}) - {}", + fd, + opt, + size, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_set_opt_time.rs b/lib/wasix/src/journal/effector/syscalls/sock_set_opt_time.rs new file mode 100644 index 00000000000..0dbdf7ab339 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_set_opt_time.rs @@ -0,0 +1,44 @@ +use std::time::Duration; + +use crate::net::socket::TimeType; + +use super::*; + +impl JournalEffector { + pub fn save_sock_set_opt_time( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + ty: TimeType, + time: Option, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketSetOptTimeV1 { + fd, + ty: ty.into(), + time, + }, + ) + } + + pub fn apply_sock_set_opt_time( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + ty: TimeType, + time: Option, + ) -> anyhow::Result<()> { + crate::syscalls::sock_set_opt_time_internal(ctx, fd, ty, time) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to set socket option (fd={}, opt={:?}, time={:?}) - {}", + fd, + ty, + time, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/sock_shutdown.rs b/lib/wasix/src/journal/effector/syscalls/sock_shutdown.rs new file mode 100644 index 00000000000..61497214b70 --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/sock_shutdown.rs @@ -0,0 +1,38 @@ +use std::net::Shutdown; + +use super::*; + +impl JournalEffector { + pub fn save_sock_shutdown( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + shutdown: Shutdown, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SocketShutdownV1 { + fd, + how: shutdown.into(), + }, + ) + } + + pub fn apply_sock_shutdown( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: Fd, + shutdown: Shutdown, + ) -> anyhow::Result<()> { + crate::syscalls::sock_shutdown_internal(ctx, fd, shutdown) + .map(|r| r.map_err(|err| err.to_string())) + .unwrap_or_else(|err| Err(err.to_string())) + .map_err(|err| { + anyhow::format_err!( + "journal restore error: failed to shutdown socket (fd={}, how={:?}) - {}", + fd, + shutdown, + err + ) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/syscalls/tty_set.rs b/lib/wasix/src/journal/effector/syscalls/tty_set.rs new file mode 100644 index 00000000000..536c5a3a2fa --- /dev/null +++ b/lib/wasix/src/journal/effector/syscalls/tty_set.rs @@ -0,0 +1,38 @@ +use crate::WasiTtyState; + +use super::*; + +impl JournalEffector { + pub fn save_tty_set( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + state: WasiTtyState, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::TtySetV1 { + tty: wasmer_wasix_types::wasi::Tty { + cols: state.cols, + rows: state.rows, + width: state.width, + height: state.height, + stdin_tty: state.stdin_tty, + stdout_tty: state.stdout_tty, + stderr_tty: state.stderr_tty, + echo: state.echo, + line_buffered: state.line_buffered, + }, + line_feeds: state.line_feeds, + }, + ) + } + + pub fn apply_tty_set( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + state: WasiTtyState, + ) -> anyhow::Result<()> { + crate::syscalls::tty_set_internal(ctx, state).map_err(|err| { + anyhow::format_err!("journal restore error: failed to set tty - {}", err) + })?; + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/thread_exit.rs b/lib/wasix/src/journal/effector/thread_exit.rs new file mode 100644 index 00000000000..eae7f8813c8 --- /dev/null +++ b/lib/wasix/src/journal/effector/thread_exit.rs @@ -0,0 +1,34 @@ +use wasmer_wasix_types::wasi::Signal; + +use super::*; + +impl JournalEffector { + pub fn save_thread_exit( + env: &WasiEnv, + id: WasiThreadId, + exit_code: Option, + ) -> anyhow::Result<()> { + env.active_journal()? + .write(JournalEntry::CloseThreadV1 { + id: id.raw(), + exit_code, + }) + .map_err(map_snapshot_err)?; + Ok(()) + } + + pub fn apply_thread_exit( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + tid: WasiThreadId, + exit_code: Option, + ) -> anyhow::Result<()> { + let env = ctx.data(); + if let Some(thread) = env.process.get_thread(&tid) { + if let Some(code) = exit_code { + thread.set_status_finished(Ok(code)); + } + thread.signal(Signal::Sigkill); + } + Ok(()) + } +} diff --git a/lib/wasix/src/journal/effector/thread_state.rs b/lib/wasix/src/journal/effector/thread_state.rs new file mode 100644 index 00000000000..93b4d3cf9fe --- /dev/null +++ b/lib/wasix/src/journal/effector/thread_state.rs @@ -0,0 +1,22 @@ +use super::*; + +impl JournalEffector { + pub fn save_thread_state( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + id: WasiThreadId, + memory_stack: Bytes, + rewind_stack: Bytes, + store_data: Bytes, + ) -> anyhow::Result<()> { + Self::save_event( + ctx, + JournalEntry::SetThreadV1 { + id: id.raw(), + call_stack: Cow::Owned(rewind_stack.into()), + memory_stack: Cow::Owned(memory_stack.into()), + store_data: Cow::Owned(store_data.into()), + is_64bit: M::is_64bit(), + }, + ) + } +} diff --git a/lib/wasix/src/journal/effector/unimplemented.rs b/lib/wasix/src/journal/effector/unimplemented.rs new file mode 100644 index 00000000000..6e82767e3bb --- /dev/null +++ b/lib/wasix/src/journal/effector/unimplemented.rs @@ -0,0 +1,4 @@ +#[derive(Debug, Clone)] +pub struct JournalEffector {} + +impl JournalEffector {} diff --git a/lib/wasix/src/journal/mod.rs b/lib/wasix/src/journal/mod.rs new file mode 100644 index 00000000000..34787b065f2 --- /dev/null +++ b/lib/wasix/src/journal/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "journal")] +mod effector; +#[cfg(not(feature = "journal"))] +#[path = "effector/unimplemented.rs"] +mod effector; + +pub use effector::*; +pub use wasmer_journal::*; diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs index b273a4f5e89..e039ea7b0bd 100644 --- a/lib/wasix/src/lib.rs +++ b/lib/wasix/src/lib.rs @@ -47,6 +47,7 @@ pub mod net; pub mod capabilities; pub mod fs; pub mod http; +pub mod journal; mod rewind; pub mod runners; pub mod runtime; @@ -57,6 +58,8 @@ mod utils; /// WAI based bindings. mod bindings; +use std::sync::Arc; + #[allow(unused_imports)] use bytes::{Bytes, BytesMut}; use os::task::control_plane::ControlPlaneError; @@ -103,7 +106,7 @@ pub use crate::{ utils::is_wasix_module, utils::{ get_wasi_version, get_wasi_versions, is_wasi_module, - store::{capture_snapshot, restore_snapshot, InstanceSnapshot}, + store::{capture_instance_snapshot, restore_instance_snapshot, InstanceSnapshot}, WasiVersion, }, }; @@ -120,6 +123,8 @@ pub enum WasiError { UnknownWasiVersion, } +pub type WasiResult = Result, WasiError>; + #[deny(unused, dead_code)] #[derive(Error, Debug)] pub enum SpawnError { @@ -203,6 +208,8 @@ pub enum WasiRuntimeError { Runtime(#[from] RuntimeError), #[error("Memory access error")] Thread(#[from] WasiThreadError), + #[error("{0}")] + Anyhow(#[from] Arc), } impl WasiRuntimeError { diff --git a/lib/wasix/src/net/socket.rs b/lib/wasix/src/net/socket.rs index ed7fb32819e..958a01578b1 100644 --- a/lib/wasix/src/net/socket.rs +++ b/lib/wasix/src/net/socket.rs @@ -72,6 +72,9 @@ pub enum InodeSocketKind { socket: Box, peer: Option, }, + RemoteTcpStream { + peer_addr: SocketAddr, + }, } pub enum WasiSocketOption { @@ -148,6 +151,7 @@ pub enum WasiSocketStatus { } #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum TimeType { ReadTimeout, WriteTimeout, @@ -157,6 +161,33 @@ pub enum TimeType { Linger, } +impl From for wasmer_journal::SocketOptTimeType { + fn from(value: TimeType) -> Self { + match value { + TimeType::ReadTimeout => Self::ReadTimeout, + TimeType::WriteTimeout => Self::WriteTimeout, + TimeType::AcceptTimeout => Self::AcceptTimeout, + TimeType::ConnectTimeout => Self::ConnectTimeout, + TimeType::BindTimeout => Self::BindTimeout, + TimeType::Linger => Self::Linger, + } + } +} + +impl From for TimeType { + fn from(value: wasmer_journal::SocketOptTimeType) -> Self { + use wasmer_journal::SocketOptTimeType; + match value { + SocketOptTimeType::ReadTimeout => TimeType::ReadTimeout, + SocketOptTimeType::WriteTimeout => TimeType::WriteTimeout, + SocketOptTimeType::AcceptTimeout => TimeType::AcceptTimeout, + SocketOptTimeType::ConnectTimeout => TimeType::ConnectTimeout, + SocketOptTimeType::BindTimeout => TimeType::BindTimeout, + SocketOptTimeType::Linger => TimeType::Linger, + } + } +} + #[derive(Debug)] //#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub(crate) struct InodeSocketProtected { @@ -176,13 +207,13 @@ pub struct InodeSocket { } impl InodeSocket { - pub fn new(kind: InodeSocketKind) -> virtual_net::Result { + pub fn new(kind: InodeSocketKind) -> Self { let protected = InodeSocketProtected { kind }; - Ok(Self { + Self { inner: Arc::new(InodeSocketInner { protected: RwLock::new(protected), }), - }) + } } pub fn poll_read_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -266,7 +297,7 @@ impl InodeSocket { tokio::select! { socket = socket => { let socket = socket.map_err(net_error_into_wasi_err)?; - Ok(Some(InodeSocket::new(InodeSocketKind::UdpSocket { socket, peer: None }).map_err(net_error_into_wasi_err)?)) + Ok(Some(InodeSocket::new(InodeSocketKind::UdpSocket { socket, peer: None }))) }, _ = tasks.sleep_now(timeout) => Err(Errno::Timedout) } @@ -326,7 +357,7 @@ impl InodeSocket { Ok(Some(InodeSocket::new(InodeSocketKind::TcpListener { socket, accept_timeout: Some(timeout), - }).map_err(net_error_into_wasi_err)?)) + }))) }, _ = tasks.sleep_now(timeout) => Err(Errno::Timedout) } @@ -410,6 +441,7 @@ impl InodeSocket { InodeSocketKind::UdpSocket { .. } => {} InodeSocketKind::Raw(_) => {} InodeSocketKind::PreSocket { .. } => return Err(Errno::Notconn), + InodeSocketKind::RemoteTcpStream { .. } => {} }; Ok(()) } @@ -502,8 +534,7 @@ impl InodeSocket { socket, write_timeout: new_write_timeout, read_timeout: new_read_timeout, - }) - .map_err(net_error_into_wasi_err)?; + }); Ok(Some(socket)) } @@ -989,6 +1020,9 @@ impl InodeSocket { InodeSocketKind::PreSocket { .. } => { return Poll::Ready(Err(Errno::Notconn)) } + InodeSocketKind::RemoteTcpStream { .. } => { + return Poll::Ready(Ok(self.data.len())) + } _ => return Poll::Ready(Err(Errno::Notsup)), }; return match res { @@ -1066,6 +1100,9 @@ impl InodeSocket { InodeSocketKind::PreSocket { .. } => { return Poll::Ready(Err(Errno::Notconn)) } + InodeSocketKind::RemoteTcpStream { .. } => { + return Poll::Ready(Ok(self.data.len())) + } _ => return Poll::Ready(Err(Errno::Notsup)), }; return match res { @@ -1305,6 +1342,7 @@ impl InodeSocketProtected { InodeSocketKind::PreSocket { handler, .. } => { handler.take(); } + InodeSocketKind::RemoteTcpStream { .. } => {} } } @@ -1316,6 +1354,7 @@ impl InodeSocketProtected { InodeSocketKind::Raw(socket) => socket.poll_read_ready(cx), InodeSocketKind::Icmp(socket) => socket.poll_read_ready(cx), InodeSocketKind::PreSocket { .. } => Poll::Pending, + InodeSocketKind::RemoteTcpStream { .. } => Poll::Pending, } .map_err(net_error_into_io_err) } @@ -1328,6 +1367,7 @@ impl InodeSocketProtected { InodeSocketKind::Raw(socket) => socket.poll_write_ready(cx), InodeSocketKind::Icmp(socket) => socket.poll_write_ready(cx), InodeSocketKind::PreSocket { .. } => Poll::Pending, + InodeSocketKind::RemoteTcpStream { .. } => Poll::Pending, } .map_err(net_error_into_io_err) } @@ -1346,6 +1386,7 @@ impl InodeSocketProtected { h.replace(handler); Ok(()) } + InodeSocketKind::RemoteTcpStream { .. } => Ok(()), } } } diff --git a/lib/wasix/src/os/task/control_plane.rs b/lib/wasix/src/os/task/control_plane.rs index be064ac4c80..96d5575e62e 100644 --- a/lib/wasix/src/os/task/control_plane.rs +++ b/lib/wasix/src/os/task/control_plane.rs @@ -6,7 +6,7 @@ use std::{ }, }; -use crate::{WasiProcess, WasiProcessId}; +use crate::{runtime::module_cache::ModuleHash, WasiProcess, WasiProcessId}; #[derive(Debug, Clone)] pub struct WasiControlPlane { @@ -109,7 +109,7 @@ impl WasiControlPlane { /// Register a new task. /// // Currently just increments the task counter. - pub(super) fn register_task(&self) -> Result { + pub(crate) fn register_task(&self) -> Result { let count = self.state.task_count.fetch_add(1, Ordering::SeqCst); if let Some(max) = self.state.config.max_task_count { if count > max { @@ -123,7 +123,7 @@ impl WasiControlPlane { /// Creates a new process // FIXME: De-register terminated processes! // Currently they just accumulate. - pub fn new_process(&self) -> Result { + pub fn new_process(&self, module_hash: ModuleHash) -> Result { if let Some(max) = self.state.config.max_task_count { if self.active_task_count() >= max { // NOTE: task count is not incremented here, only when new threads are spawned. @@ -133,7 +133,7 @@ impl WasiControlPlane { } // Create the process first to do all the allocations before locking. - let mut proc = WasiProcess::new(WasiProcessId::from(0), self.handle()); + let mut proc = WasiProcess::new(WasiProcessId::from(0), module_hash, self.handle()); let mut mutable = self.state.mutable.write().unwrap(); @@ -203,6 +203,8 @@ pub enum ControlPlaneError { #[cfg(test)] mod tests { + use crate::os::task::thread::WasiMemoryLayout; + use super::*; /// Simple test to ensure task limits are respected. @@ -213,12 +215,12 @@ mod tests { enable_asynchronous_threading: false, }); - let p1 = p.new_process().unwrap(); - let _t1 = p1.new_thread().unwrap(); - let _t2 = p1.new_thread().unwrap(); + let p1 = p.new_process(ModuleHash::random()).unwrap(); + let _t1 = p1.new_thread(WasiMemoryLayout::default()).unwrap(); + let _t2 = p1.new_thread(WasiMemoryLayout::default()).unwrap(); assert_eq!( - p.new_process().unwrap_err(), + p.new_process(ModuleHash::random()).unwrap_err(), ControlPlaneError::TaskLimitReached { max: 2 } ); } @@ -231,17 +233,17 @@ mod tests { enable_asynchronous_threading: false, }); - let p1 = p.new_process().unwrap(); + let p1 = p.new_process(ModuleHash::random()).unwrap(); for _ in 0..10 { - let _thread = p1.new_thread().unwrap(); + let _thread = p1.new_thread(WasiMemoryLayout::default()).unwrap(); } - let _t1 = p1.new_thread().unwrap(); - let _t2 = p1.new_thread().unwrap(); + let _t1 = p1.new_thread(WasiMemoryLayout::default()).unwrap(); + let _t2 = p1.new_thread(WasiMemoryLayout::default()).unwrap(); assert_eq!( - p.new_process().unwrap_err(), + p.new_process(ModuleHash::random()).unwrap_err(), ControlPlaneError::TaskLimitReached { max: 2 } ); } diff --git a/lib/wasix/src/os/task/process.rs b/lib/wasix/src/os/task/process.rs index d26b95561aa..d4501248f0e 100644 --- a/lib/wasix/src/os/task/process.rs +++ b/lib/wasix/src/os/task/process.rs @@ -1,15 +1,20 @@ -use crate::WasiRuntimeError; +#[cfg(feature = "journal")] +use crate::{journal::JournalEffector, unwind, WasiResult}; +use crate::{ + journal::SnapshotTrigger, runtime::module_cache::ModuleHash, WasiEnv, WasiRuntimeError, +}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, convert::TryInto, sync::{ atomic::{AtomicU32, Ordering}, - Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak, + Arc, Condvar, Mutex, MutexGuard, RwLock, Weak, }, time::Duration, }; use tracing::trace; +use wasmer::FunctionEnvMut; use wasmer_wasix_types::{ types::Signal, wasi::{Errno, ExitCode, Snapshot0Clockid}, @@ -24,6 +29,7 @@ use super::{ control_plane::{ControlPlaneError, WasiControlPlaneHandle}, signal::{SignalDeliveryError, SignalHandlerAbi}, task_join_handle::OwnedTaskStatus, + thread::WasiMemoryLayout, }; /// Represents the ID of a sub-process @@ -72,16 +78,21 @@ impl std::fmt::Debug for WasiProcessId { } } +pub type LockableWasiProcessInner = Arc<(Mutex, Condvar)>; + /// Represents a process running within the compute state -// TODO: fields should be private and only accessed via methods. +/// TODO: fields should be private and only accessed via methods. #[derive(Debug, Clone)] pub struct WasiProcess { /// Unique ID of this process pub(crate) pid: WasiProcessId, + /// Hash of the module that this process is using + pub(crate) module_hash: ModuleHash, /// List of all the children spawned from this thread pub(crate) parent: Option>>, - /// The inner protected region of the process - pub(crate) inner: Arc>, + /// The inner protected region of the process with a conditional + /// variable that is used for coordination such as checksums. + pub(crate) inner: LockableWasiProcessInner, /// Reference back to the compute engine // TODO: remove this reference, access should happen via separate state instead // (we don't want cyclical references) @@ -92,6 +103,20 @@ pub struct WasiProcess { pub(crate) waiting: Arc, } +/// Represents a freeze of all threads to perform some action +/// on the total state-machine. This is normally done for +/// things like snapshots which require the memory to remain +/// stable while it performs a diff. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum WasiProcessCheckpoint { + /// No checkpoint will take place and the process + /// should just execute as per normal + Execute, + /// The process needs to take a snapshot of the + /// memory and state-machine + Snapshot { trigger: SnapshotTrigger }, +} + // TODO: fields should be private and only accessed via methods. #[derive(Debug)] pub struct WasiProcessInner { @@ -105,6 +130,135 @@ pub struct WasiProcessInner { pub signal_intervals: HashMap, /// List of all the children spawned from this thread pub children: Vec, + /// Represents a checkpoint which blocks all the threads + /// and then executes some maintenance action + pub checkpoint: WasiProcessCheckpoint, +} + +pub enum MaybeCheckpointResult<'a> { + NotThisTime(FunctionEnvMut<'a, WasiEnv>), + Unwinding, +} + +impl WasiProcessInner { + /// Checkpoints the process which will cause all other threads to + /// pause and for the thread and memory state to be saved + #[cfg(feature = "journal")] + pub fn checkpoint( + inner: LockableWasiProcessInner, + ctx: FunctionEnvMut<'_, WasiEnv>, + for_what: WasiProcessCheckpoint, + ) -> WasiResult> { + // Set the checkpoint flag and then enter the normal processing loop + { + let mut inner = inner.0.lock().unwrap(); + inner.checkpoint = for_what; + } + + Self::maybe_checkpoint::(inner, ctx) + } + + /// If a checkpoint has been started this will block the current process + /// until the checkpoint operation has completed + #[cfg(feature = "journal")] + pub fn maybe_checkpoint( + inner: LockableWasiProcessInner, + ctx: FunctionEnvMut<'_, WasiEnv>, + ) -> WasiResult> { + // Enter the lock which will determine if we are in a checkpoint or not + + use bytes::Bytes; + use wasmer::AsStoreMut; + use wasmer_types::OnCalledAction; + + use crate::{rewind_ext, WasiError}; + let guard = inner.0.lock().unwrap(); + if guard.checkpoint == WasiProcessCheckpoint::Execute { + // No checkpoint so just carry on + return Ok(Ok(MaybeCheckpointResult::NotThisTime(ctx))); + } + trace!("checkpoint capture"); + drop(guard); + + // Perform the unwind action + unwind::(ctx, move |mut ctx, memory_stack, rewind_stack| { + // Grab all the globals and serialize them + let store_data = + crate::utils::store::capture_instance_snapshot(&mut ctx.as_store_mut()) + .serialize() + .unwrap(); + let memory_stack = memory_stack.freeze(); + let rewind_stack = rewind_stack.freeze(); + let store_data = Bytes::from(store_data); + + tracing::debug!( + "stack snapshot unwind (memory_stack={}, rewind_stack={}, store_data={})", + memory_stack.len(), + rewind_stack.len(), + store_data.len(), + ); + + // Write our thread state to the snapshot + let tid = ctx.data().thread.tid(); + if let Err(err) = JournalEffector::save_thread_state::( + &mut ctx, + tid, + memory_stack.clone(), + rewind_stack.clone(), + store_data.clone(), + ) { + return wasmer_types::OnCalledAction::Trap(err.into()); + } + + let mut guard = inner.0.lock().unwrap(); + + // Wait for the checkpoint to finish (or if we are the last thread + // to freeze then we have to execute the checksum operation) + loop { + if let WasiProcessCheckpoint::Snapshot { trigger } = guard.checkpoint { + ctx.data().thread.set_check_pointing(true); + + // Now if we are the last thread we also write the memory + let is_last_thread = guard.threads.values().all(WasiThread::is_check_pointing); + if is_last_thread { + if let Err(err) = + JournalEffector::save_memory_and_snapshot(&mut ctx, &mut guard, trigger) + { + inner.1.notify_all(); + return wasmer_types::OnCalledAction::Trap(err.into()); + } + + // Clear the checkpointing flag and notify everyone to wake up + ctx.data().thread.set_check_pointing(false); + guard.checkpoint = WasiProcessCheckpoint::Execute; + trace!("checkpoint complete"); + inner.1.notify_all(); + } else { + guard = inner.1.wait(guard).unwrap(); + } + continue; + } + + ctx.data().thread.set_check_pointing(false); + trace!("checkpoint finished"); + + // Rewind the stack and carry on + return match rewind_ext::(&mut ctx, memory_stack, rewind_stack, store_data, None) + { + Errno::Success => OnCalledAction::InvokeAgain, + err => { + tracing::warn!( + "snapshot resumption failed - could not rewind the stack - errno={}", + err + ); + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) + } + }; + } + })?; + + Ok(Ok(MaybeCheckpointResult::Unwinding)) + } } // TODO: why do we need this, how is it used? @@ -128,18 +282,23 @@ impl Drop for WasiProcessWait { } impl WasiProcess { - pub fn new(pid: WasiProcessId, plane: WasiControlPlaneHandle) -> Self { + pub fn new(pid: WasiProcessId, module_hash: ModuleHash, plane: WasiControlPlaneHandle) -> Self { WasiProcess { pid, + module_hash, parent: None, compute: plane, - inner: Arc::new(RwLock::new(WasiProcessInner { - pid, - threads: Default::default(), - thread_count: Default::default(), - signal_intervals: Default::default(), - children: Default::default(), - })), + inner: Arc::new(( + Mutex::new(WasiProcessInner { + pid, + threads: Default::default(), + thread_count: Default::default(), + signal_intervals: Default::default(), + children: Default::default(), + checkpoint: WasiProcessCheckpoint::Execute, + }), + Condvar::new(), + )), finished: Arc::new(OwnedTaskStatus::default()), waiting: Arc::new(AtomicU32::new(0)), } @@ -164,26 +323,23 @@ impl WasiProcess { .unwrap_or(WasiProcessId(0)) } - /// Gains write access to the process internals - // TODO: Make this private, all inner access should be exposed with methods. - pub fn write(&self) -> RwLockWriteGuard { - self.inner.write().unwrap() - } - - /// Gains read access to the process internals + /// Gains access to the process internals // TODO: Make this private, all inner access should be exposed with methods. - pub fn read(&self) -> RwLockReadGuard { - self.inner.read().unwrap() + pub fn lock(&self) -> MutexGuard<'_, WasiProcessInner> { + self.inner.0.lock().unwrap() } /// Creates a a thread and returns it - pub fn new_thread(&self) -> Result { + pub fn new_thread( + &self, + layout: WasiMemoryLayout, + ) -> Result { let control_plane = self.compute.must_upgrade(); let task_count_guard = control_plane.register_task()?; // Determine if its the main thread or not let is_main = { - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); inner.thread_count == 0 }; @@ -197,7 +353,7 @@ impl WasiProcess { }; // The wait finished should be the process version if its the main thread - let mut inner = self.inner.write().unwrap(); + let mut inner = self.inner.0.lock().unwrap(); let finished = if is_main { self.finished.clone() } else { @@ -205,7 +361,7 @@ impl WasiProcess { }; // Insert the thread into the pool - let ctrl = WasiThread::new(self.pid(), tid, is_main, finished, task_count_guard); + let ctrl = WasiThread::new(self.pid(), tid, is_main, finished, task_count_guard, layout); inner.threads.insert(tid, ctrl.clone()); inner.thread_count += 1; @@ -214,7 +370,7 @@ impl WasiProcess { /// Gets a reference to a particular thread pub fn get_thread(&self, tid: &WasiThreadId) -> Option { - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); inner.threads.get(tid).cloned() } @@ -230,7 +386,7 @@ impl WasiProcess { let pid = self.pid(); tracing::trace!(%pid, %tid, "signal-thread({:?})", signal); - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); if let Some(thread) = inner.threads.get(&tid) { thread.signal(signal); } else { @@ -249,7 +405,7 @@ impl WasiProcess { tracing::trace!(%pid, "signal-process({:?})", signal); { - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); if self.waiting.load(Ordering::Acquire) > 0 { let mut triggered = false; for child in inner.children.iter() { @@ -261,7 +417,7 @@ impl WasiProcess { } } } - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); for thread in inner.threads.values() { thread.signal(signal); } @@ -269,7 +425,7 @@ impl WasiProcess { /// Signals one of the threads every interval pub fn signal_interval(&self, signal: Signal, interval: Option, repeat: bool) { - let mut inner = self.inner.write().unwrap(); + let mut inner = self.inner.0.lock().unwrap(); let interval = match interval { None => { @@ -293,7 +449,7 @@ impl WasiProcess { /// Returns the number of active threads for this process pub fn active_threads(&self) -> u32 { - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); inner.thread_count } @@ -312,7 +468,7 @@ impl WasiProcess { pub async fn join_children(&mut self) -> Option>> { let _guard = WasiProcessWait::new(self); let children: Vec<_> = { - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); inner.children.clone() }; if children.is_empty() { @@ -324,7 +480,7 @@ impl WasiProcess { let inner = self.inner.clone(); waits.push(async move { let join = process.join().await; - let mut inner = inner.write().unwrap(); + let mut inner = inner.0.lock().unwrap(); inner.children.retain(|a| a.pid != child.pid); join }) @@ -340,7 +496,7 @@ impl WasiProcess { pub async fn join_any_child(&mut self) -> Result, Errno> { let _guard = WasiProcessWait::new(self); let children: Vec<_> = { - let inner = self.inner.read().unwrap(); + let inner = self.inner.0.lock().unwrap(); inner.children.clone() }; if children.is_empty() { @@ -353,7 +509,7 @@ impl WasiProcess { let inner = self.inner.clone(); waits.push(async move { let join = process.join().await; - let mut inner = inner.write().unwrap(); + let mut inner = inner.0.lock().unwrap(); inner.children.retain(|a| a.pid != child.pid); (child, join) }) @@ -373,7 +529,7 @@ impl WasiProcess { pub fn terminate(&self, exit_code: ExitCode) { // FIXME: this is wrong, threads might still be running! // Need special logic for the main thread. - let guard = self.inner.read().unwrap(); + let guard = self.inner.0.lock().unwrap(); for thread in guard.threads.values() { thread.set_status_finished(Ok(exit_code)) } diff --git a/lib/wasix/src/os/task/task_join_handle.rs b/lib/wasix/src/os/task/task_join_handle.rs index c374305fd96..18d7271bac6 100644 --- a/lib/wasix/src/os/task/task_join_handle.rs +++ b/lib/wasix/src/os/task/task_join_handle.rs @@ -138,6 +138,10 @@ impl OwnedTaskStatus { } } + pub async fn await_termination_anyhow(&self) -> anyhow::Result { + Ok(self.await_termination().await?) + } + pub fn handle(&self) -> TaskJoinHandle { TaskJoinHandle { watch: self.watch_tx.subscribe(), diff --git a/lib/wasix/src/os/task/thread.rs b/lib/wasix/src/os/task/thread.rs index 894d3ae74a9..94ee92a4ec4 100644 --- a/lib/wasix/src/os/task/thread.rs +++ b/lib/wasix/src/os/task/thread.rs @@ -1,7 +1,10 @@ +use serde::{Deserialize, Serialize}; +#[cfg(feature = "journal")] +use std::sync::atomic::{AtomicBool, Ordering}; use std::{ collections::HashMap, ops::{Deref, DerefMut}, - sync::{Arc, Mutex, RwLock, Weak}, + sync::{Arc, Condvar, Mutex, Weak}, task::Waker, }; @@ -14,6 +17,7 @@ use wasmer_wasix_types::{ use crate::{ os::task::process::{WasiProcessId, WasiProcessInner}, + syscalls::HandleRewindType, WasiRuntimeError, }; @@ -23,7 +27,7 @@ use super::{ }; /// Represents the ID of a WASI thread -#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct WasiThreadId(u32); impl WasiThreadId { @@ -95,6 +99,7 @@ pub struct ThreadStack { #[derive(Clone, Debug)] pub struct WasiThread { state: Arc, + layout: WasiMemoryLayout, // This is used for stack rewinds rewind: Option, @@ -110,6 +115,44 @@ impl WasiThread { pub(crate) fn take_rewind(&mut self) -> Option { self.rewind.take() } + + pub(crate) fn has_rewind_of_type(&self, _type: HandleRewindType) -> bool { + match _type { + HandleRewindType::ResultDriven => match &self.rewind { + Some(rewind) => rewind.rewind_result.is_some(), + None => false, + }, + HandleRewindType::Resultless => match &self.rewind { + Some(rewind) => rewind.rewind_result.is_none(), + None => false, + }, + } + } + + /// Sets a flag that tells others that this thread is currently + /// check pointing itself + #[cfg(feature = "journal")] + pub(crate) fn set_check_pointing(&self, val: bool) { + self.state.check_pointing.store(val, Ordering::SeqCst); + } + + /// Reads a flag that determines if this thread is currently + /// check pointing itself or not + #[cfg(feature = "journal")] + pub(crate) fn is_check_pointing(&self) -> bool { + self.state.check_pointing.load(Ordering::SeqCst) + } + + /// Gets the memory layout for this thread + #[allow(dead_code)] + pub(crate) fn memory_layout(&self) -> &WasiMemoryLayout { + &self.layout + } + + /// Gets the memory layout for this thread + pub(crate) fn set_memory_layout(&mut self, layout: WasiMemoryLayout) { + self.layout = layout; + } } /// A guard that ensures a thread is marked as terminated when dropped. @@ -159,7 +202,7 @@ pub(crate) struct RewindResult { pub memory_stack: Bytes, /// Generic serialized object passed back to the rewind resumption code /// (uses the bincode serializer) - pub rewind_result: Bytes, + pub rewind_result: Option, } #[derive(Debug)] @@ -170,6 +213,8 @@ struct WasiThreadState { signals: Mutex<(Vec, Vec)>, stack: Mutex, status: Arc, + #[cfg(feature = "journal")] + check_pointing: AtomicBool, // Registers the task termination with the ControlPlane on drop. // Never accessed, since it's a drop guard. @@ -185,6 +230,7 @@ impl WasiThread { is_main: bool, status: Arc, guard: TaskCountGuard, + layout: WasiMemoryLayout, ) -> Self { Self { state: Arc::new(WasiThreadState { @@ -194,8 +240,11 @@ impl WasiThread { status, signals: Mutex::new((Vec::new(), Vec::new())), stack: Mutex::new(ThreadStack::default()), + #[cfg(feature = "journal")] + check_pointing: AtomicBool::new(false), _task_count_guard: guard, }), + layout, rewind: None, } } @@ -445,7 +494,7 @@ impl WasiThread { #[derive(Debug)] pub struct WasiThreadHandleProtected { thread: WasiThread, - inner: Weak>, + inner: Weak<(Mutex, Condvar)>, } #[derive(Debug, Clone)] @@ -456,7 +505,7 @@ pub struct WasiThreadHandle { impl WasiThreadHandle { pub(crate) fn new( thread: WasiThread, - inner: &Arc>, + inner: &Arc<(Mutex, Condvar)>, ) -> WasiThreadHandle { Self { protected: Arc::new(WasiThreadHandleProtected { @@ -479,7 +528,7 @@ impl Drop for WasiThreadHandleProtected { fn drop(&mut self) { let id = self.thread.tid(); if let Some(inner) = Weak::upgrade(&self.inner) { - let mut inner = inner.write().unwrap(); + let mut inner = inner.0.lock().unwrap(); if let Some(ctrl) = inner.threads.remove(&id) { ctrl.set_status_finished(Ok(Errno::Success.into())); } @@ -496,7 +545,7 @@ impl std::ops::Deref for WasiThreadHandle { } } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone)] pub enum WasiThreadError { #[error("Multithreading is not supported")] Unsupported, @@ -510,7 +559,7 @@ pub enum WasiThreadError { // Note: Boxed so we can keep the error size down InstanceCreateFailed(Box), #[error("Initialization function failed - {0}")] - InitFailed(anyhow::Error), + InitFailed(Arc), /// This will happen if WASM is running in a thread has not been created by the spawn_wasm call #[error("WASM context is invalid")] InvalidWasmContext, diff --git a/lib/wasix/src/rewind.rs b/lib/wasix/src/rewind.rs index 2ad1fc2f4b4..92f72a6edc3 100644 --- a/lib/wasix/src/rewind.rs +++ b/lib/wasix/src/rewind.rs @@ -28,6 +28,8 @@ pub struct RewindState { pub is_64bit: bool, } +pub type RewindStateOption = Option<(RewindState, Option)>; + /// Represents the work that will be done when a thread goes to deep sleep and /// includes the things needed to restore it again pub struct DeepSleepWork { diff --git a/lib/wasix/src/runners/dcgi/callbacks.rs b/lib/wasix/src/runners/dcgi/callbacks.rs new file mode 100644 index 00000000000..87b430b5c3a --- /dev/null +++ b/lib/wasix/src/runners/dcgi/callbacks.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; + +use derivative::Derivative; + +use super::*; +use crate::runners::wcgi::{self, CreateEnvConfig, CreateEnvResult, RecycleEnvConfig}; +use virtual_fs::NullFile; +use wasmer_wasix_types::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}; + +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct DcgiCallbacks { + #[derivative(Debug = "ignore")] + inner: Arc, + factory: DcgiInstanceFactory, +} + +impl DcgiCallbacks { + pub fn new(factory: DcgiInstanceFactory, inner: C) -> Self + where + C: wcgi::Callbacks, + { + Self { + inner: Arc::new(inner), + factory, + } + } +} + +#[async_trait::async_trait] +impl wcgi::Callbacks for DcgiCallbacks { + fn started(&self, abort: AbortHandle) { + self.inner.started(abort) + } + + fn on_stderr(&self, stderr: &[u8]) { + self.inner.on_stderr(stderr) + } + + fn on_stderr_error(&self, error: std::io::Error) { + self.inner.on_stderr_error(error) + } + + async fn recycle_env(&self, conf: RecycleEnvConfig) { + tracing::debug!("recycling DCGI instance"); + + // The stdio have to be reattached on each call as they are + // read to completion (EOF) during nominal flows + conf.env + .state + .fs + .swap_file(__WASI_STDIN_FILENO, Box::::default()) + .ok(); + conf.env + .state + .fs + .swap_file(__WASI_STDOUT_FILENO, Box::::default()) + .ok(); + conf.env + .state + .fs + .swap_file(__WASI_STDERR_FILENO, Box::::default()) + .ok(); + + // Now we make the instance available for reuse + self.factory.release(conf).await; + } + + async fn create_env(&self, mut conf: CreateEnvConfig) -> anyhow::Result { + tracing::debug!("attempting to acquire existing DCGI instance"); + + if let Some(res) = self.factory.acquire(&mut conf).await { + tracing::debug!("found existing DCGI instance"); + return Ok(res); + } + + let mut ret = self.inner.create_env(conf).await; + + if let Ok(ret) = ret.as_mut() { + // We disable the cleanup to prevent the instance so that the + // resources can be reused + ret.env.disable_fs_cleanup = true; + } + + ret + } +} diff --git a/lib/wasix/src/runners/dcgi/factory.rs b/lib/wasix/src/runners/dcgi/factory.rs new file mode 100644 index 00000000000..9e10a74f09b --- /dev/null +++ b/lib/wasix/src/runners/dcgi/factory.rs @@ -0,0 +1,105 @@ +use std::sync::{Arc, Mutex}; + +use derivative::Derivative; +use virtual_fs::Pipe; +use wasmer_wasix_types::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}; + +use crate::{ + runners::wcgi::{CreateEnvConfig, CreateEnvResult, RecycleEnvConfig}, + state::conv_env_vars, + WasiStateCreationError, +}; + +use super::*; + +#[derive(Debug, Default)] +struct State { + /// Once the instance is running it will + instance: Option, +} + +/// This factory will store and reuse instances between invocations thus +/// allowing for the instances to be stateful. +#[derive(Derivative, Clone, Default)] +#[derivative(Debug)] +pub struct DcgiInstanceFactory { + state: Arc>, +} + +impl DcgiInstanceFactory { + pub fn new() -> Self { + Default::default() + } + + pub async fn release(&self, conf: RecycleEnvConfig) { + let mut state = self.state.lock().unwrap(); + state.instance.replace(DcgiInstance { + env: conf.env, + //memory: conf.memory, + //store: conf.store, + }); + } + + pub async fn acquire(&self, conf: &mut CreateEnvConfig) -> Option { + let mut state = self.state.lock().unwrap(); + if let Some(inst) = state.instance.take() { + tracing::debug!("attempting to reinitialize DCGI instance"); + match convert_instance(inst, conf) { + Ok(converted) => return Some(converted), + Err(err) => { + tracing::warn!("failed to reinitialize DCGI instance - {}", err); + } + } + } + + None + } +} + +fn convert_instance( + inst: DcgiInstance, + conf: &mut CreateEnvConfig, +) -> anyhow::Result { + let mut env = inst.env; + + let (req_body_sender, req_body_receiver) = Pipe::channel(); + let (res_body_sender, res_body_receiver) = Pipe::channel(); + let (stderr_sender, stderr_receiver) = Pipe::channel(); + + env.reinit()?; + + // Replace the environment variables as these will change + // depending on the WCGI call + *env.state.envs.lock().unwrap() = conv_env_vars( + conf.env + .iter() + .map(|(k, v)| (k.clone(), v.as_bytes().to_vec())) + .collect(), + ); + + // The stdio have to be reattached on each call as they are + // read to completion (EOF) during nominal flows + env.state + .fs + .swap_file(__WASI_STDIN_FILENO, Box::new(req_body_receiver)) + .map_err(WasiStateCreationError::FileSystemError)?; + + env.state + .fs + .swap_file(__WASI_STDOUT_FILENO, Box::new(res_body_sender)) + .map_err(WasiStateCreationError::FileSystemError)?; + + env.state + .fs + .swap_file(__WASI_STDERR_FILENO, Box::new(stderr_sender)) + .map_err(WasiStateCreationError::FileSystemError)?; + + Ok(CreateEnvResult { + env, + //memory: Some((inst.memory, inst.store)), + memory: None, + body_sender: req_body_sender, + body_receiver: res_body_receiver, + stderr_receiver, + }) +} diff --git a/lib/wasix/src/runners/dcgi/handler.rs b/lib/wasix/src/runners/dcgi/handler.rs new file mode 100644 index 00000000000..627751fd4ec --- /dev/null +++ b/lib/wasix/src/runners/dcgi/handler.rs @@ -0,0 +1,76 @@ +use std::{ops::Deref, pin::Pin, sync::Arc, task::Poll}; + +use anyhow::Error; +use futures::{Future, FutureExt}; +use http::{Request, Response}; +use hyper::{service::Service, Body}; + +use crate::runners::wcgi; + +use super::DcgiInstanceFactory; + +/// The shared object that manages the instantiaion of WASI executables and +/// communicating with them via the CGI protocol. +#[derive(Clone, Debug)] +pub(crate) struct Handler { + state: Arc, + inner: wcgi::Handler, +} + +impl Handler { + pub(crate) fn new(handler: wcgi::Handler) -> Self { + Handler { + state: Arc::new(SharedState { + inner: handler.deref().clone(), + factory: DcgiInstanceFactory::new(), + master_lock: Default::default(), + }), + inner: handler, + } + } + + #[tracing::instrument(level = "debug", skip_all, err)] + pub(crate) async fn handle(&self, req: Request) -> Result, Error> { + // we acquire a guard token so that only one request at a time can be processed + // which effectively means that DCGI is single-threaded. This is a limitation + // of the MVP which should be rectified in future releases. + let guard_token = self.state.master_lock.clone().lock_owned().await; + + // Process the request as a normal WCGI request + self.inner.handle(req, guard_token).await + } +} + +impl Deref for Handler { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.state + } +} + +#[derive(derivative::Derivative, Clone)] +#[derivative(Debug)] +pub(crate) struct SharedState { + pub(crate) inner: Arc, + factory: DcgiInstanceFactory, + master_lock: Arc>, +} + +impl Service> for Handler { + type Response = Response; + type Error = Error; + type Future = Pin, Error>> + Send>>; + + fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll> { + // TODO: We probably should implement some sort of backpressure here... + Poll::Ready(Ok(())) + } + + fn call(&mut self, request: Request) -> Self::Future { + // Note: all fields are reference-counted so cloning is pretty cheap + let handler = self.clone(); + let fut = async move { handler.handle(request).await }; + fut.boxed() + } +} diff --git a/lib/wasix/src/runners/dcgi/instance.rs b/lib/wasix/src/runners/dcgi/instance.rs new file mode 100644 index 00000000000..df04de1a593 --- /dev/null +++ b/lib/wasix/src/runners/dcgi/instance.rs @@ -0,0 +1,10 @@ +//use wasmer::{Memory, Store}; + +use crate::WasiEnv; + +#[derive(Debug)] +pub(crate) struct DcgiInstance { + pub env: WasiEnv, + //pub memory: Memory, + //pub store: Store, +} diff --git a/lib/wasix/src/runners/dcgi/mod.rs b/lib/wasix/src/runners/dcgi/mod.rs new file mode 100644 index 00000000000..f9b46080d8c --- /dev/null +++ b/lib/wasix/src/runners/dcgi/mod.rs @@ -0,0 +1,11 @@ +mod callbacks; +mod factory; +mod handler; +mod instance; +mod runner; + +pub use self::runner::{Config, DcgiRunner}; +pub use callbacks::DcgiCallbacks; +pub use factory::DcgiInstanceFactory; +pub use futures::future::AbortHandle; +pub(crate) use instance::DcgiInstance; diff --git a/lib/wasix/src/runners/dcgi/runner.rs b/lib/wasix/src/runners/dcgi/runner.rs new file mode 100644 index 00000000000..5731728d9fb --- /dev/null +++ b/lib/wasix/src/runners/dcgi/runner.rs @@ -0,0 +1,233 @@ +use std::{net::SocketAddr, sync::Arc}; + +use anyhow::Error; +use wasmer_journal::FilteredJournalBuilder; +use wcgi_host::CgiDialect; +use webc::metadata::Command; + +use crate::{ + bin_factory::BinaryPackage, + capabilities::Capabilities, + journal::DynJournal, + runners::{ + dcgi::handler::Handler, + wcgi::{self, NoOpWcgiCallbacks, WcgiRunner}, + MappedDirectory, + }, + runtime::{DynRuntime, OverriddenRuntime}, + Runtime, +}; + +use super::{DcgiCallbacks, DcgiInstanceFactory}; + +#[derive(Debug)] +pub struct DcgiRunner { + config: Config, + inner: wcgi::WcgiRunner, +} + +impl DcgiRunner { + pub fn new(factory: DcgiInstanceFactory) -> Self { + let callbacks = DcgiCallbacks::new(factory, NoOpWcgiCallbacks); + DcgiRunner { + config: Config { + inner: wcgi::Config::new(callbacks.clone()), + }, + inner: WcgiRunner::new(callbacks), + } + } + + pub fn config(&mut self) -> &mut Config { + &mut self.config + } + + #[tracing::instrument(skip_all)] + fn prepare_handler( + &mut self, + command_name: &str, + pkg: &BinaryPackage, + runtime: Arc, + ) -> Result { + let inner: wcgi::Handler = + self.inner + .prepare_handler(command_name, pkg, true, CgiDialect::Rfc3875, runtime)?; + Ok(Handler::new(inner)) + } +} + +/// The base URI used by a [`Dcgi`] runner. +pub const DCGI_RUNNER_URI: &str = "https://webc.org/runner/dcgi"; + +impl crate::runners::Runner for DcgiRunner { + fn can_run_command(command: &Command) -> Result { + Ok(command.runner.starts_with(DCGI_RUNNER_URI)) + } + + fn run_command( + &mut self, + command_name: &str, + pkg: &BinaryPackage, + runtime: Arc, + ) -> Result<(), Error> { + // We use a filter in front of the journals supplied to the runtime. + // The reason for this is that DCGI currently only supports persisting the + // file system changes as it is unable to run the main function more than + // once due to limitations in the runtime + let journals = runtime + .journals() + .clone() + .into_iter() + .map(|journal| { + let journal = FilteredJournalBuilder::new() + .with_ignore_memory(true) + .with_ignore_threads(true) + .with_ignore_core(true) + .with_ignore_snapshots(true) + .with_ignore_networking(true) + .with_ignore_stdio(true) + .build(journal); + Arc::new(journal) as Arc + }) + .collect::>(); + let runtime = OverriddenRuntime::new(runtime).with_journals(journals); + let runtime = Arc::new(runtime) as Arc; + + //We now pass the runtime to the the handlers + let handler = self.prepare_handler(command_name, pkg, Arc::clone(&runtime))?; + self.inner.run_command_with_handler(handler, runtime) + } +} + +#[derive(Debug)] +pub struct Config { + inner: wcgi::Config, +} + +impl Config { + pub fn inner(&mut self) -> &mut wcgi::Config { + &mut self.inner + } + + pub fn addr(&mut self, addr: SocketAddr) -> &mut Self { + self.inner.addr(addr); + self + } + + /// Add an argument to the WASI executable's command-line arguments. + pub fn arg(&mut self, arg: impl Into) -> &mut Self { + self.inner.arg(arg); + self + } + + /// Add multiple arguments to the WASI executable's command-line arguments. + pub fn args(&mut self, args: A) -> &mut Self + where + A: IntoIterator, + S: Into, + { + self.inner.args(args); + self + } + + /// Expose an environment variable to the guest. + pub fn env(&mut self, name: impl Into, value: impl Into) -> &mut Self { + self.inner.env(name, value); + self + } + + /// Expose multiple environment variables to the guest. + pub fn envs(&mut self, variables: I) -> &mut Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self.inner.envs(variables); + self + } + + /// Forward all of the host's environment variables to the guest. + pub fn forward_host_env(&mut self) -> &mut Self { + self.inner.forward_host_env(); + self + } + + pub fn map_directory(&mut self, dir: MappedDirectory) -> &mut Self { + self.inner.map_directory(dir); + self + } + + pub fn map_directories( + &mut self, + mappings: impl IntoIterator, + ) -> &mut Self { + self.inner.map_directories(mappings); + self + } + + /// Set callbacks that will be triggered at various points in the runner's + /// lifecycle. + pub fn callbacks( + &mut self, + callbacks: impl wcgi::Callbacks + Send + Sync + 'static, + ) -> &mut Self { + self.inner.callbacks(callbacks); + self + } + + /// Add a package that should be available to the instance at runtime. + pub fn inject_package(&mut self, pkg: BinaryPackage) -> &mut Self { + self.inner.inject_package(pkg); + self + } + + /// Add packages that should be available to the instance at runtime. + pub fn inject_packages( + &mut self, + packages: impl IntoIterator, + ) -> &mut Self { + self.inner.inject_packages(packages); + self + } + + pub fn capabilities(&mut self) -> &mut Capabilities { + self.inner.capabilities() + } + + pub fn add_snapshot_trigger(&mut self, on: crate::journal::SnapshotTrigger) { + self.inner.add_snapshot_trigger(on); + } + + pub fn add_default_snapshot_triggers(&mut self) -> &mut Self { + self.inner.add_default_snapshot_triggers(); + self + } + + pub fn has_snapshot_trigger(&self, on: crate::journal::SnapshotTrigger) -> bool { + self.inner.has_snapshot_trigger(on) + } + + pub fn with_snapshot_interval(&mut self, period: std::time::Duration) -> &mut Self { + self.inner.with_snapshot_interval(period); + self + } + + pub fn add_journal(&mut self, journal: Arc) -> &mut Self { + self.inner.add_journal(journal); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn send_and_sync() { + fn assert_send() {} + fn assert_sync() {} + + assert_send::(); + assert_sync::(); + } +} diff --git a/lib/wasix/src/runners/mod.rs b/lib/wasix/src/runners/mod.rs index ed9fa74f006..e32221049ec 100644 --- a/lib/wasix/src/runners/mod.rs +++ b/lib/wasix/src/runners/mod.rs @@ -1,5 +1,7 @@ mod runner; +#[cfg(feature = "webc_runner_rt_dcgi")] +pub mod dcgi; #[cfg(feature = "webc_runner_rt_emscripten")] pub mod emscripten; pub mod wasi; diff --git a/lib/wasix/src/runners/wasi.rs b/lib/wasix/src/runners/wasi.rs index f1461315066..93911957bb7 100644 --- a/lib/wasix/src/runners/wasi.rs +++ b/lib/wasix/src/runners/wasi.rs @@ -11,9 +11,10 @@ use webc::metadata::{annotations::Wasi, Command}; use crate::{ bin_factory::BinaryPackage, capabilities::Capabilities, + journal::{DynJournal, SnapshotTrigger}, runners::{wasi_common::CommonWasiOptions, MappedDirectory}, - runtime::task_manager::VirtualTaskManagerExt, - Runtime, WasiEnvBuilder, WasiRuntimeError, + runtime::{module_cache::ModuleHash, task_manager::VirtualTaskManagerExt}, + Runtime, WasiEnvBuilder, WasiError, WasiRuntimeError, }; use super::wasi_common::MappedCommand; @@ -188,6 +189,37 @@ impl WasiRunner { self.wasi.capabilities = capabilities; } + pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) -> &mut Self { + self.wasi.snapshot_on.push(on); + self + } + + pub fn add_default_snapshot_triggers(&mut self) -> &mut Self { + for on in crate::journal::DEFAULT_SNAPSHOT_TRIGGERS { + if !self.has_snapshot_trigger(on) { + self.add_snapshot_trigger(on); + } + } + self + } + + pub fn has_snapshot_trigger(&self, on: SnapshotTrigger) -> bool { + self.wasi.snapshot_on.iter().any(|t| *t == on) + } + + pub fn with_snapshot_interval(&mut self, period: std::time::Duration) -> &mut Self { + if !self.has_snapshot_trigger(SnapshotTrigger::PeriodicInterval) { + self.add_snapshot_trigger(SnapshotTrigger::PeriodicInterval); + } + self.wasi.snapshot_interval.replace(period); + self + } + + pub fn add_journal(&mut self, journal: Arc) -> &mut Self { + self.wasi.journals.push(journal); + self + } + pub fn with_stdin(mut self, stdin: Box) -> Self { self.set_stdin(stdin); self @@ -231,6 +263,7 @@ impl WasiRunner { let container_fs = if let Some(pkg) = pkg { builder.add_webc(pkg.clone()); + builder.set_module_hash(pkg.hash()); Some(Arc::clone(&pkg.webc_fs)) } else { None @@ -257,6 +290,7 @@ impl WasiRunner { runtime: Arc, program_name: &str, module: &Module, + module_hash: ModuleHash, asyncify: bool, ) -> Result<(), Error> { let wasi = webc::metadata::annotations::Wasi::new(program_name); @@ -264,9 +298,9 @@ impl WasiRunner { let env = self.prepare_webc_env(program_name, &wasi, None, runtime, None)?; if asyncify { - env.run_with_store_async(module.clone(), store)?; + env.run_with_store_async(module.clone(), module_hash, store)?; } else { - env.run_with_store(module.clone(), &mut store)?; + env.run_with_store_ext(module.clone(), module_hash, &mut store)?; } Ok(()) @@ -295,11 +329,23 @@ impl crate::runners::Runner for WasiRunner { .annotation("wasi")? .unwrap_or_else(|| Wasi::new(command_name)); - let env = self + #[allow(unused_mut)] + let mut env = self .prepare_webc_env(command_name, &wasi, Some(pkg), Arc::clone(&runtime), None) - .context("Unable to prepare the WASI environment")? - .build()?; + .context("Unable to prepare the WASI environment")?; + + #[cfg(feature = "journal")] + { + for journal in self.wasi.journals.clone() { + env.add_journal(journal); + } + + for snapshot_trigger in self.wasi.snapshot_on.iter().cloned() { + env.add_snapshot_trigger(snapshot_trigger); + } + } + let env = env.build()?; let store = runtime.new_store(); let command_name = command_name.to_string(); @@ -316,7 +362,51 @@ impl crate::runners::Runner for WasiRunner { task_handle .wait_finished() .await - .map_err(|err| Arc::into_inner(err).expect("Error shouldn't be shared")) + .map_err(|err| { + // We do our best to recover the error + let msg = err.to_string(); + let weak = Arc::downgrade(&err); + Arc::into_inner(err).unwrap_or_else(|| { + weak.upgrade() + .map(|err| match err.as_ref() { + WasiRuntimeError::Init(a) => WasiRuntimeError::Init(a.clone()), + WasiRuntimeError::Export(a) => { + WasiRuntimeError::Export(a.clone()) + } + WasiRuntimeError::Instantiation(a) => { + WasiRuntimeError::Instantiation(a.clone()) + } + WasiRuntimeError::Wasi(WasiError::Exit(a)) => { + WasiRuntimeError::Wasi(WasiError::Exit(*a)) + } + WasiRuntimeError::Wasi(WasiError::UnknownWasiVersion) => { + WasiRuntimeError::Wasi(WasiError::UnknownWasiVersion) + } + WasiRuntimeError::Wasi(WasiError::DeepSleep(_)) => { + WasiRuntimeError::Anyhow(Arc::new(anyhow::format_err!( + "deep-sleep" + ))) + } + WasiRuntimeError::ControlPlane(a) => { + WasiRuntimeError::ControlPlane(a.clone()) + } + WasiRuntimeError::Runtime(a) => { + WasiRuntimeError::Runtime(a.clone()) + } + WasiRuntimeError::Thread(a) => { + WasiRuntimeError::Thread(a.clone()) + } + WasiRuntimeError::Anyhow(a) => { + WasiRuntimeError::Anyhow(a.clone()) + } + }) + .unwrap_or_else(|| { + WasiRuntimeError::Anyhow(Arc::new(anyhow::format_err!( + "{}", msg + ))) + }) + }) + }) .context("Unable to wait for the process to exit") } .in_current_span(), diff --git a/lib/wasix/src/runners/wasi_common.rs b/lib/wasix/src/runners/wasi_common.rs index d02fa7d2185..453fa3c077c 100644 --- a/lib/wasix/src/runners/wasi_common.rs +++ b/lib/wasix/src/runners/wasi_common.rs @@ -5,12 +5,16 @@ use std::{ }; use anyhow::{Context, Error}; +use derivative::Derivative; use futures::future::BoxFuture; use virtual_fs::{FileSystem, FsError, OverlayFileSystem, RootFileSystemBuilder, TmpFileSystem}; use webc::metadata::annotations::Wasi as WasiAnnotation; use crate::{ - bin_factory::BinaryPackage, capabilities::Capabilities, runners::MappedDirectory, + bin_factory::BinaryPackage, + capabilities::Capabilities, + journal::{DynJournal, SnapshotTrigger}, + runners::MappedDirectory, WasiEnvBuilder, }; @@ -22,7 +26,8 @@ pub struct MappedCommand { pub target: String, } -#[derive(Debug, Default, Clone)] +#[derive(Derivative, Default, Clone)] +#[derivative(Debug)] pub(crate) struct CommonWasiOptions { pub(crate) args: Vec, pub(crate) env: HashMap, @@ -31,6 +36,10 @@ pub(crate) struct CommonWasiOptions { pub(crate) mapped_host_commands: Vec, pub(crate) injected_packages: Vec, pub(crate) capabilities: Capabilities, + #[derivative(Debug = "ignore")] + pub(crate) journals: Vec>, + pub(crate) snapshot_on: Vec, + pub(crate) snapshot_interval: Option, pub(crate) current_dir: Option, } @@ -43,6 +52,7 @@ impl CommonWasiOptions { root_fs: Option, ) -> Result<(), anyhow::Error> { let root_fs = root_fs.unwrap_or_else(|| RootFileSystemBuilder::default().build()); + let fs = prepare_filesystem(root_fs, &self.mapped_dirs, container_fs, builder)?; builder.add_preopen_dir("/")?; diff --git a/lib/wasix/src/runners/wcgi/callbacks.rs b/lib/wasix/src/runners/wcgi/callbacks.rs new file mode 100644 index 00000000000..1716691b314 --- /dev/null +++ b/lib/wasix/src/runners/wcgi/callbacks.rs @@ -0,0 +1,62 @@ +use std::{collections::HashMap, sync::Arc}; + +use virtual_fs::Pipe; +use wasmer::{Memory, Module, Store}; + +use crate::{runtime::module_cache::ModuleHash, WasiEnv}; + +use super::{create_env::default_recycle_env, handler::SetupBuilder, *}; + +/// Configuration used for creating a new environment +pub struct CreateEnvConfig { + pub env: HashMap, + pub program_name: String, + pub module: Module, + pub module_hash: ModuleHash, + pub runtime: Arc, + pub setup_builder: SetupBuilder, +} + +/// Result of a create operation on a new environment +pub struct CreateEnvResult { + pub env: WasiEnv, + pub memory: Option<(Memory, Store)>, + pub body_sender: Pipe, + pub body_receiver: Pipe, + pub stderr_receiver: Pipe, +} + +/// Configuration used for reusing an new environment +pub struct RecycleEnvConfig { + pub env: WasiEnv, + pub memory: Memory, + pub store: Store, +} + +/// Callbacks that are triggered at various points in the lifecycle of a runner +/// and any WebAssembly instances it may start. +#[async_trait::async_trait] +pub trait Callbacks: Send + Sync + 'static { + /// A callback that is called whenever the server starts. + fn started(&self, _abort: AbortHandle) {} + + /// Data was written to stderr by an instance. + fn on_stderr(&self, _stderr: &[u8]) {} + + /// Reading from stderr failed. + fn on_stderr_error(&self, _error: std::io::Error) {} + + /// Recycle the WASI environment + async fn recycle_env(&self, conf: RecycleEnvConfig) { + default_recycle_env(conf).await + } + + /// Create the WASI environment + async fn create_env(&self, conf: CreateEnvConfig) -> anyhow::Result { + default_create_env(conf).await + } +} + +pub struct NoOpWcgiCallbacks; + +impl Callbacks for NoOpWcgiCallbacks {} diff --git a/lib/wasix/src/runners/wcgi/create_env.rs b/lib/wasix/src/runners/wcgi/create_env.rs new file mode 100644 index 00000000000..c8ab9fe9ac8 --- /dev/null +++ b/lib/wasix/src/runners/wcgi/create_env.rs @@ -0,0 +1,48 @@ +use virtual_fs::Pipe; + +use crate::{ + capabilities::Capabilities, http::HttpClientCapabilityV1, + runners::wcgi::callbacks::CreateEnvResult, WasiEnvBuilder, +}; + +use super::{callbacks::CreateEnvConfig, RecycleEnvConfig}; + +pub(crate) async fn default_recycle_env(mut conf: RecycleEnvConfig) { + tracing::debug!("Destroying the WebAssembly instance"); + + conf.env.disable_fs_cleanup = false; + conf.env.on_exit(None).await; +} + +pub(crate) async fn default_create_env(conf: CreateEnvConfig) -> anyhow::Result { + tracing::debug!("Creating the WebAssembly instance"); + + let (req_body_sender, req_body_receiver) = Pipe::channel(); + let (res_body_sender, res_body_receiver) = Pipe::channel(); + let (stderr_sender, stderr_receiver) = Pipe::channel(); + + let mut builder = WasiEnvBuilder::new(&conf.program_name); + + (conf.setup_builder)(&mut builder)?; + + builder.add_envs(conf.env); + + let builder = builder + .stdin(Box::new(req_body_receiver)) + .stdout(Box::new(res_body_sender)) + .stderr(Box::new(stderr_sender)) + .capabilities(Capabilities { + insecure_allow_all: true, + http_client: HttpClientCapabilityV1::new_allow_all(), + threading: Default::default(), + }); + let env = builder.build()?; + + Ok(CreateEnvResult { + env, + memory: None, + body_sender: req_body_sender, + body_receiver: res_body_receiver, + stderr_receiver, + }) +} diff --git a/lib/wasix/src/runners/wcgi/handler.rs b/lib/wasix/src/runners/wcgi/handler.rs index 2101142e3ca..6f0eb012bc8 100644 --- a/lib/wasix/src/runners/wcgi/handler.rs +++ b/lib/wasix/src/runners/wcgi/handler.rs @@ -2,15 +2,26 @@ use std::{collections::HashMap, ops::Deref, pin::Pin, sync::Arc, task::Poll}; use anyhow::Error; use futures::{Future, FutureExt, StreamExt}; -use http::{Request, Response}; +use http::{Request, Response, StatusCode}; use hyper::{service::Service, Body}; use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt}; use tracing::Instrument; +use virtual_mio::InlineWaker; use wasmer::Module; +use wasmer_wasix_types::wasi::ExitCode; use wcgi_host::CgiDialect; use crate::{ - capabilities::Capabilities, http::HttpClientCapabilityV1, runners::wcgi::Callbacks, Pipe, + bin_factory::run_exec, + os::task::OwnedTaskStatus, + runners::wcgi::{ + callbacks::{CreateEnvConfig, RecycleEnvConfig}, + Callbacks, + }, + runtime::{ + module_cache::ModuleHash, + task_manager::{TaskWasm, TaskWasmRecycleProperties}, + }, Runtime, VirtualTaskManager, WasiEnvBuilder, }; @@ -20,45 +31,46 @@ use crate::{ pub(crate) struct Handler(Arc); impl Handler { - pub(crate) fn new(state: SharedState) -> Self { - Handler(Arc::new(state)) + pub(crate) fn new(state: Arc) -> Self { + Handler(state) } #[tracing::instrument(level = "debug", skip_all, err)] - pub(crate) async fn handle(&self, req: Request) -> Result, Error> { + pub(crate) async fn handle( + &self, + req: Request, + token: T, + ) -> Result, Error> + where + T: Send + 'static, + { tracing::debug!(headers=?req.headers()); let (parts, body) = req.into_parts(); - let (req_body_sender, req_body_receiver) = Pipe::channel(); - let (res_body_sender, res_body_receiver) = Pipe::channel(); - let (stderr_sender, stderr_receiver) = Pipe::channel(); - - tracing::debug!("Creating the WebAssembly instance"); - - let mut builder = WasiEnvBuilder::new(&self.program_name); - - (self.setup_builder)(&mut builder)?; - // Note: We want to apply the CGI environment variables *after* // anything specified by WASI annotations so users get a chance to // override things like $DOCUMENT_ROOT and $SCRIPT_FILENAME. let mut request_specific_env = HashMap::new(); + request_specific_env.insert("REQUEST_METHOD".to_string(), parts.method.to_string()); + request_specific_env.insert("SCRIPT_NAME".to_string(), parts.uri.path().to_string()); + if let Some(query) = parts.uri.query() { + request_specific_env.insert("QUERY_STRING".to_string(), query.to_string()); + } self.dialect .prepare_environment_variables(parts, &mut request_specific_env); - builder.add_envs(request_specific_env); - - let builder = builder - .stdin(Box::new(req_body_receiver)) - .stdout(Box::new(res_body_sender)) - .stderr(Box::new(stderr_sender)) - .capabilities(Capabilities { - insecure_allow_all: true, - http_client: HttpClientCapabilityV1::new_allow_all(), - threading: Default::default(), - }); - let module = self.module.clone(); + let create = self + .callbacks + .create_env(CreateEnvConfig { + env: request_specific_env, + program_name: self.program_name.clone(), + module: self.module.clone(), + module_hash: self.module_hash, + runtime: self.runtime.clone(), + setup_builder: self.setup_builder.clone(), + }) + .await?; tracing::debug!( dialect=%self.dialect, @@ -66,44 +78,125 @@ impl Handler { ); let task_manager = self.runtime.task_manager(); - let store = self.runtime.new_store(); - - let (run_tx, mut run_rx) = tokio::sync::mpsc::unbounded_channel(); - task_manager.task_dedicated(Box::new(move || { - run_tx - .send(builder.run_with_store_async(module, store)) - .ok(); - }))?; - let done = async move { run_rx.recv().await.unwrap().map_err(Error::from) }; - - let mut res_body_receiver = tokio::io::BufReader::new(res_body_receiver); + let env = create.env; + let module = self.module.clone(); + // The recycle function will attempt to reuse the instance let callbacks = Arc::clone(&self.callbacks); - let work_consume_stderr = async move { - consume_stderr(stderr_receiver, callbacks).await; - } - .in_current_span(); + let recycle = { + let callbacks = callbacks.clone(); + move |props: TaskWasmRecycleProperties| { + InlineWaker::block_on(callbacks.recycle_env(RecycleEnvConfig { + env: props.env, + store: props.store, + memory: props.memory, + })); + + // We release the token after we recycle the environment + // so that race conditions (such as reusing instances) are + // avoided + drop(token); + } + }; + let finished = env.process.finished.clone(); + + /* + * TODO: Reusing memory for DCGI calls and not just the file system + * + * DCGI does not support reusing the memory for the following reasons + * 1. The environment variables can not be overridden after libc does its lazy loading + * 2. The HTTP request variables are passed as environment variables and hence can not be changed + * after the first call is made on the memory + * 3. The `SpawnMemoryType` is not send however this handler is running as a Send async. In order + * to fix this the entire handler would need to run in its own non-Send thread. + + // Determine if we are going to create memory and import it or just rely on self creation of memory + let spawn_type = create + .memory + .map(|memory| SpawnMemoryType::ShareMemory(memory, store.as_store_ref())); + */ + + // We run the WCGI thread on the dedicated WASM + // thread pool that has support for asynchronous + // threading, etc... task_manager - .task_shared(Box::new(move || Box::pin(work_consume_stderr))) - .ok(); - - let work_drive_io = async move { - if let Err(e) = drive_request_to_completion(done, body, req_body_sender).await { - tracing::error!( - error = &*e as &dyn std::error::Error, - "Unable to drive the request to completion" - ); + .task_wasm( + TaskWasm::new(Box::new(run_exec), env, module, false) + //.with_optional_memory(spawn_type) + .with_recycle(Box::new(recycle)), + ) + .map_err(|err| { + tracing::warn!("failed to execute WCGI thread - {}", err); + err + })?; + + let mut res_body_receiver = tokio::io::BufReader::new(create.body_receiver); + + let stderr_receiver = create.stderr_receiver; + let propagate_stderr = self.propagate_stderr; + let work_consume_stderr = { + let callbacks = callbacks.clone(); + async move { consume_stderr(stderr_receiver, callbacks, propagate_stderr).await } + .in_current_span() + }; + + tracing::trace!( + dialect=%self.dialect, + "spawning request forwarder", + ); + + let req_body_sender = create.body_sender; + let ret = drive_request_to_completion(finished, body, req_body_sender).await; + + // When set this will cause any stderr responses to + // take precedence over nominal responses but it + // will cause the stderr pipe to be read to the end + // before transmitting the body + if propagate_stderr { + if let Some(stderr) = work_consume_stderr.await { + if !stderr.is_empty() { + return Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from(stderr))?); + } } + } else { + task_manager + .task_shared(Box::new(move || { + Box::pin(async move { + work_consume_stderr.await; + }) + })) + .ok(); } - .in_current_span(); - task_manager - .task_shared(Box::new(move || Box::pin(work_drive_io))) - .ok(); + + match ret { + Ok(_) => {} + Err(e) => { + let e = e.to_string(); + tracing::error!(error = e, "Unable to drive the request to completion"); + return Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from(e.as_bytes().to_vec()))?); + } + } + + tracing::trace!( + dialect=%self.dialect, + "extracting response parts", + ); let parts = self .dialect .extract_response_header(&mut res_body_receiver) - .await?; + .await; + let parts = parts?; + + tracing::trace!( + dialect=%self.dialect, + status=%parts.status, + "received response parts", + ); let chunks = futures::stream::try_unfold(res_body_receiver, |mut r| async move { match r.fill_buf().await { @@ -118,6 +211,11 @@ impl Handler { }); let body = hyper::Body::wrap_stream(chunks); + tracing::trace!( + dialect=%self.dialect, + "returning response with body stream", + ); + let response = hyper::Response::from_parts(parts, body); Ok(response) } @@ -134,10 +232,10 @@ impl Deref for Handler { /// Drive the request to completion by streaming the request body to the /// instance and waiting for it to exit. async fn drive_request_to_completion( - done: impl Future>, + finished: Arc, mut request_body: hyper::Body, - mut instance_stdin: impl AsyncWrite + Send + Unpin + 'static, -) -> Result<(), Error> { + mut instance_stdin: impl AsyncWrite + Send + Sync + Unpin + 'static, +) -> Result { let request_body_send = async move { // Copy the request into our instance, chunk-by-chunk. If the instance // dies before we finish writing the body, the instance's side of the @@ -161,9 +259,8 @@ async fn drive_request_to_completion( } .in_current_span(); - futures::try_join!(done, request_body_send)?; - - Ok(()) + let (ret, _) = futures::try_join!(finished.await_termination_anyhow(), request_body_send)?; + Ok(ret) } /// Read the instance's stderr, taking care to preserve output even when WASI @@ -172,9 +269,15 @@ async fn drive_request_to_completion( async fn consume_stderr( stderr: impl AsyncRead + Send + Unpin + 'static, callbacks: Arc, -) { + propagate_stderr: bool, +) -> Option> { let mut stderr = tokio::io::BufReader::new(stderr); + let mut propagate = match propagate_stderr { + true => Some(Vec::new()), + false => None, + }; + // Note: we don't want to just read_to_end() because a reading error // would cause us to lose all of stderr. At least this way we'll be // able to show users the partial result. @@ -185,26 +288,35 @@ async fn consume_stderr( break; } Ok(chunk) => { + tracing::trace!("received stderr (len={})", chunk.len()); + if let Some(propogate) = propagate.as_mut() { + propogate.write_all(chunk).await.ok(); + } callbacks.on_stderr(chunk); let bytes_read = chunk.len(); stderr.consume(bytes_read); } Err(e) => { + tracing::trace!("received stderr (err={})", e); callbacks.on_stderr_error(e); break; } } } + + propagate } -type SetupBuilder = Box Result<(), anyhow::Error> + Send + Sync>; +pub type SetupBuilder = Arc Result<(), anyhow::Error> + Send + Sync>; #[derive(derivative::Derivative)] #[derivative(Debug)] pub(crate) struct SharedState { pub(crate) module: Module, + pub(crate) module_hash: ModuleHash, pub(crate) dialect: CgiDialect, pub(crate) program_name: String, + pub(crate) propagate_stderr: bool, #[derivative(Debug = "ignore")] pub(crate) setup_builder: SetupBuilder, #[derivative(Debug = "ignore")] @@ -226,7 +338,7 @@ impl Service> for Handler { fn call(&mut self, request: Request) -> Self::Future { // Note: all fields are reference-counted so cloning is pretty cheap let handler = self.clone(); - let fut = async move { handler.handle(request).await }; + let fut = async move { handler.handle(request, ()).await }; fut.boxed() } } diff --git a/lib/wasix/src/runners/wcgi/mod.rs b/lib/wasix/src/runners/wcgi/mod.rs index e39e197f6af..29dad55f56b 100644 --- a/lib/wasix/src/runners/wcgi/mod.rs +++ b/lib/wasix/src/runners/wcgi/mod.rs @@ -1,5 +1,11 @@ +mod callbacks; +mod create_env; mod handler; mod runner; -pub use self::runner::{Callbacks, Config, WcgiRunner}; +pub use self::runner::{Config, WcgiRunner}; +pub use callbacks::NoOpWcgiCallbacks; +pub use callbacks::{Callbacks, CreateEnvConfig, CreateEnvResult, RecycleEnvConfig}; +pub(crate) use create_env::default_create_env; pub use futures::future::AbortHandle; +pub(crate) use handler::{Handler, SharedState}; diff --git a/lib/wasix/src/runners/wcgi/runner.rs b/lib/wasix/src/runners/wcgi/runner.rs index 200481c9425..882e8ade5bf 100644 --- a/lib/wasix/src/runners/wcgi/runner.rs +++ b/lib/wasix/src/runners/wcgi/runner.rs @@ -1,10 +1,9 @@ use std::{net::SocketAddr, sync::Arc, time::Duration}; use anyhow::{Context, Error}; -use futures::future::AbortHandle; use http::{Request, Response}; use hyper::Body; -use tower::{make::Shared, ServiceBuilder}; +use tower::{make::Shared, Service, ServiceBuilder}; use tower_http::{catch_panic::CatchPanicLayer, cors::CorsLayer, trace::TraceLayer}; use tracing::Span; use wcgi_host::CgiDialect; @@ -25,14 +24,21 @@ use crate::{ Runtime, WasiEnvBuilder, }; -#[derive(Debug, Default)] +use super::Callbacks; + +#[derive(Debug)] pub struct WcgiRunner { config: Config, } impl WcgiRunner { - pub fn new() -> Self { - WcgiRunner::default() + pub fn new(callbacks: C) -> Self + where + C: Callbacks, + { + Self { + config: Config::new(callbacks), + } } pub fn config(&mut self) -> &mut Config { @@ -40,10 +46,12 @@ impl WcgiRunner { } #[tracing::instrument(skip_all)] - fn prepare_handler( + pub(crate) fn prepare_handler( &mut self, command_name: &str, pkg: &BinaryPackage, + propagate_stderr: bool, + default_dialect: CgiDialect, runtime: Arc, ) -> Result { let cmd = pkg @@ -59,7 +67,7 @@ impl WcgiRunner { let Wcgi { dialect, .. } = metadata.annotation("wcgi")?.unwrap_or_default(); let dialect = match dialect { Some(d) => d.parse().context("Unable to parse the CGI dialect")?, - None => CgiDialect::Wcgi, + None => default_dialect, }; let container_fs = Arc::clone(&pkg.webc_fs); @@ -69,39 +77,39 @@ impl WcgiRunner { let setup_builder = move |builder: &mut WasiEnvBuilder| { wasi_common.prepare_webc_env(builder, Some(Arc::clone(&container_fs)), &wasi, None)?; builder.set_runtime(Arc::clone(&rt)); - Ok(()) }; let shared = SharedState { module, + module_hash: pkg.hash(), dialect, + propagate_stderr, program_name: command_name.to_string(), - setup_builder: Box::new(setup_builder), + setup_builder: Arc::new(setup_builder), callbacks: Arc::clone(&self.config.callbacks), runtime, }; - Ok(Handler::new(shared)) - } -} - -impl crate::runners::Runner for WcgiRunner { - fn can_run_command(command: &Command) -> Result { - Ok(command - .runner - .starts_with(webc::metadata::annotations::WCGI_RUNNER_URI)) + Ok(Handler::new(Arc::new(shared))) } - fn run_command( + pub(crate) fn run_command_with_handler( &mut self, - command_name: &str, - pkg: &BinaryPackage, + handler: S, runtime: Arc, - ) -> Result<(), Error> { - let handler = self.prepare_handler(command_name, pkg, Arc::clone(&runtime))?; - let callbacks = Arc::clone(&self.config.callbacks); - + ) -> Result<(), Error> + where + S: Service< + Request, + Response = http::Response, + Error = anyhow::Error, + Future = std::pin::Pin< + Box, Error>> + Send>, + >, + >, + S: Clone + Send + Sync + 'static, + { let service = ServiceBuilder::new() .layer( TraceLayer::new_for_http() @@ -125,6 +133,7 @@ impl crate::runners::Runner for WcgiRunner { let address = self.config.addr; tracing::info!(%address, "Starting the server"); + let callbacks = Arc::clone(&self.config.callbacks); runtime .task_manager() .spawn_and_block_on(async move { @@ -147,13 +156,37 @@ impl crate::runners::Runner for WcgiRunner { } } +impl crate::runners::Runner for WcgiRunner { + fn can_run_command(command: &Command) -> Result { + Ok(command + .runner + .starts_with(webc::metadata::annotations::WCGI_RUNNER_URI)) + } + + fn run_command( + &mut self, + command_name: &str, + pkg: &BinaryPackage, + runtime: Arc, + ) -> Result<(), Error> { + let handler = self.prepare_handler( + command_name, + pkg, + false, + CgiDialect::Wcgi, + Arc::clone(&runtime), + )?; + self.run_command_with_handler(handler, runtime) + } +} + #[derive(derivative::Derivative)] #[derivative(Debug)] pub struct Config { - wasi: CommonWasiOptions, - addr: SocketAddr, + pub(crate) wasi: CommonWasiOptions, + pub(crate) addr: SocketAddr, #[derivative(Debug = "ignore")] - callbacks: Arc, + pub(crate) callbacks: Arc, } impl Config { @@ -241,34 +274,55 @@ impl Config { pub fn capabilities(&mut self) -> &mut Capabilities { &mut self.wasi.capabilities } -} -impl Default for Config { - fn default() -> Self { - Self { - addr: ([127, 0, 0, 1], 8000).into(), - wasi: CommonWasiOptions::default(), - callbacks: Arc::new(NoopCallbacks), + #[cfg(feature = "journal")] + pub fn add_snapshot_trigger(&mut self, on: crate::journal::SnapshotTrigger) { + self.wasi.snapshot_on.push(on); + } + + #[cfg(feature = "journal")] + pub fn add_default_snapshot_triggers(&mut self) -> &mut Self { + for on in crate::journal::DEFAULT_SNAPSHOT_TRIGGERS { + if !self.has_snapshot_trigger(on) { + self.add_snapshot_trigger(on); + } } + self } -} -/// Callbacks that are triggered at various points in the lifecycle of a runner -/// and any WebAssembly instances it may start. -pub trait Callbacks: Send + Sync + 'static { - /// A callback that is called whenever the server starts. - fn started(&self, _abort: AbortHandle) {} + #[cfg(feature = "journal")] + pub fn has_snapshot_trigger(&self, on: crate::journal::SnapshotTrigger) -> bool { + self.wasi.snapshot_on.iter().any(|t| *t == on) + } - /// Data was written to stderr by an instance. - fn on_stderr(&self, _stderr: &[u8]) {} + #[cfg(feature = "journal")] + pub fn with_snapshot_interval(&mut self, period: std::time::Duration) -> &mut Self { + if !self.has_snapshot_trigger(crate::journal::SnapshotTrigger::PeriodicInterval) { + self.add_snapshot_trigger(crate::journal::SnapshotTrigger::PeriodicInterval); + } + self.wasi.snapshot_interval.replace(period); + self + } - /// Reading from stderr failed. - fn on_stderr_error(&self, _error: std::io::Error) {} + #[cfg(feature = "journal")] + pub fn add_journal(&mut self, journal: Arc) -> &mut Self { + self.wasi.journals.push(journal); + self + } } -struct NoopCallbacks; - -impl Callbacks for NoopCallbacks {} +impl Config { + pub fn new(callbacks: C) -> Self + where + C: Callbacks, + { + Self { + addr: ([127, 0, 0, 1], 8000).into(), + wasi: CommonWasiOptions::default(), + callbacks: Arc::new(callbacks), + } + } +} #[cfg(test)] mod tests { diff --git a/lib/wasix/src/runtime/mod.rs b/lib/wasix/src/runtime/mod.rs index a8f3384d893..56f40b9defe 100644 --- a/lib/wasix/src/runtime/mod.rs +++ b/lib/wasix/src/runtime/mod.rs @@ -11,6 +11,7 @@ use self::{ use std::{ fmt, + ops::Deref, sync::{Arc, Mutex}, }; @@ -19,6 +20,8 @@ use futures::future::BoxFuture; use virtual_net::{DynVirtualNetworking, VirtualNetworking}; use wasmer::Module; +#[cfg(feature = "journal")] +use crate::journal::DynJournal; use crate::{ http::{DynHttpClient, HttpClient}, os::TtyBridge, @@ -90,7 +93,7 @@ where } /// Load a a Webassembly module, trying to use a pre-compiled version if possible. - fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result> { + fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, anyhow::Result> { let engine = self.engine(); let module_cache = self.module_cache(); @@ -105,8 +108,27 @@ where fn load_module_sync(&self, wasm: &[u8]) -> Result { InlineWaker::block_on(self.load_module(wasm)) } + + /// The list of journals which will be used to restore the state of the + /// runtime at a particular point in time + #[cfg(feature = "journal")] + fn journals(&self) -> &'_ Vec> { + &EMPTY_JOURNAL_LIST + } + + /// The snapshot capturer takes and restores snapshots of the WASM process at specific + /// points in time by reading and writing log entries + #[cfg(feature = "journal")] + fn active_journal(&self) -> Option<&'_ DynJournal> { + None + } } +pub type DynRuntime = dyn Runtime + Send + Sync; + +#[cfg(feature = "journal")] +static EMPTY_JOURNAL_LIST: Vec> = Vec::new(); + /// Load a a Webassembly module, trying to use a pre-compiled version if possible. /// // This function exists to provide a reusable baseline implementation for @@ -180,6 +202,9 @@ pub struct PluggableRuntime { pub module_cache: Arc, #[derivative(Debug = "ignore")] pub tty: Option>, + #[cfg(feature = "journal")] + #[derivative(Debug = "ignore")] + pub journals: Vec>, } impl PluggableRuntime { @@ -214,6 +239,8 @@ impl PluggableRuntime { source: Arc::new(source), package_loader: Arc::new(loader), module_cache: Arc::new(module_cache::in_memory()), + #[cfg(feature = "journal")] + journals: Vec::new(), } } @@ -263,6 +290,12 @@ impl PluggableRuntime { self.http_client = Some(Arc::new(client)); self } + + #[cfg(feature = "journal")] + pub fn add_journal(&mut self, journal: Arc) -> &mut Self { + self.journals.push(journal); + self + } } impl Runtime for PluggableRuntime { @@ -308,4 +341,213 @@ impl Runtime for PluggableRuntime { fn module_cache(&self) -> Arc { self.module_cache.clone() } + + #[cfg(feature = "journal")] + fn journals(&self) -> &'_ Vec> { + &self.journals + } + + #[cfg(feature = "journal")] + fn active_journal(&self) -> Option<&DynJournal> { + self.journals.iter().last().map(|a| a.as_ref()) + } +} + +/// Runtime that allows for certain things to be overridden +/// such as the active journals +#[derive(Clone, Derivative)] +#[derivative(Debug)] +pub struct OverriddenRuntime { + inner: Arc, + task_manager: Option>, + networking: Option, + http_client: Option, + package_loader: Option>, + source: Option>, + engine: Option, + module_cache: Option>, + #[derivative(Debug = "ignore")] + tty: Option>, + #[cfg(feature = "journal")] + #[derivative(Debug = "ignore")] + journals: Option>>, +} + +impl OverriddenRuntime { + pub fn new(inner: Arc) -> Self { + Self { + inner, + task_manager: None, + networking: None, + http_client: None, + package_loader: None, + source: None, + engine: None, + module_cache: None, + tty: None, + #[cfg(feature = "journal")] + journals: None, + } + } + + pub fn with_task_manager(mut self, task_manager: Arc) -> Self { + self.task_manager.replace(task_manager); + self + } + + pub fn with_networking(mut self, networking: DynVirtualNetworking) -> Self { + self.networking.replace(networking); + self + } + + pub fn with_http_client(mut self, http_client: DynHttpClient) -> Self { + self.http_client.replace(http_client); + self + } + + pub fn with_package_loader( + mut self, + package_loader: Arc, + ) -> Self { + self.package_loader.replace(package_loader); + self + } + + pub fn with_source(mut self, source: Arc) -> Self { + self.source.replace(source); + self + } + + pub fn with_engine(mut self, engine: wasmer::Engine) -> Self { + self.engine.replace(engine); + self + } + + pub fn with_module_cache(mut self, module_cache: Arc) -> Self { + self.module_cache.replace(module_cache); + self + } + + #[cfg(feature = "journal")] + pub fn with_tty(mut self, tty: Arc) -> Self { + self.tty.replace(tty); + self + } + + #[cfg(feature = "journal")] + pub fn with_journals(mut self, journals: Vec>) -> Self { + self.journals.replace(journals); + self + } +} + +impl Runtime for OverriddenRuntime { + fn networking(&self) -> &DynVirtualNetworking { + if let Some(net) = self.networking.as_ref() { + net + } else { + self.inner.networking() + } + } + + fn task_manager(&self) -> &Arc { + if let Some(rt) = self.task_manager.as_ref() { + rt + } else { + self.inner.task_manager() + } + } + + fn source(&self) -> Arc { + if let Some(source) = self.source.clone() { + source + } else { + self.inner.source() + } + } + + fn package_loader(&self) -> Arc { + if let Some(loader) = self.package_loader.clone() { + loader + } else { + self.inner.package_loader() + } + } + + fn module_cache(&self) -> Arc { + if let Some(cache) = self.module_cache.clone() { + cache + } else { + self.inner.module_cache() + } + } + + fn engine(&self) -> wasmer::Engine { + if let Some(engine) = self.engine.clone() { + engine + } else { + self.inner.engine() + } + } + + fn new_store(&self) -> wasmer::Store { + if let Some(engine) = self.engine.clone() { + wasmer::Store::new(engine) + } else { + self.inner.new_store() + } + } + + fn http_client(&self) -> Option<&DynHttpClient> { + if let Some(client) = self.http_client.as_ref() { + Some(client) + } else { + self.inner.http_client() + } + } + + fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> { + if let Some(tty) = self.tty.as_ref() { + Some(tty.deref()) + } else { + self.inner.tty() + } + } + + #[cfg(feature = "journal")] + fn journals(&self) -> &'_ Vec> { + if let Some(journals) = self.journals.as_ref() { + journals + } else { + self.inner.journals() + } + } + + #[cfg(feature = "journal")] + fn active_journal(&self) -> Option<&'_ DynJournal> { + if let Some(journals) = self.journals.as_ref() { + journals.iter().last().map(|a| a.as_ref()) + } else { + self.inner.active_journal() + } + } + + fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, anyhow::Result> { + if self.engine.is_some() || self.module_cache.is_some() { + let engine = self.engine(); + let module_cache = self.module_cache(); + let task = async move { load_module(&engine, &module_cache, wasm).await }; + Box::pin(task) + } else { + self.inner.load_module(wasm) + } + } + + fn load_module_sync(&self, wasm: &[u8]) -> Result { + if self.engine.is_some() || self.module_cache.is_some() { + InlineWaker::block_on(self.load_module(wasm)) + } else { + self.inner.load_module_sync(wasm) + } + } } diff --git a/lib/wasix/src/runtime/module_cache/types.rs b/lib/wasix/src/runtime/module_cache/types.rs index afe5251bbba..57324cbd7e9 100644 --- a/lib/wasix/src/runtime/module_cache/types.rs +++ b/lib/wasix/src/runtime/module_cache/types.rs @@ -5,6 +5,7 @@ use std::{ path::PathBuf, }; +use rand::RngCore; use wasmer::{Engine, Module}; use crate::runtime::module_cache::FallbackCache; @@ -136,6 +137,14 @@ impl ModuleHash { ModuleHash(key) } + // Creates a random hash for the module + pub fn random() -> Self { + let mut rand = rand::thread_rng(); + let mut key = [0u8; 8]; + rand.fill_bytes(&mut key); + Self(key) + } + /// Parse a XXHash hash from a hex-encoded string. pub fn parse_hex(hex_str: &str) -> Result { let mut hash = [0_u8; 8]; diff --git a/lib/wasix/src/runtime/task_manager/mod.rs b/lib/wasix/src/runtime/task_manager/mod.rs index ac0c0f36223..b42be6784aa 100644 --- a/lib/wasix/src/runtime/task_manager/mod.rs +++ b/lib/wasix/src/runtime/task_manager/mod.rs @@ -7,6 +7,7 @@ use std::task::{Context, Poll}; use std::{pin::Pin, time::Duration}; use bytes::Bytes; +use derivative::Derivative; use futures::future::BoxFuture; use futures::{Future, TryFutureExt}; use wasmer::{AsStoreMut, AsStoreRef, Memory, MemoryType, Module, Store, StoreMut, StoreRef}; @@ -14,7 +15,7 @@ use wasmer_wasix_types::wasi::{Errno, ExitCode}; use crate::os::task::thread::WasiThreadError; use crate::syscalls::AsyncifyFuture; -use crate::{capture_snapshot, InstanceSnapshot, WasiEnv, WasiFunctionEnv, WasiThread}; +use crate::{capture_instance_snapshot, InstanceSnapshot, WasiEnv, WasiFunctionEnv, WasiThread}; pub use virtual_mio::waker::*; @@ -38,7 +39,8 @@ pub type WasmResumeTrigger = dyn FnOnce() -> Pin>, + /// The instance will be recycled back to this function when the WASM run has finished + #[derivative(Debug = "ignore")] + pub recycle: Option>, } /// Callback that will be invoked @@ -54,9 +59,21 @@ pub type TaskWasmRun = dyn FnOnce(TaskWasmRunProperties) + Send + 'static; /// Callback that will be invoked pub type TaskExecModule = dyn FnOnce(Module) + Send + 'static; +/// The properties passed to the task +#[derive(Debug)] +pub struct TaskWasmRecycleProperties { + pub env: WasiEnv, + pub memory: Memory, + pub store: Store, +} + +/// Callback that will be invoked +pub type TaskWasmRecycle = dyn FnOnce(TaskWasmRecycleProperties) + Send + 'static; + /// Represents a WASM task that will be executed on a dedicated thread pub struct TaskWasm<'a, 'b> { pub run: Box, + pub recycle: Option>, pub env: WasiEnv, pub module: Module, pub snapshot: Option<&'b InstanceSnapshot>, @@ -67,14 +84,19 @@ pub struct TaskWasm<'a, 'b> { impl<'a, 'b> TaskWasm<'a, 'b> { pub fn new(run: Box, env: WasiEnv, module: Module, update_layout: bool) -> Self { + let shared_memory = module.imports().memories().next().map(|a| *a.ty()); Self { run, env, module, snapshot: None, - spawn_type: SpawnMemoryType::CreateMemory, + spawn_type: match shared_memory { + Some(ty) => SpawnMemoryType::CreateMemoryOfType(ty), + None => SpawnMemoryType::CreateMemory, + }, trigger: None, update_layout, + recycle: None, } } @@ -83,6 +105,13 @@ impl<'a, 'b> TaskWasm<'a, 'b> { self } + pub fn with_optional_memory(mut self, spawn_type: Option>) -> Self { + if let Some(spawn_type) = spawn_type { + self.spawn_type = spawn_type; + } + self + } + pub fn with_snapshot(mut self, snapshot: &'b InstanceSnapshot) -> Self { self.snapshot.replace(snapshot); self @@ -92,6 +121,11 @@ impl<'a, 'b> TaskWasm<'a, 'b> { self.trigger.replace(trigger); self } + + pub fn with_recycle(mut self, trigger: Box) -> Self { + self.recycle.replace(trigger); + self + } } /// A task executor backed by a thread pool. @@ -312,7 +346,7 @@ impl dyn VirtualTaskManager { } } - let snapshot = capture_snapshot(&mut store.as_store_mut()); + let snapshot = capture_instance_snapshot(&mut store.as_store_mut()); let env = ctx.data(&store); let module = env.inner().module_clone(); let memory = env.inner().memory_clone(); diff --git a/lib/wasix/src/runtime/task_manager/tokio.rs b/lib/wasix/src/runtime/task_manager/tokio.rs index f23fe03f855..9f6b6752d64 100644 --- a/lib/wasix/src/runtime/task_manager/tokio.rs +++ b/lib/wasix/src/runtime/task_manager/tokio.rs @@ -147,6 +147,7 @@ impl VirtualTaskManager for TokioTaskManager { fn task_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError> { // Create the context on a new store let run = task.run; + let recycle = task.recycle; let (ctx, store) = WasiFunctionEnv::new_with_store( task.module, task.env, @@ -171,6 +172,7 @@ impl VirtualTaskManager for TokioTaskManager { ctx, store, trigger_result: Some(result), + recycle, }); }); }); @@ -186,6 +188,7 @@ impl VirtualTaskManager for TokioTaskManager { ctx, store, trigger_result: None, + recycle, }); }); } diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs index f3096f5a3fa..7a23c5e3712 100644 --- a/lib/wasix/src/state/builder.rs +++ b/lib/wasix/src/state/builder.rs @@ -6,13 +6,13 @@ use std::{ sync::Arc, }; -use bytes::Bytes; use rand::Rng; use thiserror::Error; use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile}; -use wasmer::{AsStoreMut, Instance, Module, RuntimeError, Store}; -use wasmer_wasix_types::wasi::{Errno, ExitCode}; +use wasmer::{AsStoreMut, Instance, Module, Store}; +#[cfg(feature = "journal")] +use crate::journal::{DynJournal, SnapshotTrigger}; #[cfg(feature = "sys")] use crate::PluggableRuntime; use crate::{ @@ -20,10 +20,13 @@ use crate::{ capabilities::Capabilities, fs::{WasiFs, WasiFsRoot, WasiInodes}, os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane}, - runtime::task_manager::InlineWaker, + runtime::module_cache::ModuleHash, state::WasiState, - syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}, - RewindState, Runtime, WasiEnv, WasiError, WasiFunctionEnv, WasiRuntimeError, + syscalls::{ + rewind_ext2, + types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}, + }, + Runtime, WasiEnv, WasiError, WasiFunctionEnv, WasiRuntimeError, }; use super::env::WasiEnvInit; @@ -67,10 +70,21 @@ pub struct WasiEnvBuilder { /// List of webc dependencies to be injected. pub(super) uses: Vec, + pub(super) module_hash: Option, + /// List of host commands to map into the WASI instance. pub(super) map_commands: HashMap, pub(super) capabilites: Capabilities, + + #[cfg(feature = "journal")] + pub(super) snapshot_on: Vec, + + #[cfg(feature = "journal")] + pub(super) snapshot_interval: Option, + + #[cfg(feature = "journal")] + pub(super) journals: Vec>, } impl std::fmt::Debug for WasiEnvBuilder { @@ -91,7 +105,7 @@ impl std::fmt::Debug for WasiEnvBuilder { } /// Error type returned when bad data is given to [`WasiEnvBuilder`]. -#[derive(Error, Debug, PartialEq, Eq)] +#[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum WasiStateCreationError { #[error("bad environment variable format: `{0}`")] EnvironmentVariableFormatError(String), @@ -281,6 +295,14 @@ impl WasiEnvBuilder { self } + /// Sets the module hash for the running process. This ensures that the journal + /// can restore the records for the right module. If no module hash is supplied + /// then the process will start with a random module hash. + pub fn set_module_hash(&mut self, hash: ModuleHash) -> &mut Self { + self.module_hash.replace(hash); + self + } + /// Adds a container this module inherits from. /// /// This will make all of the container's files and commands available to the @@ -496,6 +518,20 @@ impl WasiEnvBuilder { Ok(self) } + /// Specifies one or more journal files that Wasmer will use to restore + /// the state of the WASM process. + /// + /// The state of the WASM process and its sandbox will be reapplied use + /// the journals in the order that you specify here. + /// + /// The last journal file specified will be created if it does not exist + /// and opened for read and write. New journal events will be written to this + /// file + #[cfg(feature = "journal")] + pub fn add_journal(&mut self, journal: Arc) { + self.journals.push(journal); + } + pub fn set_current_dir(&mut self, dir: impl Into) { self.current_dir = Some(dir.into()); } @@ -598,6 +634,16 @@ impl WasiEnvBuilder { self.capabilites = capabilities; } + #[cfg(feature = "journal")] + pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) { + self.snapshot_on.push(on); + } + + #[cfg(feature = "journal")] + pub fn with_snapshot_interval(&mut self, interval: std::time::Duration) { + self.snapshot_interval.replace(interval); + } + /// Consumes the [`WasiEnvBuilder`] and produces a [`WasiEnvInit`], which /// can be used to construct a new [`WasiEnv`]. /// @@ -739,19 +785,6 @@ impl WasiEnvBuilder { wasi_fs.set_current_dir(s); } - let envs = self - .envs - .into_iter() - .map(|(key, value)| { - let mut env = Vec::with_capacity(key.len() + value.len() + 1); - env.extend_from_slice(key.as_bytes()); - env.push(b'='); - env.extend_from_slice(&value); - - env - }) - .collect(); - let state = WasiState { fs: wasi_fs, secret: rand::thread_rng().gen::<[u8; 32]>(), @@ -760,13 +793,19 @@ impl WasiEnvBuilder { preopen: self.vfs_preopens.clone(), futexs: Default::default(), clock_offset: Default::default(), - envs, + envs: std::sync::Mutex::new(conv_env_vars(self.envs)), }; let runtime = self.runtime.unwrap_or_else(|| { #[cfg(feature = "sys-thread")] { - Arc::new(PluggableRuntime::new(Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::default()))) + #[allow(unused_mut)] + let mut runtime = PluggableRuntime::new(Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::default())); + #[cfg(feature = "journal")] + for journal in self.journals.clone() { + runtime.add_journal(journal); + } + Arc::new(runtime) } #[cfg(not(feature = "sys-thread"))] @@ -799,9 +838,14 @@ impl WasiEnvBuilder { memory_ty: None, process: None, thread: None, + #[cfg(feature = "journal")] + call_initialize: self.journals.is_empty(), + #[cfg(not(feature = "journal"))] call_initialize: true, can_deep_sleep: false, extra_tracing: true, + #[cfg(feature = "journal")] + snapshot_on: self.snapshot_on, }; Ok(init) @@ -809,8 +853,9 @@ impl WasiEnvBuilder { #[allow(clippy::result_large_err)] pub fn build(self) -> Result { + let module_hash = self.module_hash.unwrap_or_else(ModuleHash::random); let init = self.build_init()?; - WasiEnv::from_init(init) + WasiEnv::from_init(init, module_hash) } /// Construct a [`WasiFunctionEnv`]. @@ -823,8 +868,9 @@ impl WasiEnvBuilder { self, store: &mut impl AsStoreMut, ) -> Result { + let module_hash = self.module_hash.unwrap_or_else(ModuleHash::random); let init = self.build_init()?; - let env = WasiEnv::from_init(init)?; + let env = WasiEnv::from_init(init, module_hash)?; let func_env = WasiFunctionEnv::new(store, env); Ok(func_env) } @@ -839,19 +885,44 @@ impl WasiEnvBuilder { self, module: Module, store: &mut impl AsStoreMut, + ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { + self.instantiate_ext(module, ModuleHash::random(), store) + } + + #[allow(clippy::result_large_err)] + pub fn instantiate_ext( + self, + module: Module, + module_hash: ModuleHash, + store: &mut impl AsStoreMut, ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { let init = self.build_init()?; - WasiEnv::instantiate(init, module, store) + WasiEnv::instantiate(init, module, module_hash, store) } #[allow(clippy::result_large_err)] pub fn run(self, module: Module) -> Result<(), WasiRuntimeError> { + self.run_ext(module, ModuleHash::random()) + } + + #[allow(clippy::result_large_err)] + pub fn run_ext(self, module: Module, module_hash: ModuleHash) -> Result<(), WasiRuntimeError> { let mut store = wasmer::Store::default(); - self.run_with_store(module, &mut store) + self.run_with_store_ext(module, module_hash, &mut store) } #[allow(clippy::result_large_err)] pub fn run_with_store(self, module: Module, store: &mut Store) -> Result<(), WasiRuntimeError> { + self.run_with_store_ext(module, ModuleHash::random(), store) + } + + #[allow(clippy::result_large_err)] + pub fn run_with_store_ext( + self, + module: Module, + module_hash: ModuleHash, + store: &mut Store, + ) -> Result<(), WasiRuntimeError> { // If no handle or runtime exists then create one #[cfg(feature = "sys-thread")] let _guard = if tokio::runtime::Handle::try_current().is_err() { @@ -872,13 +943,23 @@ impl WasiEnvBuilder { ); } - let (instance, env) = self.instantiate(module, store)?; + let (instance, env) = self.instantiate_ext(module, module_hash, store)?; + + // Bootstrap the process + // Unsafe: The bootstrap must be executed in the same thread that runs the + // actual WASM code + let rewind_state = unsafe { env.bootstrap(store)? }; + if rewind_state.is_some() { + let mut ctx = env.env.clone().into_mut(store); + rewind_ext2(&mut ctx, rewind_state) + .map_err(|exit| WasiRuntimeError::Wasi(WasiError::Exit(exit)))?; + } let start = instance.exports.get_function("_start")?; env.data(&store).thread.set_status_running(); let result = crate::run_wasi_func_start(start, store); - let (result, exit_code) = wasi_exit_code(result); + let (result, exit_code) = super::wasi_exit_code(result); let pid = env.data(&store).pid(); let tid = env.data(&store).tid(); @@ -890,7 +971,7 @@ impl WasiEnvBuilder { "main exit", ); - env.cleanup(store, Some(exit_code)); + env.on_exit(store, Some(exit_code)); result } @@ -900,178 +981,26 @@ impl WasiEnvBuilder { pub fn run_with_store_async( self, module: Module, + module_hash: ModuleHash, mut store: Store, ) -> Result<(), WasiRuntimeError> { - // If no handle or runtime exists then create one - #[cfg(feature = "sys-thread")] - let _guard = if tokio::runtime::Handle::try_current().is_err() { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - Some(runtime) - } else { - None - }; - #[cfg(feature = "sys-thread")] - let _guard = _guard.as_ref().map(|r| r.enter()); - - let (_, env) = self.instantiate(module, &mut store)?; - - env.data(&store).thread.set_status_running(); - - let tasks = env.data(&store).tasks().clone(); - let pid = env.data(&store).pid(); - let tid = env.data(&store).tid(); - - // The return value is passed synchronously and will block until the result - // is returned this is because the main thread can go into a deep sleep and - // exit the dedicated thread - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - - tasks.task_dedicated(Box::new(move || { - run_with_deep_sleep(store, None, env, tx); - }))?; - - let result = InlineWaker::block_on(rx.recv()); - let result = result.unwrap_or_else(|| { - Err(WasiRuntimeError::Runtime(RuntimeError::new( - "main thread terminated without a result, this normally means a panic occurred", - ))) - }); - let (result, exit_code) = wasi_exit_code(result); - - tracing::trace!( - %pid, - %tid, - %exit_code, - error=result.as_ref().err().map(|e| e as &dyn std::error::Error), - "main exit", - ); - - result - } -} - -/// Extract the exit code from a `Result<(), WasiRuntimeError>`. -/// -/// We need this because calling `exit(0)` inside a WASI program technically -/// triggers [`WasiError`] with an exit code of `0`, but the end user won't want -/// that treated as an error. -fn wasi_exit_code( - mut result: Result<(), WasiRuntimeError>, -) -> (Result<(), WasiRuntimeError>, ExitCode) { - let exit_code = match &result { - Ok(_) => Errno::Success.into(), - Err(err) => match err.as_exit_code() { - Some(code) if code.is_success() => { - // This is actually not an error, so we need to fix up the - // result - result = Ok(()); - Errno::Success.into() - } - Some(other) => other, - None => Errno::Noexec.into(), - }, - }; - - (result, exit_code) -} - -fn run_with_deep_sleep( - mut store: Store, - rewind_state: Option<(RewindState, Bytes)>, - env: WasiFunctionEnv, - sender: tokio::sync::mpsc::UnboundedSender>, -) { - if let Some((rewind_state, rewind_result)) = rewind_state { - tracing::trace!("Rewinding"); - let errno = if rewind_state.is_64bit { - crate::rewind_ext::( - env.env.clone().into_mut(&mut store), - rewind_state.memory_stack, - rewind_state.rewind_stack, - rewind_state.store_data, - rewind_result, - ) - } else { - crate::rewind_ext::( - env.env.clone().into_mut(&mut store), - rewind_state.memory_stack, - rewind_state.rewind_stack, - rewind_state.store_data, - rewind_result, - ) - }; - - if errno != Errno::Success { - let exit_code = ExitCode::from(errno); - env.cleanup(&mut store, Some(exit_code)); - let _ = sender.send(Err(WasiRuntimeError::Wasi(WasiError::Exit(exit_code)))); - return; - } + let (_, env) = self.instantiate_ext(module, module_hash, &mut store)?; + env.run_async(store)?; + Ok(()) } - - let instance = match env.data(&store).try_clone_instance() { - Some(instance) => instance, - None => { - tracing::debug!("Unable to clone the instance"); - env.cleanup(&mut store, None); - let _ = sender.send(Err(WasiRuntimeError::Wasi(WasiError::Exit( - Errno::Noexec.into(), - )))); - return; - } - }; - - let start = match instance.exports.get_function("_start") { - Ok(start) => start, - Err(e) => { - tracing::debug!("Unable to get the _start function"); - env.cleanup(&mut store, None); - let _ = sender.send(Err(e.into())); - return; - } - }; - - let result = start.call(&mut store, &[]); - handle_result(store, env, result, sender); } -fn handle_result( - mut store: Store, - env: WasiFunctionEnv, - result: Result, RuntimeError>, - sender: tokio::sync::mpsc::UnboundedSender>, -) { - let result = match result.map_err(|e| e.downcast::()) { - Err(Ok(WasiError::DeepSleep(work))) => { - let pid = env.data(&store).pid(); - let tid = env.data(&store).tid(); - tracing::trace!(%pid, %tid, "entered a deep sleep"); - - let tasks = env.data(&store).tasks().clone(); - let rewind = work.rewind; - let respawn = - move |ctx, store, res| run_with_deep_sleep(store, Some((rewind, res)), ctx, sender); - - // Spawns the WASM process after a trigger - unsafe { - tasks - .resume_wasm_after_poller(Box::new(respawn), env, store, work.trigger) - .unwrap(); - } +pub(crate) fn conv_env_vars(envs: Vec<(String, Vec)>) -> Vec> { + envs.into_iter() + .map(|(key, value)| { + let mut env = Vec::with_capacity(key.len() + value.len() + 1); + env.extend_from_slice(key.as_bytes()); + env.push(b'='); + env.extend_from_slice(&value); - return; - } - Ok(_) => Ok(()), - Err(Ok(other)) => Err(other.into()), - Err(Err(e)) => Err(e.into()), - }; - - let (result, exit_code) = wasi_exit_code(result); - env.cleanup(&mut store, Some(exit_code)); - sender.send(result).ok(); + env + }) + .collect() } /// Builder for preopened directories. diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 44ab7213f60..71c331dbf43 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "journal")] +use std::collections::HashSet; use std::{ collections::HashMap, ops::Deref, @@ -20,6 +22,8 @@ use wasmer_wasix_types::{ wasi::{Errno, ExitCode, Snapshot0Clockid}, }; +#[cfg(feature = "journal")] +use crate::journal::{DynJournal, JournalEffector, SnapshotTrigger}; use crate::{ bin_factory::{BinFactory, BinaryPackage}, capabilities::Capabilities, @@ -30,10 +34,13 @@ use crate::{ process::{WasiProcess, WasiProcessId}, thread::{WasiMemoryLayout, WasiThread, WasiThreadHandle, WasiThreadId}, }, - runtime::{resolver::PackageSpecifier, task_manager::InlineWaker, SpawnMemoryType}, + runtime::{ + module_cache::ModuleHash, resolver::PackageSpecifier, task_manager::InlineWaker, + SpawnMemoryType, + }, syscalls::platform_clock_time_get, Runtime, VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, WasiFunctionEnv, - WasiRuntimeError, WasiStateCreationError, WasiVFork, + WasiResult, WasiRuntimeError, WasiStateCreationError, WasiVFork, }; pub(crate) use super::handles::*; @@ -229,6 +236,10 @@ pub struct WasiEnvInit { /// Indicates if extra tracing should be output pub extra_tracing: bool, + + /// Indicates triggers that will cause a snapshot to be taken + #[cfg(feature = "journal")] + pub snapshot_on: Vec, } impl WasiEnvInit { @@ -250,7 +261,7 @@ impl WasiEnvInit { self.state.clock_offset.lock().unwrap().clone(), ), args: self.state.args.clone(), - envs: self.state.envs.clone(), + envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()), preopen: self.state.preopen.clone(), }, runtime: self.runtime.clone(), @@ -265,6 +276,8 @@ impl WasiEnvInit { call_initialize: self.call_initialize, can_deep_sleep: self.can_deep_sleep, extra_tracing: false, + #[cfg(feature = "journal")] + snapshot_on: self.snapshot_on.clone(), } } } @@ -298,6 +311,21 @@ pub struct WasiEnv { /// Is this environment capable and setup for deep sleeping pub enable_deep_sleep: bool, + /// Enables the snap shotting functionality + pub enable_journal: bool, + + /// Flag that indicatees if the environment is currently replaying the journal + /// (and hence it should not record new events) + pub replaying_journal: bool, + + /// Flag that indicates the cleanup of the environment is to be disabled + /// (this is normally used so that the instance can be reused later on) + pub(crate) disable_fs_cleanup: bool, + + /// List of situations that the process will checkpoint on + #[cfg(feature = "journal")] + snapshot_on: HashSet, + /// Inner functions and references that are loaded before the environment starts /// (inner is not safe to send between threads and so it is private and will /// not be cloned when `WasiEnv` is cloned) @@ -327,6 +355,11 @@ impl Clone for WasiEnv { runtime: self.runtime.clone(), capabilities: self.capabilities.clone(), enable_deep_sleep: self.enable_deep_sleep, + enable_journal: self.enable_journal, + replaying_journal: self.replaying_journal, + #[cfg(feature = "journal")] + snapshot_on: self.snapshot_on.clone(), + disable_fs_cleanup: self.disable_fs_cleanup, } } } @@ -339,8 +372,8 @@ impl WasiEnv { /// Forking the WasiState is used when either fork or vfork is called pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> { - let process = self.control_plane.new_process()?; - let handle = process.new_thread()?; + let process = self.control_plane.new_process(self.process.module_hash)?; + let handle = process.new_thread(self.layout.clone())?; let thread = handle.as_thread(); thread.copy_stack_from(&self.thread); @@ -363,6 +396,11 @@ impl WasiEnv { runtime: self.runtime.clone(), capabilities: self.capabilities.clone(), enable_deep_sleep: self.enable_deep_sleep, + enable_journal: self.enable_journal, + replaying_journal: false, + #[cfg(feature = "journal")] + snapshot_on: self.snapshot_on.clone(), + disable_fs_cleanup: self.disable_fs_cleanup, }; Ok((new_env, handle)) } @@ -375,6 +413,56 @@ impl WasiEnv { self.thread.tid() } + /// Re-initializes this environment so that it can be executed again + pub fn reinit(&mut self) -> Result<(), WasiStateCreationError> { + // If the cleanup logic is enabled then we need to rebuild the + // file descriptors which would have been destroyed when the + // main thread exited + if !self.disable_fs_cleanup { + // First we clear any open files as the descriptors would + // otherwise clash + if let Ok(mut map) = self.state.fs.fd_map.write() { + map.clear(); + } + self.state.fs.preopen_fds.write().unwrap().clear(); + self.state + .fs + .next_fd + .store(3, std::sync::atomic::Ordering::SeqCst); + *self.state.fs.current_dir.lock().unwrap() = "/".to_string(); + + // We need to rebuild the basic file descriptors + self.state.fs.create_stdin(&self.state.inodes); + self.state.fs.create_stdout(&self.state.inodes); + self.state.fs.create_stderr(&self.state.inodes); + self.state + .fs + .create_rootfd() + .map_err(WasiStateCreationError::WasiFsSetupError)?; + self.state + .fs + .create_preopens(&self.state.inodes, true) + .map_err(WasiStateCreationError::WasiFsSetupError)?; + } + + // The process and thread state need to be reset + self.process = WasiProcess::new( + self.process.pid, + self.process.module_hash, + self.process.compute.clone(), + ); + self.thread = WasiThread::new( + self.thread.pid(), + self.thread.tid(), + self.thread.is_main(), + self.process.finished.clone(), + self.process.compute.must_upgrade().register_task()?, + self.thread.memory_layout().clone(), + ); + + Ok(()) + } + /// Returns true if this module is capable of deep sleep /// (needs asyncify to unwind and rewin) /// @@ -398,32 +486,45 @@ impl WasiEnv { } #[allow(clippy::result_large_err)] - pub(crate) fn from_init(init: WasiEnvInit) -> Result { + pub(crate) fn from_init( + init: WasiEnvInit, + module_hash: ModuleHash, + ) -> Result { let process = if let Some(p) = init.process { p } else { - init.control_plane.new_process()? + init.control_plane.new_process(module_hash)? }; + + let layout = WasiMemoryLayout::default(); let thread = if let Some(t) = init.thread { t } else { - process.new_thread()? + process.new_thread(layout.clone())? }; let mut env = Self { control_plane: init.control_plane, process, thread: thread.as_thread(), - layout: WasiMemoryLayout::default(), + layout, vfork: None, poll_seed: 0, state: Arc::new(init.state), inner: Default::default(), owned_handles: Vec::new(), + #[cfg(feature = "journal")] + enable_journal: init.runtime.active_journal().is_some(), + #[cfg(not(feature = "journal"))] + enable_journal: false, + replaying_journal: false, + enable_deep_sleep: init.capabilities.threading.enable_asynchronous_threading, runtime: init.runtime, bin_factory: init.bin_factory, - enable_deep_sleep: init.capabilities.threading.enable_asynchronous_threading, capabilities: init.capabilities, + #[cfg(feature = "journal")] + snapshot_on: init.snapshot_on.into_iter().collect(), + disable_fs_cleanup: false, }; env.owned_handles.push(thread); @@ -443,6 +544,7 @@ impl WasiEnv { pub(crate) fn instantiate( mut init: WasiEnvInit, module: Module, + module_hash: ModuleHash, store: &mut impl AsStoreMut, ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { let call_initialize = init.call_initialize; @@ -454,8 +556,7 @@ impl WasiEnv { } } - let env = Self::from_init(init)?; - + let env = Self::from_init(init, module_hash)?; let pid = env.process.pid(); let mut store = store.as_store_mut(); @@ -499,7 +600,7 @@ impl WasiEnv { ); func_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_on_exit(Some(Errno::Noexec.into())); return Err(err.into()); } }; @@ -518,7 +619,7 @@ impl WasiEnv { ); func_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_on_exit(Some(Errno::Noexec.into())); return Err(err.into()); } @@ -528,7 +629,7 @@ impl WasiEnv { if let Err(err) = crate::run_wasi_func_start(initialize, &mut store) { func_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_on_exit(Some(Errno::Noexec.into())); return Err(err); } } @@ -565,9 +666,7 @@ impl WasiEnv { } /// Porcesses any signals that are batched up or any forced exit codes - pub(crate) fn process_signals_and_exit( - ctx: &mut FunctionEnvMut<'_, Self>, - ) -> Result, WasiError> { + pub(crate) fn process_signals_and_exit(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult { // If a signal handler has never been set then we need to handle signals // differently let env = ctx.data(); @@ -602,9 +701,7 @@ impl WasiEnv { } /// Porcesses any signals that are batched up - pub(crate) fn process_signals( - ctx: &mut FunctionEnvMut<'_, Self>, - ) -> Result, WasiError> { + pub(crate) fn process_signals(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult { // If a signal handler has never been set then we need to handle signals // differently let env = ctx.data(); @@ -638,29 +735,28 @@ impl WasiEnv { if let Some(handler) = inner.signal.clone() { // We might also have signals that trigger on timers let mut now = 0; - let has_signal_interval = { - let mut any = false; - let inner = env.process.inner.read().unwrap(); + { + let mut has_signal_interval = false; + let inner = env.process.inner.0.lock().unwrap(); if !inner.signal_intervals.is_empty() { now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() as u128; for signal in inner.signal_intervals.values() { let elapsed = now - signal.last_signal; if elapsed >= signal.interval.as_nanos() { - any = true; + has_signal_interval = true; break; } } } - any - }; - if has_signal_interval { - let mut inner = env.process.inner.write().unwrap(); - for signal in inner.signal_intervals.values_mut() { - let elapsed = now - signal.last_signal; - if elapsed >= signal.interval.as_nanos() { - signal.last_signal = now; - signals.push(signal.signal); + if has_signal_interval { + let mut inner = env.process.inner.0.lock().unwrap(); + for signal in inner.signal_intervals.values_mut() { + let elapsed = now - signal.last_signal; + if elapsed >= signal.interval.as_nanos() { + signal.last_signal = now; + signals.push(signal.signal); + } } } } @@ -682,11 +778,15 @@ impl WasiEnv { return Err(wasi_err); } Err(runtime_err) => { - tracing::warn!( - pid=%ctx.data().pid(), - runtime_err=&runtime_err as &dyn std::error::Error, - "signal handler runtime error", - ); + // anything other than a kill command should report + // the error, killed things may not gracefully close properly + if signal != Signal::Sigkill { + tracing::warn!( + pid=%ctx.data().pid(), + runtime_err=&runtime_err as &dyn std::error::Error, + "signal handler runtime error", + ); + } return Err(WasiError::Exit(Errno::Intr.into())); } } @@ -775,6 +875,16 @@ impl WasiEnv { self.try_inner().map(|i| i.memory()) } + /// Providers safe access to the memory + /// (it must be initialized before it can be used) + /// This has been marked as unsafe as it will panic if its executed + /// on the wrong thread or before the inner is set + pub(crate) unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> { + self.try_memory().expect( + "You must initialize the WasiEnv before using it and can not pass it between threads", + ) + } + /// Providers safe access to the memory /// (it must be initialized before it can be used) pub(crate) fn try_memory_view<'a>( @@ -824,6 +934,36 @@ impl WasiEnv { self.state.stdin() } + /// Returns true if the process should perform snapshots or not + pub fn should_journal(&self) -> bool { + self.enable_journal && !self.replaying_journal + } + + /// Returns the active journal or fails with an error + #[cfg(feature = "journal")] + pub fn active_journal(&self) -> Result<&DynJournal, Errno> { + self.runtime().active_journal().ok_or_else(|| { + tracing::warn!("failed to save thread exit as there is not active journal"); + Errno::Fault + }) + } + + /// Returns true if a particular snapshot trigger is enabled + #[cfg(feature = "journal")] + pub fn has_snapshot_trigger(&self, trigger: SnapshotTrigger) -> bool { + self.snapshot_on.contains(&trigger) + } + + /// Returns true if a particular snapshot trigger is enabled + #[cfg(feature = "journal")] + pub fn pop_snapshot_trigger(&mut self, trigger: SnapshotTrigger) -> bool { + if trigger.only_once() { + self.snapshot_on.remove(&trigger) + } else { + self.snapshot_on.contains(&trigger) + } + } + /// Internal helper function to get a standard device handle. /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. pub fn std_dev_get( @@ -1030,37 +1170,55 @@ impl WasiEnv { /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub fn blocking_cleanup(&self, exit_code: Option) { - let cleanup = self.cleanup(exit_code); + pub fn blocking_on_exit(&self, exit_code: Option) { + let cleanup = self.on_exit(exit_code); InlineWaker::block_on(cleanup); } /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub fn cleanup(&self, exit_code: Option) -> BoxFuture<'static, ()> { + pub fn on_exit(&self, exit_code: Option) -> BoxFuture<'static, ()> { const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); + // If snap-shooting is enabled then we should record an event that the thread has exited. + #[cfg(feature = "journal")] + if self.should_journal() { + if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), exit_code) { + tracing::warn!("failed to save snapshot event for thread exit - {}", err); + } + + if self.thread.is_main() { + if let Err(err) = JournalEffector::save_process_exit(self, exit_code) { + tracing::warn!("failed to save snapshot event for process exit - {}", err); + } + } + } + // If this is the main thread then also close all the files if self.thread.is_main() { - tracing::trace!(pid=%self.pid(), "cleaning up open file handles"); - let process = self.process.clone(); + let disable_fs_cleanup = self.disable_fs_cleanup; + let pid = self.pid(); let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT); let state = self.state.clone(); Box::pin(async move { - // Perform the clean operation using the asynchronous runtime - tokio::select! { - _ = timeout => { - tracing::debug!( - "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}" - ); - }, - _ = state.fs.close_all() => { } - } + if !disable_fs_cleanup { + tracing::trace!(pid = %pid, "cleaning up open file handles"); - // Now send a signal that the thread is terminated - process.signal_process(Signal::Sigquit); + // Perform the clean operation using the asynchronous runtime + tokio::select! { + _ = timeout => { + tracing::debug!( + "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}" + ); + }, + _ = state.fs.close_all() => { } + } + + // Now send a signal that the thread is terminated + process.signal_process(Signal::Sigquit); + } // Terminate the process let exit_code = exit_code.unwrap_or_else(|| Errno::Canceled.into()); diff --git a/lib/wasix/src/state/func_env.rs b/lib/wasix/src/state/func_env.rs index 7ae4258be2b..cff6ed1918a 100644 --- a/lib/wasix/src/state/func_env.rs +++ b/lib/wasix/src/state/func_env.rs @@ -1,15 +1,19 @@ +use std::sync::Arc; + use tracing::trace; use wasmer::{ AsStoreMut, AsStoreRef, ExportError, FunctionEnv, Imports, Instance, Memory, Module, Store, }; use wasmer_wasix_types::wasi::ExitCode; +#[cfg(feature = "journal")] +use crate::syscalls::restore_snapshot; use crate::{ import_object_for_all_wasi_versions, runtime::SpawnMemoryType, state::WasiInstanceHandles, - utils::{get_wasi_version, get_wasi_versions, store::restore_snapshot}, - InstanceSnapshot, WasiEnv, WasiError, WasiThreadError, + utils::{get_wasi_version, get_wasi_versions, store::restore_instance_snapshot}, + InstanceSnapshot, RewindStateOption, WasiEnv, WasiError, WasiRuntimeError, WasiThreadError, }; /// The default stack size for WASIX - the number itself is the default that compilers @@ -61,7 +65,7 @@ impl WasiFunctionEnv { init(&instance, &store).map_err(|err| { tracing::warn!("failed to init instance - {}", err); - WasiThreadError::InitFailed(err) + WasiThreadError::InitFailed(Arc::new(err)) })?; // Initialize the WASI environment @@ -74,7 +78,7 @@ impl WasiFunctionEnv { // Set all the globals if let Some(snapshot) = snapshot { tracing::trace!("restoring snapshot for new thread"); - restore_snapshot(&mut store, snapshot); + restore_instance_snapshot(&mut store, snapshot); } Ok((ctx, store)) @@ -170,9 +174,23 @@ impl WasiFunctionEnv { // Update the stack layout which is need for asyncify let env = self.data_mut(store); + let tid = env.tid(); let layout = &mut env.layout; layout.stack_upper = stack_base; layout.stack_size = layout.stack_upper - layout.stack_lower; + + // Replace the thread object itself + env.thread.set_memory_layout(layout.clone()); + + // Replace the thread object with this new layout + { + let mut guard = env.process.lock(); + guard + .threads + .values_mut() + .filter(|t| t.tid() == tid) + .for_each(|t| t.set_memory_layout(layout.clone())) + } } tracing::trace!("initializing with layout {:?}", self.data(store).layout); @@ -206,14 +224,74 @@ impl WasiFunctionEnv { /// This function should only be called from within a syscall /// as it can potentially execute local thread variable cleanup /// code - pub fn cleanup(&self, store: &mut impl AsStoreMut, exit_code: Option) { + pub fn on_exit(&self, store: &mut impl AsStoreMut, exit_code: Option) { trace!( - "wasi[{}:{}]::cleanup", + "wasi[{}:{}]::on_exit", self.data(store).pid(), self.data(store).tid() ); // Cleans up all the open files (if this is the main thread) - self.data(store).blocking_cleanup(exit_code); + self.data(store).blocking_on_exit(exit_code); + } + + /// Bootstraps this main thread and context with any journals that + /// may be present + /// + /// # Safety + /// + /// This function manipulates the memory of the process and thus must be executed + /// by the WASM process thread itself. + /// + #[allow(clippy::result_large_err)] + #[allow(unused_variables, unused_mut)] + pub unsafe fn bootstrap( + &self, + mut store: &'_ mut impl AsStoreMut, + ) -> Result { + #[allow(unused_mut)] + let mut rewind_state = None; + + #[cfg(feature = "journal")] + { + // If there are journals we need to restore then do so (this will + // prevent the initialization function from running + let restore_journals = self.data(&store).runtime.journals().clone(); + if !restore_journals.is_empty() { + self.data_mut(&mut store).replaying_journal = true; + + for journal in restore_journals { + let ctx = self.env.clone().into_mut(&mut store); + let rewind = match restore_snapshot(ctx, journal, true) { + Ok(r) => r, + Err(err) => { + self.data_mut(&mut store).replaying_journal = false; + return Err(err); + } + }; + rewind_state = rewind.map(|rewind| (rewind, None)); + } + + self.data_mut(&mut store).replaying_journal = false; + } + + // The first event we save is an event that records the module hash. + // Note: This is used to detect if an incorrect journal is used on the wrong + // process or if a process has been recompiled + let wasm_hash = self.data(&store).process.module_hash.as_bytes(); + let mut ctx = self.env.clone().into_mut(&mut store); + crate::journal::JournalEffector::save_event( + &mut ctx, + crate::journal::JournalEntry::InitModuleV1 { wasm_hash }, + ) + .map_err(|err| { + WasiRuntimeError::Runtime(wasmer::RuntimeError::new(format!( + "journal failied to save the module initialization event - {}", + err + ))) + })?; + } + + Ok(rewind_state) } } diff --git a/lib/wasix/src/state/mod.rs b/lib/wasix/src/state/mod.rs index e18dac9fbbd..aa8f6dc91a9 100644 --- a/lib/wasix/src/state/mod.rs +++ b/lib/wasix/src/state/mod.rs @@ -19,6 +19,7 @@ mod builder; mod env; mod func_env; mod handles; +mod run; mod types; use std::{ @@ -29,6 +30,7 @@ use std::{ time::Duration, }; +use run::*; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use virtual_fs::{FileOpener, FileSystem, FsError, OpenOptions, VirtualFile}; @@ -131,7 +133,7 @@ pub(crate) struct WasiState { pub futexs: Mutex, pub clock_offset: Mutex>, pub args: Vec, - pub envs: Vec>, + pub envs: Mutex>>, // TODO: should not be here, since this requires active work to resolve. // State should only hold active runtime state that can be reproducibly re-created. @@ -253,7 +255,7 @@ impl WasiState { futexs: Default::default(), clock_offset: Mutex::new(self.clock_offset.lock().unwrap().clone()), args: self.args.clone(), - envs: self.envs.clone(), + envs: Mutex::new(self.envs.lock().unwrap().clone()), preopen: self.preopen.clone(), } } diff --git a/lib/wasix/src/state/run.rs b/lib/wasix/src/state/run.rs new file mode 100644 index 00000000000..45f450d6d5e --- /dev/null +++ b/lib/wasix/src/state/run.rs @@ -0,0 +1,205 @@ +use virtual_mio::InlineWaker; +use wasmer::{RuntimeError, Store}; +use wasmer_wasix_types::wasi::ExitCode; + +use crate::{RewindStateOption, WasiError, WasiRuntimeError}; + +use super::*; + +impl WasiFunctionEnv { + #[allow(clippy::result_large_err)] + pub fn run_async(self, mut store: Store) -> Result<(Self, Store), WasiRuntimeError> { + // If no handle or runtime exists then create one + #[cfg(feature = "sys-thread")] + let _guard = if tokio::runtime::Handle::try_current().is_err() { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + Some(runtime) + } else { + None + }; + #[cfg(feature = "sys-thread")] + let _guard = _guard.as_ref().map(|r| r.enter()); + + self.data(&store).thread.set_status_running(); + + let tasks = self.data(&store).tasks().clone(); + let pid = self.data(&store).pid(); + let tid = self.data(&store).tid(); + + // The return value is passed synchronously and will block until the result + // is returned this is because the main thread can go into a deep sleep and + // exit the dedicated thread + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + + let this = self.clone(); + tasks.task_dedicated(Box::new(move || { + // Unsafe: The bootstrap must be executed in the same thread that runs the + // actual WASM code + let rewind_state = unsafe { + match this.bootstrap(&mut store) { + Ok(a) => a, + Err(err) => { + this.on_exit(&mut store, None); + tx.send(Err(err)).ok(); + return; + } + } + }; + run_with_deep_sleep(store, rewind_state, this, tx); + }))?; + + let result = InlineWaker::block_on(rx.recv()); + let store = match result { + Some(result) => { + tracing::trace!( + %pid, + %tid, + error=result.as_ref().err().map(|e| e as &dyn std::error::Error), + "main exit", + ); + result? + } + None => { + tracing::trace!( + %pid, + %tid, + "main premature termination", + ); + return Err(WasiRuntimeError::Runtime(RuntimeError::new( + "main thread terminated without a result, this normally means a panic occurred", + ))); + } + }; + Ok((self, store)) + } +} + +fn run_with_deep_sleep( + mut store: Store, + rewind_state: RewindStateOption, + env: WasiFunctionEnv, + sender: tokio::sync::mpsc::UnboundedSender>, +) { + if let Some((rewind_state, rewind_result)) = rewind_state { + tracing::trace!("Rewinding"); + let mut ctx = env.env.clone().into_mut(&mut store); + let errno = if rewind_state.is_64bit { + crate::rewind_ext::( + &mut ctx, + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + rewind_result, + ) + } else { + crate::rewind_ext::( + &mut ctx, + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + rewind_result, + ) + }; + + if errno != Errno::Success { + let exit_code = ExitCode::from(errno); + env.on_exit(&mut store, Some(exit_code)); + if exit_code.is_success() { + let _ = sender.send(Ok(store)); + } else { + let _ = sender.send(Err(WasiRuntimeError::Wasi(WasiError::Exit(exit_code)))); + } + return; + } + } + + let instance = match env.data(&store).try_clone_instance() { + Some(instance) => instance, + None => { + tracing::debug!("Unable to clone the instance"); + env.on_exit(&mut store, None); + let _ = sender.send(Err(WasiRuntimeError::Wasi(WasiError::Exit( + Errno::Noexec.into(), + )))); + return; + } + }; + + let start = match instance.exports.get_function("_start") { + Ok(start) => start, + Err(e) => { + tracing::debug!("Unable to get the _start function"); + env.on_exit(&mut store, None); + let _ = sender.send(Err(e.into())); + return; + } + }; + + let result = start.call(&mut store, &[]); + handle_result(store, env, result, sender); +} + +fn handle_result( + mut store: Store, + env: WasiFunctionEnv, + result: Result, RuntimeError>, + sender: tokio::sync::mpsc::UnboundedSender>, +) { + let result: Result<_, WasiRuntimeError> = match result.map_err(|e| e.downcast::()) { + Err(Ok(WasiError::DeepSleep(work))) => { + let pid = env.data(&store).pid(); + let tid = env.data(&store).tid(); + tracing::trace!(%pid, %tid, "entered a deep sleep"); + + let tasks = env.data(&store).tasks().clone(); + let rewind = work.rewind; + let respawn = move |ctx, store, res| { + run_with_deep_sleep(store, Some((rewind, Some(res))), ctx, sender) + }; + + // Spawns the WASM process after a trigger + unsafe { + tasks + .resume_wasm_after_poller(Box::new(respawn), env, store, work.trigger) + .unwrap(); + } + + return; + } + Ok(_) => Ok(()), + Err(Ok(other)) => Err(other.into()), + Err(Err(e)) => Err(e.into()), + }; + + let (result, exit_code) = wasi_exit_code(result); + env.on_exit(&mut store, Some(exit_code)); + sender.send(result.map(|_| store)).ok(); +} + +/// Extract the exit code from a `Result<(), WasiRuntimeError>`. +/// +/// We need this because calling `exit(0)` inside a WASI program technically +/// triggers [`WasiError`] with an exit code of `0`, but the end user won't want +/// that treated as an error. +pub(super) fn wasi_exit_code( + mut result: Result<(), WasiRuntimeError>, +) -> (Result<(), WasiRuntimeError>, ExitCode) { + let exit_code = match &result { + Ok(_) => Errno::Success.into(), + Err(err) => match err.as_exit_code() { + Some(code) if code.is_success() => { + // This is actually not an error, so we need to fix up the + // result + result = Ok(()); + Errno::Success.into() + } + Some(other) => other, + None => Errno::Noexec.into(), + }, + }; + + (result, exit_code) +} diff --git a/lib/wasix/src/syscalls/journal.rs b/lib/wasix/src/syscalls/journal.rs new file mode 100644 index 00000000000..c2040111291 --- /dev/null +++ b/lib/wasix/src/syscalls/journal.rs @@ -0,0 +1,665 @@ +use super::*; + +#[allow(clippy::extra_unused_type_parameters)] +#[cfg(not(feature = "journal"))] +pub fn maybe_snapshot_once( + ctx: FunctionEnvMut<'_, WasiEnv>, + _trigger: crate::journal::SnapshotTrigger, +) -> WasiResult> { + Ok(Ok(ctx)) +} + +#[cfg(feature = "journal")] +pub fn maybe_snapshot_once( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + trigger: crate::journal::SnapshotTrigger, +) -> WasiResult> { + use crate::os::task::process::{WasiProcessCheckpoint, WasiProcessInner}; + + unsafe { handle_rewind_ext::(&mut ctx, HandleRewindType::Resultless) }; + + if !ctx.data().enable_journal { + return Ok(Ok(ctx)); + } + + if ctx.data_mut().pop_snapshot_trigger(trigger) { + let inner = ctx.data().process.inner.clone(); + let res = wasi_try_ok_ok!(WasiProcessInner::checkpoint::( + inner, + ctx, + WasiProcessCheckpoint::Snapshot { trigger }, + )?); + match res { + MaybeCheckpointResult::Unwinding => return Ok(Err(Errno::Success)), + MaybeCheckpointResult::NotThisTime(c) => { + ctx = c; + } + } + } + Ok(Ok(ctx)) +} + +#[allow(clippy::extra_unused_type_parameters)] +#[cfg(not(feature = "journal"))] +pub fn maybe_snapshot( + ctx: FunctionEnvMut<'_, WasiEnv>, +) -> WasiResult> { + Ok(Ok(ctx)) +} + +#[cfg(feature = "journal")] +pub fn maybe_snapshot( + mut ctx: FunctionEnvMut<'_, WasiEnv>, +) -> WasiResult> { + use crate::os::task::process::{WasiProcessCheckpoint, WasiProcessInner}; + + if !ctx.data().enable_journal { + return Ok(Ok(ctx)); + } + + let inner = ctx.data().process.inner.clone(); + let res = wasi_try_ok_ok!(WasiProcessInner::maybe_checkpoint::(inner, ctx)?); + match res { + MaybeCheckpointResult::Unwinding => return Ok(Err(Errno::Success)), + MaybeCheckpointResult::NotThisTime(c) => { + ctx = c; + } + } + Ok(Ok(ctx)) +} + +/// Safety: This function manipulates the memory of the process and thus must +/// be executed by the WASM process thread itself. +/// +#[allow(clippy::result_large_err)] +#[cfg(feature = "journal")] +pub unsafe fn restore_snapshot( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + journal: Arc, + bootstrapping: bool, +) -> Result, WasiRuntimeError> { + use std::ops::Range; + + use crate::journal::Journal; + + // We delay the spawning of threads until the end as its + // possible that the threads will be cancelled before all the + // events finished the streaming process + let mut spawn_threads: HashMap = Default::default(); + + // We delay the memory updates until the end as its possible the + // memory will be cleared before all the events finished the + // streaming process + let mut update_memory: HashMap, Cow<'_, [u8]>> = Default::default(); + let mut update_tty = None; + + // We capture the stdout and stderr while we replay + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let mut stdout_fds = HashSet::new(); + let mut stderr_fds = HashSet::new(); + stdout_fds.insert(1 as WasiFd); + stderr_fds.insert(2 as WasiFd); + + // Loop through all the events and process them + let cur_module_hash = Some(ctx.data().process.module_hash.as_bytes()); + let mut journal_module_hash = None; + let mut rewind = None; + while let Some(next) = journal.read().map_err(anyhow_err_to_runtime_err)? { + tracing::trace!("Restoring snapshot event - {next:?}"); + match next { + crate::journal::JournalEntry::InitModuleV1 { wasm_hash } => { + journal_module_hash.replace(wasm_hash); + } + crate::journal::JournalEntry::ProcessExitV1 { exit_code } => { + if bootstrapping { + rewind = None; + spawn_threads.clear(); + update_memory.clear(); + update_tty.take(); + stdout.clear(); + stderr.clear(); + stdout_fds.clear(); + stderr_fds.clear(); + stdout_fds.insert(1 as WasiFd); + stderr_fds.insert(2 as WasiFd); + } else { + JournalEffector::apply_process_exit(&mut ctx, exit_code) + .map_err(anyhow_err_to_runtime_err)?; + } + } + crate::journal::JournalEntry::FileDescriptorWriteV1 { + fd, + offset, + data, + is_64bit, + } => { + if stdout_fds.contains(&fd) { + stdout.push((offset, data, is_64bit)); + continue; + } + if stderr_fds.contains(&fd) { + stderr.push((offset, data, is_64bit)); + continue; + } + + if is_64bit { + JournalEffector::apply_fd_write::(&ctx, fd, offset, data) + } else { + JournalEffector::apply_fd_write::(&ctx, fd, offset, data) + } + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorSeekV1 { fd, offset, whence } => { + JournalEffector::apply_fd_seek(&mut ctx, fd, offset, whence) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::UpdateMemoryRegionV1 { region, data } => { + if cur_module_hash != journal_module_hash { + continue; + } + + if bootstrapping { + update_memory.insert(region, data.clone()); + } else { + JournalEffector::apply_memory(&mut ctx, region, &data) + .map_err(anyhow_err_to_runtime_err)?; + } + } + crate::journal::JournalEntry::CloseThreadV1 { id, exit_code } => { + if id == ctx.data().tid().raw() { + if bootstrapping { + rewind = None; + spawn_threads.clear(); + update_memory.clear(); + update_tty.take(); + stdout.clear(); + stderr.clear(); + stdout_fds.clear(); + stderr_fds.clear(); + stdout_fds.insert(1 as WasiFd); + stderr_fds.insert(2 as WasiFd); + } else { + JournalEffector::apply_process_exit(&mut ctx, exit_code) + .map_err(anyhow_err_to_runtime_err)?; + } + } else if bootstrapping { + spawn_threads.remove(&Into::::into(id)); + } else { + JournalEffector::apply_thread_exit( + &mut ctx, + Into::::into(id), + exit_code, + ) + .map_err(anyhow_err_to_runtime_err)?; + } + } + crate::journal::JournalEntry::SetThreadV1 { + id, + call_stack, + memory_stack, + store_data, + is_64bit, + } => { + if cur_module_hash != journal_module_hash { + continue; + } + + let state = RewindState { + memory_stack: memory_stack.to_vec().into(), + rewind_stack: call_stack.to_vec().into(), + store_data: store_data.to_vec().into(), + is_64bit, + }; + + let id = Into::::into(id); + if id == ctx.data().tid() { + rewind.replace(state); + } else if bootstrapping { + spawn_threads.insert(id, state); + } else { + return Err(WasiRuntimeError::Runtime(RuntimeError::user( + anyhow::format_err!( + "Snapshot restoration does not currently support live updates of running threads." + ) + .into(), + ))); + } + } + crate::journal::JournalEntry::CloseFileDescriptorV1 { fd } => { + stdout_fds.remove(&fd); + stderr_fds.remove(&fd); + JournalEffector::apply_fd_close(&mut ctx, fd).map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::OpenFileDescriptorV1 { + fd, + dirfd, + dirflags, + path, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + } => { + JournalEffector::apply_path_open( + &mut ctx, + fd, + dirfd, + dirflags, + &path, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + ) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::RemoveDirectoryV1 { fd, path } => { + JournalEffector::apply_path_remove_directory(&mut ctx, fd, &path) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::UnlinkFileV1 { fd, path } => { + JournalEffector::apply_path_unlink(&mut ctx, fd, &path) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::PathRenameV1 { + old_fd, + old_path, + new_fd, + new_path, + } => { + JournalEffector::apply_path_rename(&mut ctx, old_fd, &old_path, new_fd, &new_path) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::SnapshotV1 { when: _, trigger } => { + if cur_module_hash != journal_module_hash { + continue; + } + ctx.data_mut().pop_snapshot_trigger(trigger); + } + crate::journal::JournalEntry::SetClockTimeV1 { clock_id, time } => { + JournalEffector::apply_clock_time_set(&mut ctx, clock_id, time) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::RenumberFileDescriptorV1 { old_fd, new_fd } => { + if old_fd != new_fd { + stdout_fds.remove(&new_fd); + stderr_fds.remove(&new_fd); + } + if stdout_fds.remove(&old_fd) { + stdout_fds.insert(new_fd); + } + if stderr_fds.remove(&old_fd) { + stderr_fds.insert(new_fd); + } + JournalEffector::apply_fd_renumber(&mut ctx, old_fd, new_fd) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::DuplicateFileDescriptorV1 { + original_fd, + copied_fd, + } => { + if original_fd != copied_fd { + stdout_fds.remove(&copied_fd); + stderr_fds.remove(&copied_fd); + } + if stdout_fds.contains(&original_fd) { + stdout_fds.insert(copied_fd); + } + if stderr_fds.contains(&original_fd) { + stderr_fds.insert(copied_fd); + } + JournalEffector::apply_fd_duplicate(&mut ctx, original_fd, copied_fd) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::CreateDirectoryV1 { fd, path } => { + JournalEffector::apply_path_create_directory(&mut ctx, fd, &path) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::PathSetTimesV1 { + fd, + flags, + path, + st_atim, + st_mtim, + fst_flags, + } => { + JournalEffector::apply_path_set_times( + &mut ctx, fd, flags, &path, st_atim, st_mtim, fst_flags, + ) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorSetTimesV1 { + fd, + st_atim, + st_mtim, + fst_flags, + } => { + JournalEffector::apply_fd_set_times(&mut ctx, fd, st_atim, st_mtim, fst_flags) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorSetSizeV1 { fd, st_size } => { + JournalEffector::apply_fd_set_size(&mut ctx, fd, st_size) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorSetFlagsV1 { fd, flags } => { + JournalEffector::apply_fd_set_flags(&mut ctx, fd, flags) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorSetRightsV1 { + fd, + fs_rights_base, + fs_rights_inheriting, + } => { + JournalEffector::apply_fd_set_rights( + &mut ctx, + fd, + fs_rights_base, + fs_rights_inheriting, + ) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorAdviseV1 { + fd, + offset, + len, + advice, + } => { + JournalEffector::apply_fd_advise(&mut ctx, fd, offset, len, advice) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::FileDescriptorAllocateV1 { fd, offset, len } => { + JournalEffector::apply_fd_allocate(&mut ctx, fd, offset, len) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::CreateHardLinkV1 { + old_fd, + old_path, + old_flags, + new_fd, + new_path, + } => { + JournalEffector::apply_path_link( + &mut ctx, old_fd, old_flags, &old_path, new_fd, &new_path, + ) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::CreateSymbolicLinkV1 { + old_path, + fd, + new_path, + } => { + JournalEffector::apply_path_symlink(&mut ctx, &old_path, fd, &new_path) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::ChangeDirectoryV1 { path } => { + JournalEffector::apply_chdir(&mut ctx, &path).map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::CreatePipeV1 { fd1, fd2 } => { + JournalEffector::apply_fd_pipe(&mut ctx, fd1, fd2) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::EpollCreateV1 { fd } => { + JournalEffector::apply_epoll_create(&mut ctx, fd) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::EpollCtlV1 { + epfd, + op, + fd, + event, + } => { + JournalEffector::apply_epoll_ctl(&mut ctx, epfd, op, fd, event) + .map_err(anyhow_err_to_runtime_err)?; + } + crate::journal::JournalEntry::TtySetV1 { tty, line_feeds } => { + let state = crate::WasiTtyState { + cols: tty.cols, + rows: tty.rows, + width: tty.width, + height: tty.height, + stdin_tty: tty.stdin_tty, + stdout_tty: tty.stdout_tty, + stderr_tty: tty.stderr_tty, + echo: tty.echo, + line_buffered: tty.line_buffered, + line_feeds, + }; + + if bootstrapping { + update_tty.replace(state); + } else { + JournalEffector::apply_tty_set(&mut ctx, state) + .map_err(anyhow_err_to_runtime_err)?; + } + } + crate::journal::JournalEntry::PortAddAddrV1 { cidr } => { + JournalEffector::apply_port_addr_add(&mut ctx, cidr) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortDelAddrV1 { addr } => { + JournalEffector::apply_port_addr_remove(&mut ctx, addr) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortAddrClearV1 => { + JournalEffector::apply_port_addr_clear(&mut ctx) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortBridgeV1 { + network, + token, + security, + } => JournalEffector::apply_port_bridge(&mut ctx, &network, &token, security) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::PortUnbridgeV1 => { + JournalEffector::apply_port_unbridge(&mut ctx).map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortDhcpAcquireV1 => { + JournalEffector::apply_port_dhcp_acquire(&mut ctx) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortGatewaySetV1 { ip } => { + JournalEffector::apply_port_gateway_set(&mut ctx, ip) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortRouteAddV1 { + cidr, + via_router, + preferred_until, + expires_at, + } => JournalEffector::apply_port_route_add( + &mut ctx, + cidr, + via_router, + preferred_until, + expires_at, + ) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::PortRouteClearV1 => { + JournalEffector::apply_port_route_clear(&mut ctx) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::PortRouteDelV1 { ip } => { + JournalEffector::apply_port_route_remove(&mut ctx, ip) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketOpenV1 { af, ty, pt, fd } => { + JournalEffector::apply_sock_open(&mut ctx, af, ty, pt, fd) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketListenV1 { fd, backlog } => { + JournalEffector::apply_sock_listen(&mut ctx, fd, backlog as usize) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketBindV1 { fd, addr } => { + JournalEffector::apply_sock_bind(&mut ctx, fd, addr) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketConnectedV1 { fd, addr } => { + JournalEffector::apply_sock_connect(&mut ctx, fd, addr) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketAcceptedV1 { + listen_fd, + fd, + peer_addr, + fd_flags, + non_blocking: nonblocking, + } => JournalEffector::apply_sock_accepted( + &mut ctx, + listen_fd, + fd, + peer_addr, + fd_flags, + nonblocking, + ) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketJoinIpv4MulticastV1 { + fd, + multiaddr, + iface, + } => JournalEffector::apply_sock_join_ipv4_multicast(&mut ctx, fd, multiaddr, iface) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketJoinIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => JournalEffector::apply_sock_join_ipv6_multicast(&mut ctx, fd, multiaddr, iface) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketLeaveIpv4MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => JournalEffector::apply_sock_leave_ipv4_multicast(&mut ctx, fd, multiaddr, iface) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketLeaveIpv6MulticastV1 { + fd, + multi_addr: multiaddr, + iface, + } => JournalEffector::apply_sock_leave_ipv6_multicast(&mut ctx, fd, multiaddr, iface) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketSendFileV1 { + socket_fd, + file_fd, + offset, + count, + } => JournalEffector::apply_sock_send_file(&mut ctx, socket_fd, file_fd, offset, count) + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketSendToV1 { + fd, + data, + flags, + addr, + is_64bit, + } => if is_64bit { + JournalEffector::apply_sock_send_to::(&ctx, fd, data, flags, addr) + } else { + JournalEffector::apply_sock_send_to::(&ctx, fd, data, flags, addr) + } + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketSendV1 { + fd, + data, + flags, + is_64bit, + } => if is_64bit { + JournalEffector::apply_sock_send::(&ctx, fd, data, flags) + } else { + JournalEffector::apply_sock_send::(&ctx, fd, data, flags) + } + .map_err(anyhow_err_to_runtime_err)?, + crate::journal::JournalEntry::SocketSetOptFlagV1 { fd, opt, flag } => { + JournalEffector::apply_sock_set_opt_flag(&mut ctx, fd, opt, flag) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketSetOptSizeV1 { fd, opt, size } => { + JournalEffector::apply_sock_set_opt_size(&mut ctx, fd, opt, size) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketSetOptTimeV1 { fd, ty, time } => { + JournalEffector::apply_sock_set_opt_time(&mut ctx, fd, ty.into(), time) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::SocketShutdownV1 { fd, how } => { + JournalEffector::apply_sock_shutdown(&mut ctx, fd, how.into()) + .map_err(anyhow_err_to_runtime_err)? + } + crate::journal::JournalEntry::CreateEventV1 { + initial_val, + flags, + fd, + } => JournalEffector::apply_fd_event(&mut ctx, initial_val, flags, fd) + .map_err(anyhow_err_to_runtime_err)?, + } + } + + // If we are not in the same module then we fire off an exit + // that simulates closing the process (hence keeps everything + // in a clean state) + if journal_module_hash.is_some() && cur_module_hash != journal_module_hash { + tracing::error!( + "The WASM module hash does not match the journal module hash (journal_hash={:x?} vs module_hash{:x?}) - forcing a restart", + journal_module_hash.unwrap(), + cur_module_hash.unwrap() + ); + if bootstrapping { + rewind = None; + spawn_threads.clear(); + update_memory.clear(); + update_tty.take(); + stdout.clear(); + stderr.clear(); + stdout_fds.clear(); + stderr_fds.clear(); + stdout_fds.insert(1 as WasiFd); + stderr_fds.insert(2 as WasiFd); + } else { + JournalEffector::apply_process_exit(&mut ctx, None) + .map_err(anyhow_err_to_runtime_err)?; + } + } else { + tracing::debug!( + "journal used on a different module - the process will simulate a restart." + ); + } + + // We do not yet support multi threading + if !spawn_threads.is_empty() { + return Err(WasiRuntimeError::Runtime(RuntimeError::user( + anyhow::format_err!( + "Snapshot restoration does not currently support multiple threads." + ) + .into(), + ))); + } + + // Now output the stdout and stderr + for (offset, data, is_64bit) in stdout { + if is_64bit { + JournalEffector::apply_fd_write::(&ctx, 1, offset, data) + } else { + JournalEffector::apply_fd_write::(&ctx, 1, offset, data) + } + .map_err(anyhow_err_to_runtime_err)?; + } + + for (offset, data, is_64bit) in stderr { + if is_64bit { + JournalEffector::apply_fd_write::(&ctx, 2, offset, data) + } else { + JournalEffector::apply_fd_write::(&ctx, 2, offset, data) + } + .map_err(anyhow_err_to_runtime_err)?; + } + // Next we apply all the memory updates that were delayed while the logs + // were processed to completion. + for (region, data) in update_memory { + JournalEffector::apply_memory(&mut ctx, region, &data) + .map_err(anyhow_err_to_runtime_err)?; + } + if let Some(state) = update_tty { + JournalEffector::apply_tty_set(&mut ctx, state).map_err(anyhow_err_to_runtime_err)?; + } + + Ok(rewind) +} diff --git a/lib/wasix/src/syscalls/mod.rs b/lib/wasix/src/syscalls/mod.rs index ef24583bafd..610f4075419 100644 --- a/lib/wasix/src/syscalls/mod.rs +++ b/lib/wasix/src/syscalls/mod.rs @@ -16,6 +16,7 @@ pub mod wasm; #[cfg(any(target_os = "windows"))] pub mod windows; +pub mod journal; pub mod wasi; pub mod wasix; @@ -54,6 +55,7 @@ use std::{io::IoSlice, marker::PhantomData, mem::MaybeUninit, task::Waker, time: pub(crate) use bytes::{Bytes, BytesMut}; pub(crate) use cooked_waker::IntoWaker; +pub use journal::*; pub(crate) use sha2::Sha256; pub(crate) use tracing::{debug, error, trace, warn}; #[cfg(any( @@ -117,10 +119,12 @@ use crate::{ fs_error_into_wasi_err, virtual_file_type_to_wasi_file_type, Fd, InodeVal, Kind, MAX_SYMLINKS, }, - os::task::thread::RewindResult, + journal::{DynJournal, JournalEffector}, + os::task::{process::MaybeCheckpointResult, thread::RewindResult}, runtime::task_manager::InlineWaker, utils::store::InstanceSnapshot, - DeepSleepWork, RewindPostProcess, RewindState, SpawnError, WasiInodes, + DeepSleepWork, RewindPostProcess, RewindState, RewindStateOption, SpawnError, WasiInodes, + WasiResult, WasiRuntimeError, }; pub(crate) use crate::{net::net_error_into_wasi_err, utils::WasiParkingLot}; @@ -241,9 +245,9 @@ fn block_on_with_timeout( tasks: &Arc, timeout: Option, work: Fut, -) -> Result, WasiError> +) -> WasiResult where - Fut: Future, WasiError>>, + Fut: Future>, { let mut nonblocking = false; if timeout == Some(Duration::ZERO) { @@ -293,7 +297,7 @@ pub(crate) fn __asyncify( ctx: &mut FunctionEnvMut<'_, WasiEnv>, timeout: Option, work: Fut, -) -> Result, WasiError> +) -> WasiResult where T: 'static, Fut: std::future::Future>, @@ -481,7 +485,7 @@ pub(crate) fn __asyncify_light( env: &WasiEnv, timeout: Option, work: Fut, -) -> Result, WasiError> +) -> WasiResult where T: 'static, Fut: Future>, @@ -949,7 +953,7 @@ pub(crate) fn deep_sleep( trigger: Pin>, ) -> Result<(), WasiError> { // Grab all the globals and serialize them - let store_data = crate::utils::store::capture_snapshot(&mut ctx.as_store_mut()) + let store_data = crate::utils::store::capture_instance_snapshot(&mut ctx.as_store_mut()) .serialize() .unwrap(); let store_data = Bytes::from(store_data); @@ -958,17 +962,15 @@ pub(crate) fn deep_sleep( let tasks = ctx.data().tasks().clone(); let res = unwind::(ctx, move |_ctx, memory_stack, rewind_stack| { // Schedule the process on the stack so that it can be resumed - OnCalledAction::Trap(Box::new(RuntimeError::user(Box::new( - WasiError::DeepSleep(DeepSleepWork { - trigger, - rewind: RewindState { - memory_stack: memory_stack.freeze(), - rewind_stack: rewind_stack.freeze(), - store_data, - is_64bit: M::is_64bit(), - }, - }), - )))) + OnCalledAction::Trap(Box::new(WasiError::DeepSleep(DeepSleepWork { + trigger, + rewind: RewindState { + memory_stack: memory_stack.freeze(), + rewind_stack: rewind_stack.freeze(), + store_data, + is_64bit: M::is_64bit(), + }, + }))) })?; // If there is an error then exit the process, otherwise we are done @@ -1112,7 +1114,7 @@ where #[instrument(level = "debug", skip_all, fields(memory_stack_len = memory_stack.len(), rewind_stack_len = rewind_stack.len(), store_data_len = store_data.len()))] #[must_use = "the action must be passed to the call loop"] pub fn rewind( - ctx: FunctionEnvMut, + mut ctx: FunctionEnvMut, memory_stack: Bytes, rewind_stack: Bytes, store_data: Bytes, @@ -1122,17 +1124,23 @@ where T: serde::Serialize, { let rewind_result = bincode::serialize(&result).unwrap().into(); - rewind_ext::(ctx, memory_stack, rewind_stack, store_data, rewind_result) + rewind_ext::( + &mut ctx, + memory_stack, + rewind_stack, + store_data, + Some(rewind_result), + ) } #[instrument(level = "debug", skip_all, fields(memory_stack_len = memory_stack.len(), rewind_stack_len = rewind_stack.len(), store_data_len = store_data.len()))] #[must_use = "the action must be passed to the call loop"] pub fn rewind_ext( - mut ctx: FunctionEnvMut, + ctx: &mut FunctionEnvMut, memory_stack: Bytes, rewind_stack: Bytes, store_data: Bytes, - rewind_result: Bytes, + rewind_result: Option, ) -> Errno { // Store the memory stack so that it can be restored later ctx.data_mut().thread.set_rewind(RewindResult { @@ -1148,7 +1156,7 @@ pub fn rewind_ext( return Errno::Unknown; } }; - crate::utils::store::restore_snapshot(&mut ctx, &store_snapshot); + crate::utils::store::restore_instance_snapshot(ctx, &store_snapshot); let env = ctx.data(); let memory = match env.try_memory_view(&ctx) { Some(v) => v, @@ -1203,7 +1211,7 @@ pub fn rewind_ext( .filter_map(|a| a.asyncify_start_rewind.clone()) .next() { - asyncify_start_rewind.call(&mut ctx, asyncify_data); + asyncify_start_rewind.call(ctx, asyncify_data); } else { warn!("failed to rewind the stack because the asyncify_start_rewind export is missing or inaccessible"); return Errno::Noexec; @@ -1212,18 +1220,76 @@ pub fn rewind_ext( Errno::Success } +pub fn rewind_ext2( + ctx: &mut FunctionEnvMut, + rewind_state: RewindStateOption, +) -> Result<(), ExitCode> { + if let Some((rewind_state, rewind_result)) = rewind_state { + tracing::trace!("Rewinding"); + let errno = if rewind_state.is_64bit { + crate::rewind_ext::( + ctx, + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + rewind_result, + ) + } else { + crate::rewind_ext::( + ctx, + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + rewind_result, + ) + }; + + if errno != Errno::Success { + let exit_code = ExitCode::from(errno); + ctx.data().on_exit(Some(exit_code)); + return Err(exit_code); + } + } + + Ok(()) +} + +pub fn anyhow_err_to_runtime_err(err: anyhow::Error) -> WasiRuntimeError { + WasiRuntimeError::Runtime(RuntimeError::user(err.into())) +} + pub(crate) unsafe fn handle_rewind( ctx: &mut FunctionEnvMut<'_, WasiEnv>, ) -> Option where T: serde::de::DeserializeOwned, { + handle_rewind_ext::(ctx, HandleRewindType::ResultDriven) +} + +pub(crate) enum HandleRewindType { + /// Handle rewind types that have a result to be processed + ResultDriven, + /// Handle rewind types that are resultless (generally these + /// are caused by snapshot events) + Resultless, +} + +pub(crate) unsafe fn handle_rewind_ext( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + _type: HandleRewindType, +) -> Option +where + T: serde::de::DeserializeOwned, +{ + if !ctx.data().thread.has_rewind_of_type(_type) { + return None; + }; + // If the stack has been restored if let Some(result) = ctx.data_mut().thread.take_rewind() { // Deserialize the result let memory_stack = result.memory_stack; - let ret = bincode::deserialize(&result.rewind_result) - .expect("failed to deserialize the rewind result"); // Notify asyncify that we are no longer rewinding let env = ctx.data(); @@ -1237,7 +1303,14 @@ where // Restore the memory stack let (env, mut store) = ctx.data_and_store_mut(); set_memory_stack::(env, &mut store, memory_stack); - Some(ret) + + if let Some(rewind_result) = result.rewind_result { + let ret = bincode::deserialize(&rewind_result) + .expect("failed to deserialize the rewind result"); + Some(ret) + } else { + None + } } else { None } diff --git a/lib/wasix/src/syscalls/wasi/clock_time_set.rs b/lib/wasix/src/syscalls/wasi/clock_time_set.rs index 60b2ccbbe6c..03c2df57229 100644 --- a/lib/wasix/src/syscalls/wasi/clock_time_set.rs +++ b/lib/wasix/src/syscalls/wasi/clock_time_set.rs @@ -10,7 +10,27 @@ use crate::syscalls::*; /// The value of the clock in nanoseconds #[instrument(level = "trace", skip_all, fields(?clock_id, %time), ret)] pub fn clock_time_set( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, + clock_id: Snapshot0Clockid, + time: Timestamp, +) -> Result { + let ret = clock_time_set_internal(&mut ctx, clock_id, time); + let env = ctx.data(); + + if ret == Errno::Success { + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_clock_time_set(&mut ctx, clock_id, time).map_err(|err| { + tracing::error!("failed to save clock time set event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + } + Ok(ret) +} + +pub fn clock_time_set_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, clock_id: Snapshot0Clockid, time: Timestamp, ) -> Errno { diff --git a/lib/wasix/src/syscalls/wasi/environ_get.rs b/lib/wasix/src/syscalls/wasi/environ_get.rs index fabd186be92..0c3b0c60735 100644 --- a/lib/wasix/src/syscalls/wasi/environ_get.rs +++ b/lib/wasix/src/syscalls/wasi/environ_get.rs @@ -1,5 +1,5 @@ use super::*; -use crate::syscalls::*; +use crate::{journal::SnapshotTrigger, syscalls::*}; /// ### `environ_get()` /// Read environment variable data. @@ -11,12 +11,18 @@ use crate::syscalls::*; /// A pointer to a buffer to write the environment variable string data. #[instrument(level = "debug", skip_all, ret)] pub fn environ_get( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, environ: WasmPtr, M>, environ_buf: WasmPtr, -) -> Errno { +) -> Result { + ctx = wasi_try_ok!(maybe_snapshot_once::( + ctx, + SnapshotTrigger::FirstEnviron + )?); + let env = ctx.data(); let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - write_buffer_array(&memory, &state.envs, environ, environ_buf) + let envs = state.envs.lock().unwrap(); + Ok(write_buffer_array(&memory, &envs, environ, environ_buf)) } diff --git a/lib/wasix/src/syscalls/wasi/environ_sizes_get.rs b/lib/wasix/src/syscalls/wasi/environ_sizes_get.rs index eb3b3f08d98..73f4dddd9a6 100644 --- a/lib/wasix/src/syscalls/wasi/environ_sizes_get.rs +++ b/lib/wasix/src/syscalls/wasi/environ_sizes_get.rs @@ -1,5 +1,5 @@ use super::*; -use crate::syscalls::*; +use crate::{journal::SnapshotTrigger, syscalls::*}; /// ### `environ_sizes_get()` /// Return command-line argument data sizes. @@ -10,27 +10,38 @@ use crate::syscalls::*; /// The size of the environment variable string data. #[instrument(level = "trace", skip_all, ret)] pub fn environ_sizes_get( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, environ_count: WasmPtr, environ_buf_size: WasmPtr, -) -> Errno { +) -> Result { + ctx = wasi_try_ok!(maybe_snapshot_once::( + ctx, + SnapshotTrigger::FirstEnviron + )?); + let env = ctx.data(); let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; let environ_count = environ_count.deref(&memory); let environ_buf_size = environ_buf_size.deref(&memory); - let env_var_count: M::Offset = - wasi_try!(state.envs.len().try_into().map_err(|_| Errno::Overflow)); - let env_buf_size: usize = state.envs.iter().map(|v| v.len() + 1).sum(); - let env_buf_size: M::Offset = wasi_try!(env_buf_size.try_into().map_err(|_| Errno::Overflow)); - wasi_try_mem!(environ_count.write(env_var_count)); - wasi_try_mem!(environ_buf_size.write(env_buf_size)); + let env_var_count: M::Offset = wasi_try_ok!(state + .envs + .lock() + .unwrap() + .len() + .try_into() + .map_err(|_| Errno::Overflow)); + let env_buf_size: usize = state.envs.lock().unwrap().iter().map(|v| v.len() + 1).sum(); + let env_buf_size: M::Offset = + wasi_try_ok!(env_buf_size.try_into().map_err(|_| Errno::Overflow)); + wasi_try_mem_ok!(environ_count.write(env_var_count)); + wasi_try_mem_ok!(environ_buf_size.write(env_buf_size)); trace!( %env_var_count, %env_buf_size ); - Errno::Success + Ok(Errno::Success) } diff --git a/lib/wasix/src/syscalls/wasi/fd_advise.rs b/lib/wasix/src/syscalls/wasi/fd_advise.rs index 0426d6da07c..ed2a43684fe 100644 --- a/lib/wasix/src/syscalls/wasi/fd_advise.rs +++ b/lib/wasix/src/syscalls/wasi/fd_advise.rs @@ -14,13 +14,34 @@ use crate::syscalls::*; /// The advice to give #[instrument(level = "debug", skip_all, fields(%fd, %offset, %len, ?advice), ret)] pub fn fd_advise( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, offset: Filesize, len: Filesize, advice: Advice, -) -> Errno { +) -> Result { + wasi_try_ok!(fd_advise_internal(&mut ctx, fd, offset, len, advice)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_advise(&mut ctx, fd, offset, len, advice).map_err(|err| { + tracing::error!("failed to save file descriptor advise event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn fd_advise_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + offset: Filesize, + len: Filesize, + advice: Advice, +) -> Result<(), Errno> { // this is used for our own benefit, so just returning success is a valid // implementation for now - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/fd_allocate.rs b/lib/wasix/src/syscalls/wasi/fd_allocate.rs index d3450298bcc..23462cbe48d 100644 --- a/lib/wasix/src/syscalls/wasi/fd_allocate.rs +++ b/lib/wasix/src/syscalls/wasi/fd_allocate.rs @@ -12,43 +12,63 @@ use crate::syscalls::*; /// The length from the offset marking the end of the allocation #[instrument(level = "debug", skip_all, fields(%fd, %offset, %len), ret)] pub fn fd_allocate( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, offset: Filesize, len: Filesize, -) -> Errno { +) -> Result { + wasi_try_ok!(fd_allocate_internal(&mut ctx, fd, offset, len)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_allocate(&mut ctx, fd, offset, len).map_err(|err| { + tracing::error!("failed to save file descriptor allocate event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn fd_allocate_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + offset: Filesize, + len: Filesize, +) -> Result<(), Errno> { let env = ctx.data(); let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let fd_entry = state.fs.get_fd(fd)?; let inode = fd_entry.inode; if !fd_entry.rights.contains(Rights::FD_ALLOCATE) { - return Errno::Access; + return Err(Errno::Access); } - let new_size = wasi_try!(offset.checked_add(len).ok_or(Errno::Inval)); + let new_size = offset.checked_add(len).ok_or(Errno::Inval)?; { let mut guard = inode.write(); match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(handle) = handle { let mut handle = handle.write().unwrap(); - wasi_try!(handle.set_len(new_size).map_err(fs_error_into_wasi_err)); + handle.set_len(new_size).map_err(fs_error_into_wasi_err)?; } else { - return Errno::Badf; + return Err(Errno::Badf); } } - Kind::Socket { .. } => return Errno::Badf, - Kind::Pipe { .. } => return Errno::Badf, + Kind::Socket { .. } => return Err(Errno::Badf), + Kind::Pipe { .. } => return Err(Errno::Badf), Kind::Buffer { buffer } => { buffer.resize(new_size as usize, 0); } - Kind::Symlink { .. } => return Errno::Badf, - Kind::EventNotifications { .. } | Kind::Epoll { .. } => return Errno::Badf, - Kind::Dir { .. } | Kind::Root { .. } => return Errno::Isdir, + Kind::Symlink { .. } => return Err(Errno::Badf), + Kind::EventNotifications { .. } | Kind::Epoll { .. } => return Err(Errno::Badf), + Kind::Dir { .. } | Kind::Root { .. } => return Err(Errno::Isdir), } } inode.stat.write().unwrap().st_size = new_size; debug!(%new_size); - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/fd_close.rs b/lib/wasix/src/syscalls/wasi/fd_close.rs index 1d38b4391c6..9196d2ade34 100644 --- a/lib/wasix/src/syscalls/wasi/fd_close.rs +++ b/lib/wasix/src/syscalls/wasi/fd_close.rs @@ -32,5 +32,13 @@ pub fn fd_close(mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Result( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, ret_fd: WasmPtr, -) -> Errno { +) -> Result { + let copied_fd = wasi_try_ok!(fd_dup_internal(&mut ctx, fd)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_duplicate(&mut ctx, fd, copied_fd).map_err(|err| { + tracing::error!("failed to save file descriptor duplicate event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Span::current().record("ret_fd", copied_fd); let env = ctx.data(); let (memory, state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - let fd = wasi_try!(state.fs.clone_fd(fd)); + wasi_try_mem_ok!(ret_fd.write(&memory, copied_fd)); - Span::current().record("ret_fd", fd); - wasi_try_mem!(ret_fd.write(&memory, fd)); + Ok(Errno::Success) +} - Errno::Success +pub(crate) fn fd_dup_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, +) -> Result { + let env = ctx.data(); + let (memory, state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; + let fd = state.fs.clone_fd(fd)?; + Ok(fd) } diff --git a/lib/wasix/src/syscalls/wasi/fd_event.rs b/lib/wasix/src/syscalls/wasi/fd_event.rs index 00e0bb8a754..6f3a01f2942 100644 --- a/lib/wasix/src/syscalls/wasi/fd_event.rs +++ b/lib/wasix/src/syscalls/wasi/fd_event.rs @@ -5,11 +5,34 @@ use crate::{fs::NotificationInner, syscalls::*}; /// Creates a file handle for event notifications #[instrument(level = "debug", skip_all, fields(%initial_val, ret_fd = field::Empty), ret)] pub fn fd_event( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, initial_val: u64, flags: EventFdFlags, ret_fd: WasmPtr, -) -> Errno { +) -> Result { + let fd = wasi_try_ok!(fd_event_internal(&mut ctx, initial_val, flags)?); + + let env = ctx.data(); + let (memory, state, _) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + Span::current().record("ret_fd", fd); + wasi_try_mem_ok!(ret_fd.write(&memory, fd)); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_event(&mut ctx, initial_val, flags, fd).map_err(|err| { + tracing::error!("failed to save fd_event event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub fn fd_event_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + initial_val: u64, + flags: EventFdFlags, +) -> Result, WasiError> { let env = ctx.data(); let (memory, state, mut inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; @@ -26,12 +49,9 @@ pub fn fd_event( | Rights::FD_WRITE | Rights::POLL_FD_READWRITE | Rights::FD_FDSTAT_SET_FLAGS; - let fd = wasi_try!(state + let fd = wasi_try_ok_ok!(state .fs .create_fd(rights, rights, Fdflags::empty(), 0, inode)); - Span::current().record("ret_fd", fd); - wasi_try_mem!(ret_fd.write(&memory, fd)); - - Errno::Success + Ok(Ok(fd)) } diff --git a/lib/wasix/src/syscalls/wasi/fd_fdstat_set_flags.rs b/lib/wasix/src/syscalls/wasi/fd_fdstat_set_flags.rs index 7ee8790a721..cedd78fecc9 100644 --- a/lib/wasix/src/syscalls/wasi/fd_fdstat_set_flags.rs +++ b/lib/wasix/src/syscalls/wasi/fd_fdstat_set_flags.rs @@ -13,6 +13,27 @@ pub fn fd_fdstat_set_flags( mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, flags: Fdflags, +) -> Result { + let ret = fd_fdstat_set_flags_internal(&mut ctx, fd, flags)?; + let env = ctx.data(); + + if ret == Errno::Success { + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_set_flags(&mut ctx, fd, flags).map_err(|err| { + tracing::error!("failed to save file set flags event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + } + + Ok(ret) +} + +pub(crate) fn fd_fdstat_set_flags_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + flags: Fdflags, ) -> Result { { let env = ctx.data(); diff --git a/lib/wasix/src/syscalls/wasi/fd_fdstat_set_rights.rs b/lib/wasix/src/syscalls/wasi/fd_fdstat_set_rights.rs index ec724f9d536..ce9aa91d2dc 100644 --- a/lib/wasix/src/syscalls/wasi/fd_fdstat_set_rights.rs +++ b/lib/wasix/src/syscalls/wasi/fd_fdstat_set_rights.rs @@ -12,25 +12,51 @@ use crate::syscalls::*; /// The inheriting rights to apply to `fd` #[instrument(level = "debug", skip_all, fields(%fd), ret)] pub fn fd_fdstat_set_rights( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, fs_rights_base: Rights, fs_rights_inheriting: Rights, -) -> Errno { +) -> Result { + wasi_try_ok!(fd_fdstat_set_rights_internal( + &mut ctx, + fd, + fs_rights_base, + fs_rights_inheriting + )); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_set_rights(&mut ctx, fd, fs_rights_base, fs_rights_inheriting) + .map_err(|err| { + tracing::error!("failed to save file set rights event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn fd_fdstat_set_rights_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, +) -> Result<(), Errno> { let env = ctx.data(); let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); + let fd_entry = fd_map.get_mut(&fd).ok_or(Errno::Badf)?; // ensure new rights are a subset of current rights if fd_entry.rights | fs_rights_base != fd_entry.rights || fd_entry.rights_inheriting | fs_rights_inheriting != fd_entry.rights_inheriting { - return Errno::Notcapable; + return Err(Errno::Notcapable); } fd_entry.rights = fs_rights_base; fd_entry.rights_inheriting = fs_rights_inheriting; - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/fd_filestat_set_size.rs b/lib/wasix/src/syscalls/wasi/fd_filestat_set_size.rs index 40979d8d8fb..ddb361744b2 100644 --- a/lib/wasix/src/syscalls/wasi/fd_filestat_set_size.rs +++ b/lib/wasix/src/syscalls/wasi/fd_filestat_set_size.rs @@ -10,17 +10,36 @@ use crate::syscalls::*; /// New size that `fd` will be set to #[instrument(level = "debug", skip_all, fields(%fd, %st_size), ret)] pub fn fd_filestat_set_size( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, st_size: Filesize, -) -> Errno { +) -> Result { + wasi_try_ok!(fd_filestat_set_size_internal(&mut ctx, fd, st_size)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_set_size(&mut ctx, fd, st_size).map_err(|err| { + tracing::error!("failed to save file set size event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn fd_filestat_set_size_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + st_size: Filesize, +) -> Result<(), Errno> { let env = ctx.data(); let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let fd_entry = state.fs.get_fd(fd)?; let inode = fd_entry.inode; if !fd_entry.rights.contains(Rights::FD_FILESTAT_SET_SIZE) { - return Errno::Access; + return Err(Errno::Access); } { @@ -29,22 +48,22 @@ pub fn fd_filestat_set_size( Kind::File { handle, .. } => { if let Some(handle) = handle { let mut handle = handle.write().unwrap(); - wasi_try!(handle.set_len(st_size).map_err(fs_error_into_wasi_err)); + handle.set_len(st_size).map_err(fs_error_into_wasi_err)?; } else { - return Errno::Badf; + return Err(Errno::Badf); } } Kind::Buffer { buffer } => { buffer.resize(st_size as usize, 0); } - Kind::Socket { .. } => return Errno::Badf, - Kind::Pipe { .. } => return Errno::Badf, - Kind::Symlink { .. } => return Errno::Badf, - Kind::EventNotifications { .. } | Kind::Epoll { .. } => return Errno::Badf, - Kind::Dir { .. } | Kind::Root { .. } => return Errno::Isdir, + Kind::Socket { .. } => return Err(Errno::Badf), + Kind::Pipe { .. } => return Err(Errno::Badf), + Kind::Symlink { .. } => return Err(Errno::Badf), + Kind::EventNotifications { .. } | Kind::Epoll { .. } => return Err(Errno::Badf), + Kind::Dir { .. } | Kind::Root { .. } => return Err(Errno::Isdir), } } inode.stat.write().unwrap().st_size = st_size; - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/fd_filestat_set_times.rs b/lib/wasix/src/syscalls/wasi/fd_filestat_set_times.rs index 5eea1e19ad6..df7db200bc1 100644 --- a/lib/wasix/src/syscalls/wasi/fd_filestat_set_times.rs +++ b/lib/wasix/src/syscalls/wasi/fd_filestat_set_times.rs @@ -12,24 +12,49 @@ use crate::syscalls::*; /// Bit-vector for controlling which times get set #[instrument(level = "debug", skip_all, fields(%fd, %st_atim, %st_mtim), ret)] pub fn fd_filestat_set_times( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, st_atim: Timestamp, st_mtim: Timestamp, fst_flags: Fstflags, -) -> Errno { +) -> Result { + wasi_try_ok!(fd_filestat_set_times_internal( + &mut ctx, fd, st_atim, st_mtim, fst_flags + )); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_set_times(&mut ctx, fd, st_atim, st_mtim, fst_flags).map_err( + |err| { + tracing::error!("failed to save file set times event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + }, + )?; + } + + Ok(Errno::Success) +} + +pub(crate) fn fd_filestat_set_times_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, +) -> Result<(), Errno> { let env = ctx.data(); let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let fd_entry = state.fs.get_fd(fd)?; if !fd_entry.rights.contains(Rights::FD_FILESTAT_SET_TIMES) { - return Errno::Access; + return Err(Errno::Access); } if (fst_flags.contains(Fstflags::SET_ATIM) && fst_flags.contains(Fstflags::SET_ATIM_NOW)) || (fst_flags.contains(Fstflags::SET_MTIM) && fst_flags.contains(Fstflags::SET_MTIM_NOW)) { - return Errno::Inval; + return Err(Errno::Inval); } let inode = fd_entry.inode; @@ -38,7 +63,7 @@ pub fn fd_filestat_set_times( let time_to_set = if fst_flags.contains(Fstflags::SET_ATIM) { st_atim } else { - wasi_try!(get_current_time_in_nanos()) + get_current_time_in_nanos()? }; inode.stat.write().unwrap().st_atim = time_to_set; } @@ -47,10 +72,10 @@ pub fn fd_filestat_set_times( let time_to_set = if fst_flags.contains(Fstflags::SET_MTIM) { st_mtim } else { - wasi_try!(get_current_time_in_nanos()) + get_current_time_in_nanos()? }; inode.stat.write().unwrap().st_mtim = time_to_set; } - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/fd_read.rs b/lib/wasix/src/syscalls/wasi/fd_read.rs index 220d1af7cb2..2d16eda58b4 100644 --- a/lib/wasix/src/syscalls/wasi/fd_read.rs +++ b/lib/wasix/src/syscalls/wasi/fd_read.rs @@ -1,9 +1,15 @@ use std::{collections::VecDeque, task::Waker}; -use virtual_fs::{AsyncReadExt, ReadBuf}; +use virtual_fs::{AsyncReadExt, DeviceFile, ReadBuf}; use super::*; -use crate::{fs::NotificationInner, net::socket::TimeType, syscalls::*}; +use crate::{ + fs::NotificationInner, + journal::SnapshotTrigger, + net::socket::TimeType, + os::task::process::{MaybeCheckpointResult, WasiProcessCheckpoint, WasiProcessInner}, + syscalls::*, +}; /// ### `fd_read()` /// Read data from file descriptor @@ -38,6 +44,10 @@ pub fn fd_read( fd_entry.offset.load(Ordering::Acquire) as usize }; + if fd == DeviceFile::STDIN { + ctx = wasi_try_ok!(maybe_snapshot_once::(ctx, SnapshotTrigger::FirstStdin)?); + } + let res = fd_read_internal::(&mut ctx, fd, iovs, iovs_len, offset, nread, true)?; fd_read_internal_handler(ctx, res, nread) } @@ -69,6 +79,10 @@ pub fn fd_pread( let pid = ctx.data().pid(); let tid = ctx.data().tid(); + if fd == DeviceFile::STDIN { + ctx = wasi_try_ok!(maybe_snapshot_once::(ctx, SnapshotTrigger::FirstStdin)?); + } + let res = fd_read_internal::(&mut ctx, fd, iovs, iovs_len, offset as usize, nread, false)?; fd_read_internal_handler::(ctx, res, nread) } @@ -109,7 +123,7 @@ pub(crate) fn fd_read_internal( offset: usize, nread: WasmPtr, should_update_cursor: bool, -) -> Result, WasiError> { +) -> WasiResult { wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?); let env = ctx.data(); diff --git a/lib/wasix/src/syscalls/wasi/fd_renumber.rs b/lib/wasix/src/syscalls/wasi/fd_renumber.rs index 1305003f344..55add2ebea7 100644 --- a/lib/wasix/src/syscalls/wasi/fd_renumber.rs +++ b/lib/wasix/src/syscalls/wasi/fd_renumber.rs @@ -9,7 +9,31 @@ use crate::syscalls::*; /// - `Fd to` /// Location to copy file descriptor to #[instrument(level = "debug", skip_all, fields(%from, %to), ret)] -pub fn fd_renumber(ctx: FunctionEnvMut<'_, WasiEnv>, from: WasiFd, to: WasiFd) -> Errno { +pub fn fd_renumber( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + from: WasiFd, + to: WasiFd, +) -> Result { + let ret = fd_renumber_internal(&mut ctx, from, to); + let env = ctx.data(); + + if ret == Errno::Success { + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_renumber(&mut ctx, from, to).map_err(|err| { + tracing::error!("failed to save file descriptor renumber event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + } + Ok(ret) +} + +pub(crate) fn fd_renumber_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + from: WasiFd, + to: WasiFd, +) -> Errno { if from == to { return Errno::Success; } @@ -27,6 +51,7 @@ pub fn fd_renumber(ctx: FunctionEnvMut<'_, WasiEnv>, from: WasiFd, to: WasiFd) - ..*fd_entry }; fd_map.insert(to, new_fd_entry); + state.fs.make_max_fd(to + 1); Errno::Success } diff --git a/lib/wasix/src/syscalls/wasi/fd_seek.rs b/lib/wasix/src/syscalls/wasi/fd_seek.rs index 50391f9c5db..02c6b063ad0 100644 --- a/lib/wasix/src/syscalls/wasi/fd_seek.rs +++ b/lib/wasix/src/syscalls/wasi/fd_seek.rs @@ -23,20 +23,51 @@ pub fn fd_seek( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + let new_offset = wasi_try_ok!(fd_seek_internal(&mut ctx, fd, offset, whence)?); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_seek(&mut ctx, fd, offset, whence).map_err(|err| { + tracing::error!("failed to save file descriptor seek event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + // reborrow + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let new_offset_ref = newoffset.deref(&memory); + let fd_entry = wasi_try_ok!(env.state.fs.get_fd(fd)); + wasi_try_mem_ok!(new_offset_ref.write(new_offset)); + + trace!( + %new_offset, + ); + + Ok(Errno::Success) +} + +pub(crate) fn fd_seek_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + offset: FileDelta, + whence: Whence, +) -> Result, WasiError> { let env = ctx.data(); let state = env.state.clone(); let (memory, _) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); + let fd_entry = wasi_try_ok_ok!(state.fs.get_fd(fd)); if !fd_entry.rights.contains(Rights::FD_SEEK) { - return Ok(Errno::Access); + return Ok(Err(Errno::Access)); } // TODO: handle case if fd is a dir? let new_offset = match whence { Whence::Cur => { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); + let fd_entry = wasi_try_ok_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); #[allow(clippy::comparison_chain)] if offset > 0 { @@ -62,7 +93,7 @@ pub fn fd_seek( let handle = handle.clone(); drop(guard); - wasi_try_ok!(__asyncify(&mut ctx, None, async move { + wasi_try_ok_ok!(__asyncify(ctx, None, async move { let mut handle = handle.write().unwrap(); let end = handle .seek(SeekFrom::End(offset)) @@ -77,7 +108,7 @@ pub fn fd_seek( Ok(()) })?); } else { - return Ok(Errno::Inval); + return Ok(Err(Errno::Inval)); } } Kind::Symlink { .. } => { @@ -90,34 +121,24 @@ pub fn fd_seek( | Kind::EventNotifications { .. } | Kind::Epoll { .. } => { // TODO: check this - return Ok(Errno::Inval); + return Ok(Err(Errno::Inval)); } Kind::Buffer { .. } => { // seeking buffers probably makes sense // FIXME: implement this - return Ok(Errno::Inval); + return Ok(Err(Errno::Inval)); } } fd_entry.offset.load(Ordering::Acquire) } Whence::Set => { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); + let fd_entry = wasi_try_ok_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); fd_entry.offset.store(offset as u64, Ordering::Release); offset as u64 } - _ => return Ok(Errno::Inval), + _ => return Ok(Err(Errno::Inval)), }; - // reborrow - let env = ctx.data(); - let memory = unsafe { env.memory_view(&ctx) }; - let new_offset_ref = newoffset.deref(&memory); - let fd_entry = wasi_try_ok!(env.state.fs.get_fd(fd)); - wasi_try_mem_ok!(new_offset_ref.write(new_offset)); - - trace!( - %new_offset, - ); - Ok(Errno::Success) + Ok(Ok(new_offset)) } diff --git a/lib/wasix/src/syscalls/wasi/fd_write.rs b/lib/wasix/src/syscalls/wasi/fd_write.rs index e60ba61ae25..e586d897163 100644 --- a/lib/wasix/src/syscalls/wasi/fd_write.rs +++ b/lib/wasix/src/syscalls/wasi/fd_write.rs @@ -1,6 +1,11 @@ use std::task::Waker; use super::*; +#[cfg(feature = "journal")] +use crate::{ + journal::{JournalEffector, JournalEntry}, + utils::map_snapshot_err, +}; use crate::{net::socket::TimeType, syscalls::*}; /// ### `fd_write()` @@ -25,8 +30,10 @@ pub fn fd_write( iovs_len: M::Offset, nwritten: WasmPtr, ) -> Result { + wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + + let env = ctx.data(); let offset = { - let mut env = ctx.data(); let state = env.state.clone(); let inodes = state.inodes.clone(); @@ -34,7 +41,25 @@ pub fn fd_write( fd_entry.offset.load(Ordering::Acquire) as usize }; - fd_write_internal::(ctx, fd, iovs, iovs_len, offset, nwritten, true) + let bytes_written = wasi_try_ok!(fd_write_internal::( + &ctx, + fd, + FdWriteSource::Iovs { iovs, iovs_len }, + offset as u64, + true, + env.enable_journal, + )?); + + Span::current().record("nwritten", bytes_written); + + let mut env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let nwritten_ref = nwritten.deref(&memory); + let bytes_written: M::Offset = + wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); + wasi_try_mem_ok!(nwritten_ref.write(bytes_written)); + + Ok(Errno::Success) } /// ### `fd_pwrite()` @@ -60,52 +85,62 @@ pub fn fd_pwrite( offset: Filesize, nwritten: WasmPtr, ) -> Result { - fd_write_internal::(ctx, fd, iovs, iovs_len, offset as usize, nwritten, false) + wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + + let enable_snapshot_capture = ctx.data().enable_journal; + + let bytes_written = wasi_try_ok!(fd_write_internal::( + &ctx, + fd, + FdWriteSource::Iovs { iovs, iovs_len }, + offset, + false, + enable_snapshot_capture, + )?); + + Span::current().record("nwritten", bytes_written); + + let mut env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let nwritten_ref = nwritten.deref(&memory); + let bytes_written: M::Offset = + wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); + wasi_try_mem_ok!(nwritten_ref.write(bytes_written)); + + Ok(Errno::Success) +} + +pub(crate) enum FdWriteSource<'a, M: MemorySize> { + Iovs { + iovs: WasmPtr<__wasi_ciovec_t, M>, + iovs_len: M::Offset, + }, + Buffer(Cow<'a, [u8]>), } -/// ### `fd_pwrite()` -/// Write to a file without adjusting its offset -/// Inputs: -/// - `Fd` -/// File descriptor (opened with writing) to write to -/// - `const __wasi_ciovec_t *iovs` -/// List of vectors to read data from -/// - `u32 iovs_len` -/// Length of data in `iovs` -/// - `Filesize offset` -/// The offset to write at -/// Output: -/// - `u32 *nwritten` -/// Number of bytes written pub(crate) fn fd_write_internal( - mut ctx: FunctionEnvMut<'_, WasiEnv>, + ctx: &FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, - iovs: WasmPtr<__wasi_ciovec_t, M>, - iovs_len: M::Offset, - offset: usize, - nwritten: WasmPtr, + data: FdWriteSource<'_, M>, + offset: u64, should_update_cursor: bool, -) -> Result { - wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); - + should_snapshot: bool, +) -> Result, WasiError> { let mut env = ctx.data(); let state = env.state.clone(); - let mut memory = unsafe { env.memory_view(&ctx) }; - let iovs_arr = wasi_try_mem_ok!(iovs.slice(&memory, iovs_len)); - let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); + let fd_entry = wasi_try_ok_ok!(state.fs.get_fd(fd)); let is_stdio = fd_entry.is_stdio; let bytes_written = { if !is_stdio && !fd_entry.rights.contains(Rights::FD_WRITE) { - return Ok(Errno::Access); + return Ok(Err(Errno::Access)); } let fd_flags = fd_entry.flags; + let mut memory = unsafe { env.memory_view(&ctx) }; - let (bytes_written, can_update_cursor) = { - let iovs_arr = wasi_try_mem_ok!(iovs_arr.access()); - + let (bytes_written, can_update_cursor, can_snapshot) = { let (mut memory, _) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; let mut guard = fd_entry.inode.write(); match guard.deref_mut() { @@ -125,42 +160,58 @@ pub(crate) fn fd_write_internal( let mut handle = handle.write().unwrap(); if !is_stdio { handle - .seek(std::io::SeekFrom::Start(offset as u64)) + .seek(std::io::SeekFrom::Start(offset)) .await .map_err(map_io_err)?; } let mut written = 0usize; - for iovs in iovs_arr.iter() { - let buf = WasmPtr::::new(iovs.buf) - .slice(&memory, iovs.buf_len) - .map_err(mem_error_to_wasi)? - .access() - .map_err(mem_error_to_wasi)?; - let local_written = match handle.write(buf.as_ref()).await { - Ok(s) => s, - Err(_) if written > 0 => break, - Err(err) => return Err(map_io_err(err)), - }; - written += local_written; - if local_written != buf.len() { - break; + + match &data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = iovs + .slice(&memory, *iovs_len) + .map_err(mem_error_to_wasi)?; + let iovs_arr = + iovs_arr.access().map_err(mem_error_to_wasi)?; + for iovs in iovs_arr.iter() { + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, iovs.buf_len) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + let local_written = + match handle.write(buf.as_ref()).await { + Ok(s) => s, + Err(_) if written > 0 => break, + Err(err) => return Err(map_io_err(err)), + }; + written += local_written; + if local_written != buf.len() { + break; + } + } + } + FdWriteSource::Buffer(data) => { + handle.write_all(data).await?; + written += data.len(); } } + if is_stdio { handle.flush().await.map_err(map_io_err)?; } Ok(written) }, ); - let written = wasi_try_ok!(res?.map_err(|err| match err { + let written = wasi_try_ok_ok!(res?.map_err(|err| match err { Errno::Timedout => Errno::Again, a => a, })); - (written, true) + (written, true, true) } else { - return Ok(Errno::Inval); + return Ok(Err(Errno::Inval)); } } Kind::Socket { socket } => { @@ -176,92 +227,187 @@ pub(crate) fn fd_write_internal( let tasks = env.tasks().clone(); - let res = __asyncify_light(env, None, async move { + let res = __asyncify_light(env, None, async { let mut sent = 0usize; - for iovs in iovs_arr.iter() { - let buf = WasmPtr::::new(iovs.buf) - .slice(&memory, iovs.buf_len) - .map_err(mem_error_to_wasi)? - .access() - .map_err(mem_error_to_wasi)?; - let local_sent = socket - .send(tasks.deref(), buf.as_ref(), Some(timeout), nonblocking) - .await?; - sent += local_sent; - if local_sent != buf.len() { - break; + + match &data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = + iovs.slice(&memory, *iovs_len).map_err(mem_error_to_wasi)?; + let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; + for iovs in iovs_arr.iter() { + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, iovs.buf_len) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + let local_sent = socket + .send( + tasks.deref(), + buf.as_ref(), + Some(timeout), + nonblocking, + ) + .await?; + sent += local_sent; + if local_sent != buf.len() { + break; + } + } + } + FdWriteSource::Buffer(data) => { + sent += socket + .send(tasks.deref(), data.as_ref(), Some(timeout), nonblocking) + .await?; } } Ok(sent) }); - let written = wasi_try_ok!(res?); - (written, false) + let written = wasi_try_ok_ok!(res?); + (written, false, false) } Kind::Pipe { pipe } => { let mut written = 0usize; - for iovs in iovs_arr.iter() { - let buf = wasi_try_ok!(WasmPtr::::new(iovs.buf) - .slice(&memory, iovs.buf_len) - .map_err(mem_error_to_wasi)); - let buf = wasi_try_ok!(buf.access().map_err(mem_error_to_wasi)); - let local_written = wasi_try_ok!( - std::io::Write::write(pipe, buf.as_ref()).map_err(map_io_err) - ); - written += local_written; - if local_written != buf.len() { - break; + + match &data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = wasi_try_ok_ok!(iovs + .slice(&memory, *iovs_len) + .map_err(mem_error_to_wasi)); + let iovs_arr = + wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi)); + for iovs in iovs_arr.iter() { + let buf = wasi_try_ok_ok!(WasmPtr::::new(iovs.buf) + .slice(&memory, iovs.buf_len) + .map_err(mem_error_to_wasi)); + let buf = wasi_try_ok_ok!(buf.access().map_err(mem_error_to_wasi)); + let local_written = + wasi_try_ok_ok!(std::io::Write::write(pipe, buf.as_ref()) + .map_err(map_io_err)); + + written += local_written; + if local_written != buf.len() { + break; + } + } + } + FdWriteSource::Buffer(data) => { + wasi_try_ok_ok!( + std::io::Write::write_all(pipe, data).map_err(map_io_err) + ); + written += data.len(); } } - (written, false) + + (written, false, true) } Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify - return Ok(Errno::Isdir); + return Ok(Err(Errno::Isdir)); } Kind::EventNotifications { inner } => { let mut written = 0usize; - for iovs in iovs_arr.iter() { - let buf_len: usize = - wasi_try_ok!(iovs.buf_len.try_into().map_err(|_| Errno::Inval)); - let will_be_written = buf_len; - - let val_cnt = buf_len / std::mem::size_of::(); - let val_cnt: M::Offset = - wasi_try_ok!(val_cnt.try_into().map_err(|_| Errno::Inval)); - - let vals = wasi_try_ok!(WasmPtr::::new(iovs.buf) - .slice(&memory, val_cnt as M::Offset) - .map_err(mem_error_to_wasi)); - let vals = wasi_try_ok!(vals.access().map_err(mem_error_to_wasi)); - for val in vals.iter() { - inner.write(*val); - } - written += will_be_written; + match &data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = wasi_try_ok_ok!(iovs + .slice(&memory, *iovs_len) + .map_err(mem_error_to_wasi)); + let iovs_arr = + wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi)); + for iovs in iovs_arr.iter() { + let buf_len: usize = wasi_try_ok_ok!(iovs + .buf_len + .try_into() + .map_err(|_| Errno::Inval)); + let will_be_written = buf_len; + + let val_cnt = buf_len / std::mem::size_of::(); + let val_cnt: M::Offset = + wasi_try_ok_ok!(val_cnt.try_into().map_err(|_| Errno::Inval)); + + let vals = wasi_try_ok_ok!(WasmPtr::::new(iovs.buf) + .slice(&memory, val_cnt as M::Offset) + .map_err(mem_error_to_wasi)); + let vals = + wasi_try_ok_ok!(vals.access().map_err(mem_error_to_wasi)); + for val in vals.iter() { + inner.write(*val); + } + + written += will_be_written; + } + } + FdWriteSource::Buffer(data) => { + let cnt = data.len() / std::mem::size_of::(); + for n in 0..cnt { + let start = n * std::mem::size_of::(); + let data = [ + data[start], + data[start + 1], + data[start + 2], + data[start + 3], + data[start + 4], + data[start + 5], + data[start + 6], + data[start + 7], + ]; + inner.write(u64::from_ne_bytes(data)); + } + } } - (written, false) + + (written, false, true) } - Kind::Symlink { .. } | Kind::Epoll { .. } => return Ok(Errno::Inval), + Kind::Symlink { .. } | Kind::Epoll { .. } => return Ok(Err(Errno::Inval)), Kind::Buffer { buffer } => { let mut written = 0usize; - for iovs in iovs_arr.iter() { - let buf = wasi_try_ok!(WasmPtr::::new(iovs.buf) - .slice(&memory, iovs.buf_len) - .map_err(mem_error_to_wasi)); - let buf = wasi_try_ok!(buf.access().map_err(mem_error_to_wasi)); - let local_written = - wasi_try_ok!( - std::io::Write::write(buffer, buf.as_ref()).map_err(map_io_err) + + match &data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = wasi_try_ok_ok!(iovs + .slice(&memory, *iovs_len) + .map_err(mem_error_to_wasi)); + let iovs_arr = + wasi_try_ok_ok!(iovs_arr.access().map_err(mem_error_to_wasi)); + for iovs in iovs_arr.iter() { + let buf = wasi_try_ok_ok!(WasmPtr::::new(iovs.buf) + .slice(&memory, iovs.buf_len) + .map_err(mem_error_to_wasi)); + let buf = wasi_try_ok_ok!(buf.access().map_err(mem_error_to_wasi)); + let local_written = + wasi_try_ok_ok!(std::io::Write::write(buffer, buf.as_ref()) + .map_err(map_io_err)); + written += local_written; + if local_written != buf.len() { + break; + } + } + } + FdWriteSource::Buffer(data) => { + wasi_try_ok_ok!( + std::io::Write::write_all(buffer, data).map_err(map_io_err) ); - written += local_written; - if local_written != buf.len() { - break; + written += data.len(); } } - (written, false) + + (written, false, true) } } }; + + #[cfg(feature = "journal")] + if should_snapshot && can_snapshot && bytes_written > 0 { + if let FdWriteSource::Iovs { iovs, iovs_len } = data { + JournalEffector::save_fd_write(ctx, fd, offset, bytes_written, iovs, iovs_len) + .map_err(|err| { + tracing::error!("failed to save terminal data - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + } + env = ctx.data(); memory = unsafe { env.memory_view(&ctx) }; @@ -269,7 +415,7 @@ pub(crate) fn fd_write_internal( if !is_stdio { if can_update_cursor && should_update_cursor { let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); + let fd_entry = wasi_try_ok_ok!(fd_map.get_mut(&fd).ok_or(Errno::Badf)); fd_entry .offset .fetch_add(bytes_written as u64, Ordering::AcqRel); @@ -284,13 +430,6 @@ pub(crate) fn fd_write_internal( } bytes_written }; - Span::current().record("nwritten", bytes_written); - let memory = unsafe { env.memory_view(&ctx) }; - let nwritten_ref = nwritten.deref(&memory); - let bytes_written: M::Offset = - wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); - wasi_try_mem_ok!(nwritten_ref.write(bytes_written)); - - Ok(Errno::Success) + Ok(Ok(bytes_written)) } diff --git a/lib/wasix/src/syscalls/wasi/path_create_directory.rs b/lib/wasix/src/syscalls/wasi/path_create_directory.rs index 473bc0e1f72..8e514f43697 100644 --- a/lib/wasix/src/syscalls/wasi/path_create_directory.rs +++ b/lib/wasix/src/syscalls/wasi/path_create_directory.rs @@ -16,25 +16,15 @@ use crate::syscalls::*; /// This right must be set on the directory that the file is created in (TODO: verify that this is true) #[instrument(level = "trace", skip_all, fields(%fd, path = field::Empty), ret)] pub fn path_create_directory( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, path: WasmPtr, path_len: M::Offset, -) -> Errno { +) -> Result { let env = ctx.data(); let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; - let working_dir = wasi_try!(state.fs.get_fd(fd)); - { - let guard = working_dir.inode.read(); - if let Kind::Root { .. } = guard.deref() { - return Errno::Access; - } - } - if !working_dir.rights.contains(Rights::PATH_CREATE_DIRECTORY) { - return Errno::Access; - } - let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_string = unsafe { get_input_str_ok!(&memory, path, path_len) }; Span::current().record("path", path_string.as_str()); // Convert relative paths into absolute paths @@ -45,8 +35,40 @@ pub fn path_create_directory( ); } - let path = std::path::PathBuf::from(&path_string); - let path_vec = wasi_try!(path + wasi_try_ok!(path_create_directory_internal(&mut ctx, fd, &path_string)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_path_create_directory(&mut ctx, fd, path_string).map_err(|err| { + tracing::error!("failed to save create directory event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn path_create_directory_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + path: &str, +) -> Result<(), Errno> { + let env = ctx.data(); + let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + let working_dir = state.fs.get_fd(fd)?; + { + let guard = working_dir.inode.read(); + if let Kind::Root { .. } = guard.deref() { + return Err(Errno::Access); + } + } + if !working_dir.rights.contains(Rights::PATH_CREATE_DIRECTORY) { + return Err(Errno::Access); + } + + let path = std::path::PathBuf::from(path); + let path_vec = path .components() .map(|comp| { comp.as_os_str() @@ -54,9 +76,9 @@ pub fn path_create_directory( .map(|inner_str| inner_str.to_string()) .ok_or(Errno::Inval) }) - .collect::, Errno>>()); + .collect::, Errno>>()?; if path_vec.is_empty() { - return Errno::Inval; + return Err(Errno::Inval); } let mut cur_dir_inode = working_dir.inode; @@ -96,18 +118,19 @@ pub fn path_create_directory( &adjusted_path.to_string_lossy(), ) { if adjusted_path_stat.st_filetype != Filetype::Directory { - return Errno::Notdir; + return Err(Errno::Notdir); } } else { - wasi_try!(state.fs_create_dir(&adjusted_path)); + state.fs_create_dir(&adjusted_path)?; } let kind = Kind::Dir { parent: cur_dir_inode.downgrade(), path: adjusted_path, entries: Default::default(), }; - let new_inode = - wasi_try!(state.fs.create_inode(inodes, kind, false, comp.to_string())); + let new_inode = state + .fs + .create_inode(inodes, kind, false, comp.to_string())?; // reborrow to insert { @@ -122,10 +145,10 @@ pub fn path_create_directory( cur_dir_inode = new_inode; } } - Kind::Root { .. } => return Errno::Access, - _ => return Errno::Notdir, + Kind::Root { .. } => return Err(Errno::Access), + _ => return Err(Errno::Notdir), } } - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/path_filestat_set_times.rs b/lib/wasix/src/syscalls/wasi/path_filestat_set_times.rs index 93e3accc57a..c471374e20e 100644 --- a/lib/wasix/src/syscalls/wasi/path_filestat_set_times.rs +++ b/lib/wasix/src/syscalls/wasi/path_filestat_set_times.rs @@ -20,7 +20,7 @@ use crate::syscalls::*; /// A bitmask controlling which attributes are set #[instrument(level = "debug", skip_all, fields(%fd, path = field::Empty, %st_atim, %st_mtim), ret)] pub fn path_filestat_set_times( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, flags: LookupFlags, path: WasmPtr, @@ -28,21 +28,11 @@ pub fn path_filestat_set_times( st_atim: Timestamp, st_mtim: Timestamp, fst_flags: Fstflags, -) -> Errno { +) -> Result { let env = ctx.data(); let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; - let fd_entry = wasi_try!(state.fs.get_fd(fd)); - let fd_inode = fd_entry.inode; - if !fd_entry.rights.contains(Rights::PATH_FILESTAT_SET_TIMES) { - return Errno::Access; - } - if (fst_flags.contains(Fstflags::SET_ATIM) && fst_flags.contains(Fstflags::SET_ATIM_NOW)) - || (fst_flags.contains(Fstflags::SET_MTIM) && fst_flags.contains(Fstflags::SET_MTIM_NOW)) - { - return Errno::Inval; - } - let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_string = unsafe { get_input_str_ok!(&memory, path, path_len) }; Span::current().record("path", path_string.as_str()); // Convert relative paths into absolute paths @@ -53,22 +43,73 @@ pub fn path_filestat_set_times( ); } - let file_inode = wasi_try!(state.fs.get_inode_at_path( - inodes, + wasi_try_ok!(path_filestat_set_times_internal( + &mut ctx, fd, + flags, &path_string, - flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, + st_atim, + st_mtim, + fst_flags )); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_path_set_times( + &mut ctx, + fd, + flags, + path_string, + st_atim, + st_mtim, + fst_flags, + ) + .map_err(|err| { + tracing::error!("failed to save file set times event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn path_filestat_set_times_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + flags: LookupFlags, + path: &str, + st_atim: Timestamp, + st_mtim: Timestamp, + fst_flags: Fstflags, +) -> Result<(), Errno> { + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + let fd_entry = state.fs.get_fd(fd)?; + let fd_inode = fd_entry.inode; + if !fd_entry.rights.contains(Rights::PATH_FILESTAT_SET_TIMES) { + return Err(Errno::Access); + } + if (fst_flags.contains(Fstflags::SET_ATIM) && fst_flags.contains(Fstflags::SET_ATIM_NOW)) + || (fst_flags.contains(Fstflags::SET_MTIM) && fst_flags.contains(Fstflags::SET_MTIM_NOW)) + { + return Err(Errno::Inval); + } + + let file_inode = + state + .fs + .get_inode_at_path(inodes, fd, path, flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0)?; let stat = { let guard = file_inode.read(); - wasi_try!(state.fs.get_stat_for_kind(guard.deref())) + state.fs.get_stat_for_kind(guard.deref())? }; if fst_flags.contains(Fstflags::SET_ATIM) || fst_flags.contains(Fstflags::SET_ATIM_NOW) { let time_to_set = if fst_flags.contains(Fstflags::SET_ATIM) { st_atim } else { - wasi_try!(get_current_time_in_nanos()) + get_current_time_in_nanos()? }; fd_inode.stat.write().unwrap().st_atim = time_to_set; } @@ -76,10 +117,10 @@ pub fn path_filestat_set_times( let time_to_set = if fst_flags.contains(Fstflags::SET_MTIM) { st_mtim } else { - wasi_try!(get_current_time_in_nanos()) + get_current_time_in_nanos()? }; fd_inode.stat.write().unwrap().st_mtim = time_to_set; } - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/path_link.rs b/lib/wasix/src/syscalls/wasi/path_link.rs index 56ce45eafa1..9b0388d6cd2 100644 --- a/lib/wasix/src/syscalls/wasi/path_link.rs +++ b/lib/wasix/src/syscalls/wasi/path_link.rs @@ -20,7 +20,7 @@ use crate::syscalls::*; /// Length of the `new_path` string #[instrument(level = "debug", skip_all, fields(%old_fd, %new_fd, old_path = field::Empty, new_path = field::Empty, follow_symlinks = false), ret)] pub fn path_link( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, old_fd: WasiFd, old_flags: LookupFlags, old_path: WasmPtr, @@ -28,64 +28,112 @@ pub fn path_link( new_fd: WasiFd, new_path: WasmPtr, new_path_len: M::Offset, -) -> Errno { +) -> Result { if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { Span::current().record("follow_symlinks", true); } let env = ctx.data(); let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; - let mut old_path_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; + let mut old_path_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) }; Span::current().record("old_path", old_path_str.as_str()); - let mut new_path_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + let mut new_path_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) }; Span::current().record("new_path", new_path_str.as_str()); - let source_fd = wasi_try!(state.fs.get_fd(old_fd)); - let target_fd = wasi_try!(state.fs.get_fd(new_fd)); + + wasi_try_ok!(path_link_internal( + &mut ctx, + old_fd, + old_flags, + &old_path_str, + new_fd, + &new_path_str + )); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_path_link( + &mut ctx, + old_fd, + old_flags, + old_path_str, + new_fd, + new_path_str, + ) + .map_err(|err| { + tracing::error!("failed to save path hard link event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn path_link_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_fd: WasiFd, + old_flags: LookupFlags, + old_path: &str, + new_fd: WasiFd, + new_path: &str, +) -> Result<(), Errno> { + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + let source_fd = state.fs.get_fd(old_fd)?; + let target_fd = state.fs.get_fd(new_fd)?; if !source_fd.rights.contains(Rights::PATH_LINK_SOURCE) || !target_fd.rights.contains(Rights::PATH_LINK_TARGET) { - return Errno::Access; + return Err(Errno::Access); } // Convert relative paths into absolute paths - old_path_str = ctx.data().state.fs.relative_path_to_absolute(old_path_str); - new_path_str = ctx.data().state.fs.relative_path_to_absolute(new_path_str); + let old_path_str = ctx + .data() + .state + .fs + .relative_path_to_absolute(old_path.to_string()); + let new_path_str = ctx + .data() + .state + .fs + .relative_path_to_absolute(new_path.to_string()); - let source_inode = wasi_try!(state.fs.get_inode_at_path( + let source_inode = state.fs.get_inode_at_path( inodes, old_fd, &old_path_str, old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, - )); + )?; let target_path_arg = std::path::PathBuf::from(&new_path_str); let (target_parent_inode, new_entry_name) = - wasi_try!(state + state .fs - .get_parent_inode_at_path(inodes, new_fd, &target_path_arg, false)); + .get_parent_inode_at_path(inodes, new_fd, &target_path_arg, false)?; if source_inode.stat.write().unwrap().st_nlink == Linkcount::max_value() { - return Errno::Mlink; + return Err(Errno::Mlink); } { let mut guard = target_parent_inode.write(); match guard.deref_mut() { Kind::Dir { entries, .. } => { if entries.contains_key(&new_entry_name) { - return Errno::Exist; + return Err(Errno::Exist); } entries.insert(new_entry_name, source_inode.clone()); } - Kind::Root { .. } => return Errno::Inval, + Kind::Root { .. } => return Err(Errno::Inval), Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } | Kind::Socket { .. } | Kind::Pipe { .. } | Kind::EventNotifications { .. } - | Kind::Epoll { .. } => return Errno::Notdir, + | Kind::Epoll { .. } => return Err(Errno::Notdir), } } source_inode.stat.write().unwrap().st_nlink += 1; - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/path_open.rs b/lib/wasix/src/syscalls/wasi/path_open.rs index cb314fd0939..58528347995 100644 --- a/lib/wasix/src/syscalls/wasi/path_open.rs +++ b/lib/wasix/src/syscalls/wasi/path_open.rs @@ -27,7 +27,7 @@ use crate::syscalls::*; /// - `Errno::Access`, `Errno::Badf`, `Errno::Fault`, `Errno::Fbig?`, `Errno::Inval`, `Errno::Io`, `Errno::Loop`, `Errno::Mfile`, `Errno::Nametoolong?`, `Errno::Nfile`, `Errno::Noent`, `Errno::Notdir`, `Errno::Rofs`, and `Errno::Notcapable` #[instrument(level = "debug", skip_all, fields(%dirfd, path = field::Empty, follow_symlinks = field::Empty, ret_fd = field::Empty), ret)] pub fn path_open( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, dirfd: WasiFd, dirflags: LookupFlags, path: WasmPtr, @@ -37,7 +37,7 @@ pub fn path_open( fs_rights_inheriting: Rights, fs_flags: Fdflags, fd: WasmPtr, -) -> Errno { +) -> Result { if dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { Span::current().record("follow_symlinks", true); } @@ -47,26 +47,16 @@ pub fn path_open( /* TODO: find actual upper bound on name size (also this is a path, not a name :think-fish:) */ let path_len64: u64 = path_len.into(); if path_len64 > 1024u64 * 1024u64 { - return Errno::Nametoolong; + return Ok(Errno::Nametoolong); } - let fd_ref = fd.deref(&memory); - // o_flags: // - __WASI_O_CREAT (create if it does not exist) // - __WASI_O_DIRECTORY (fail if not dir) // - __WASI_O_EXCL (fail if file exists) // - __WASI_O_TRUNC (truncate size to 0) - let working_dir = wasi_try!(state.fs.get_fd(dirfd)); - let working_dir_rights_inheriting = working_dir.rights_inheriting; - - // ASSUMPTION: open rights apply recursively - if !working_dir.rights.contains(Rights::PATH_OPEN) { - return Errno::Access; - } - - let mut path_string = unsafe { get_input_str!(&memory, path, path_len) }; + let mut path_string = unsafe { get_input_str_ok!(&memory, path, path_len) }; Span::current().record("path", path_string.as_str()); // Convert relative paths into absolute paths @@ -77,14 +67,79 @@ pub fn path_open( ); } - let path_arg = std::path::PathBuf::from(&path_string); + let out_fd = wasi_try_ok!(path_open_internal( + &mut ctx, + dirfd, + dirflags, + &path_string, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + )?); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_path_open( + &mut ctx, + out_fd, + dirfd, + dirflags, + path_string, + o_flags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + ) + .map_err(|err| { + tracing::error!("failed to save unlink event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + let env = ctx.data(); + let (memory, mut state, mut inodes) = + unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + + Span::current().record("ret_fd", out_fd); + + let fd_ref = fd.deref(&memory); + wasi_try_mem_ok!(fd_ref.write(out_fd)); + + Ok(Errno::Success) +} + +pub(crate) fn path_open_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + dirfd: WasiFd, + dirflags: LookupFlags, + path: &str, + o_flags: Oflags, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + fs_flags: Fdflags, +) -> Result, WasiError> { + let env = ctx.data(); + let (memory, mut state, mut inodes) = + unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + + let path_arg = std::path::PathBuf::from(&path); let maybe_inode = state.fs.get_inode_at_path( inodes, dirfd, - &path_string, + path, dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, ); + let working_dir = wasi_try_ok_ok!(state.fs.get_fd(dirfd)); + let working_dir_rights_inheriting = working_dir.rights_inheriting; + + // ASSUMPTION: open rights apply recursively + if !working_dir.rights.contains(Rights::PATH_OPEN) { + return Ok(Err(Errno::Access)); + } + let mut open_flags = 0; // TODO: traverse rights of dirs properly // COMMENTED OUT: WASI isn't giving appropriate rights here when opening @@ -160,14 +215,13 @@ pub fn path_open( if let Some(special_fd) = fd { // short circuit if we're dealing with a special file assert!(handle.is_some()); - wasi_try_mem!(fd_ref.write(*special_fd)); - return Errno::Success; + return Ok(Ok(*special_fd)); } if o_flags.contains(Oflags::DIRECTORY) { - return Errno::Notdir; + return Ok(Err(Errno::Notdir)); } if o_flags.contains(Oflags::EXCL) { - return Errno::Exist; + return Ok(Err(Errno::Exist)); } let open_options = open_options @@ -188,31 +242,30 @@ pub fn path_open( if minimum_rights.truncate { open_flags |= Fd::TRUNCATE; } - *handle = Some(Arc::new(std::sync::RwLock::new(wasi_try!(open_options - .open(&path) - .map_err(fs_error_into_wasi_err))))); + *handle = Some(Arc::new(std::sync::RwLock::new(wasi_try_ok_ok!( + open_options.open(&path).map_err(fs_error_into_wasi_err) + )))); if let Some(handle) = handle { let handle = handle.read().unwrap(); if let Some(fd) = handle.get_special_fd() { // We clone the file descriptor so that when its closed // nothing bad happens - let dup_fd = wasi_try!(state.fs.clone_fd(fd)); + let dup_fd = wasi_try_ok_ok!(state.fs.clone_fd(fd)); trace!( %dup_fd ); // some special files will return a constant FD rather than // actually open the file (/dev/stdin, /dev/stdout, /dev/stderr) - wasi_try_mem!(fd_ref.write(dup_fd)); - return Errno::Success; + return Ok(Ok(dup_fd)); } } } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), Kind::Root { .. } => { if !o_flags.contains(Oflags::DIRECTORY) { - return Errno::Notcapable; + return Ok(Err(Errno::Notcapable)); } } Kind::Dir { .. } @@ -235,16 +288,17 @@ pub fn path_open( // less-happy path, we have to try to create the file if o_flags.contains(Oflags::CREATE) { if o_flags.contains(Oflags::DIRECTORY) { - return Errno::Notdir; + return Ok(Err(Errno::Notdir)); } // strip end file name - let (parent_inode, new_entity_name) = wasi_try!(state.fs.get_parent_inode_at_path( - inodes, - dirfd, - &path_arg, - dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 - )); + let (parent_inode, new_entity_name) = + wasi_try_ok_ok!(state.fs.get_parent_inode_at_path( + inodes, + dirfd, + &path_arg, + dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 + )); let new_file_host_path = { let guard = parent_inode.read(); match guard.deref() { @@ -258,7 +312,7 @@ pub fn path_open( new_path.push(&new_entity_name); new_path } - _ => return Errno::Inval, + _ => return Ok(Err(Errno::Inval)), } }; // once we got the data we need from the parent, we lookup the host file @@ -283,7 +337,7 @@ pub fn path_open( open_flags |= Fd::TRUNCATE; } - Some(wasi_try!(open_options + Some(wasi_try_ok_ok!(open_options .open(&new_file_host_path) .map_err(|e| { fs_error_into_wasi_err(e) }))) }; @@ -294,7 +348,7 @@ pub fn path_open( path: new_file_host_path, fd: None, }; - wasi_try!(state + wasi_try_ok_ok!(state .fs .create_inode(inodes, kind, false, new_entity_name.clone())) }; @@ -311,13 +365,13 @@ pub fn path_open( new_inode } else { - return maybe_inode.unwrap_err(); + return Ok(Err(maybe_inode.unwrap_err())); } }; // TODO: check and reduce these // TODO: ensure a mutable fd to root can never be opened - let out_fd = wasi_try!(state.fs.create_fd( + let out_fd = wasi_try_ok_ok!(state.fs.create_fd( adjusted_rights, fs_rights_inheriting, fs_flags, @@ -325,8 +379,5 @@ pub fn path_open( inode )); - Span::current().record("ret_fd", out_fd); - - wasi_try_mem!(fd_ref.write(out_fd)); - Errno::Success + Ok(Ok(out_fd)) } diff --git a/lib/wasix/src/syscalls/wasi/path_remove_directory.rs b/lib/wasix/src/syscalls/wasi/path_remove_directory.rs index 6eb72b1786c..d6ac9e67f76 100644 --- a/lib/wasix/src/syscalls/wasi/path_remove_directory.rs +++ b/lib/wasix/src/syscalls/wasi/path_remove_directory.rs @@ -4,7 +4,7 @@ use crate::syscalls::*; /// Returns Errno::Notemtpy if directory is not empty #[instrument(level = "debug", skip_all, fields(%fd, path = field::Empty), ret)] pub fn path_remove_directory( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, path: WasmPtr, path_len: M::Offset, @@ -25,25 +25,47 @@ pub fn path_remove_directory( ); } - let inode = wasi_try!(state.fs.get_inode_at_path(inodes, fd, &path_str, false)); - let (parent_inode, childs_name) = wasi_try!(state.fs.get_parent_inode_at_path( - inodes, - fd, - std::path::Path::new(&path_str), - false - )); + wasi_try!(path_remove_directory_internal(&mut ctx, fd, &path_str)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + wasi_try!( + JournalEffector::save_path_remove_directory(&mut ctx, fd, path_str).map_err(|err| { + tracing::error!("failed to save remove directory event - {}", err); + Errno::Fault + }) + ) + } + + Errno::Success +} + +pub(crate) fn path_remove_directory_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + path: &str, +) -> Result<(), Errno> { + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + + let inode = state.fs.get_inode_at_path(inodes, fd, path, false)?; + let (parent_inode, childs_name) = + state + .fs + .get_parent_inode_at_path(inodes, fd, std::path::Path::new(&path), false)?; let host_path_to_remove = { let guard = inode.read(); match guard.deref() { Kind::Dir { entries, path, .. } => { - if !entries.is_empty() || wasi_try!(state.fs_read_dir(path)).count() != 0 { - return Errno::Notempty; + if !entries.is_empty() || state.fs_read_dir(path)?.count() != 0 { + return Err(Errno::Notempty); } path.clone() } - Kind::Root { .. } => return Errno::Access, - _ => return Errno::Notdir, + Kind::Root { .. } => return Err(Errno::Access), + _ => return Err(Errno::Notdir), } }; @@ -53,17 +75,20 @@ pub fn path_remove_directory( Kind::Dir { ref mut entries, .. } => { - let removed_inode = wasi_try!(entries.remove(&childs_name).ok_or(Errno::Inval)); + let removed_inode = entries.remove(&childs_name).ok_or(Errno::Inval)?; + // TODO: make this a debug assert in the future assert!(inode.ino() == removed_inode.ino()); } - Kind::Root { .. } => return Errno::Access, + Kind::Root { .. } => return Err(Errno::Access), _ => unreachable!( "Internal logic error in wasi::path_remove_directory, parent is not a directory" ), } } + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; if let Err(err) = state.fs_remove_dir(host_path_to_remove) { // reinsert to prevent FS from being in bad state let mut guard = parent_inode.write(); @@ -73,8 +98,8 @@ pub fn path_remove_directory( { entries.insert(childs_name, inode); } - return err; + return Err(err); } - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/path_rename.rs b/lib/wasix/src/syscalls/wasi/path_rename.rs index 50e86a90bf5..147c8f209ac 100644 --- a/lib/wasix/src/syscalls/wasi/path_rename.rs +++ b/lib/wasix/src/syscalls/wasi/path_rename.rs @@ -18,7 +18,7 @@ use crate::syscalls::*; /// The number of bytes to read from `new_path` #[instrument(level = "debug", skip_all, fields(%old_fd, %new_fd, old_path = field::Empty, new_path = field::Empty), ret)] pub fn path_rename( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, old_fd: WasiFd, old_path: WasmPtr, old_path_len: M::Offset, @@ -31,43 +31,67 @@ pub fn path_rename( let mut source_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) }; Span::current().record("old_path", source_str.as_str()); source_str = ctx.data().state.fs.relative_path_to_absolute(source_str); - let source_path = std::path::Path::new(&source_str); let mut target_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) }; Span::current().record("new_path", target_str.as_str()); target_str = ctx.data().state.fs.relative_path_to_absolute(target_str); - let target_path = std::path::Path::new(&target_str); + + let ret = path_rename_internal(&mut ctx, old_fd, &source_str, new_fd, &target_str)?; + let env = ctx.data(); + + if ret == Errno::Success { + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_path_rename(&mut ctx, old_fd, source_str, new_fd, target_str) + .map_err(|err| { + tracing::error!("failed to save path rename event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + } + Ok(ret) +} + +pub fn path_rename_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + source_fd: WasiFd, + source_path: &str, + target_fd: WasiFd, + target_path: &str, +) -> Result { + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; { - let source_fd = wasi_try_ok!(state.fs.get_fd(old_fd)); + let source_fd = wasi_try_ok!(state.fs.get_fd(source_fd)); if !source_fd.rights.contains(Rights::PATH_RENAME_SOURCE) { return Ok(Errno::Access); } - let target_fd = wasi_try_ok!(state.fs.get_fd(new_fd)); + let target_fd = wasi_try_ok!(state.fs.get_fd(target_fd)); if !target_fd.rights.contains(Rights::PATH_RENAME_TARGET) { return Ok(Errno::Access); } } // this is to be sure the source file is fetch from filesystem if needed - wasi_try_ok!(state.fs.get_inode_at_path( + wasi_try_ok!(state + .fs + .get_inode_at_path(inodes, source_fd, source_path, true)); + // Create the destination inode if the file exists. + let _ = state + .fs + .get_inode_at_path(inodes, target_fd, target_path, true); + let (source_parent_inode, source_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path( inodes, - old_fd, - source_path.to_str().as_ref().unwrap(), + source_fd, + Path::new(source_path), + true + )); + let (target_parent_inode, target_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path( + inodes, + target_fd, + Path::new(target_path), true )); - // Create the destination inode if the file exists. - let _ = - state - .fs - .get_inode_at_path(inodes, new_fd, target_path.to_str().as_ref().unwrap(), true); - let (source_parent_inode, source_entry_name) = - wasi_try_ok!(state - .fs - .get_parent_inode_at_path(inodes, old_fd, source_path, true)); - let (target_parent_inode, target_entry_name) = - wasi_try_ok!(state - .fs - .get_parent_inode_at_path(inodes, new_fd, target_path, true)); let mut need_create = true; let host_adjusted_target_path = { let guard = target_parent_inode.read(); diff --git a/lib/wasix/src/syscalls/wasi/path_symlink.rs b/lib/wasix/src/syscalls/wasi/path_symlink.rs index e39619d57a4..66a45988777 100644 --- a/lib/wasix/src/syscalls/wasi/path_symlink.rs +++ b/lib/wasix/src/syscalls/wasi/path_symlink.rs @@ -16,32 +16,62 @@ use crate::syscalls::*; /// The number of bytes to read from `new_path` #[instrument(level = "debug", skip_all, fields(%fd, old_path = field::Empty, new_path = field::Empty), ret)] pub fn path_symlink( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, old_path: WasmPtr, old_path_len: M::Offset, fd: WasiFd, new_path: WasmPtr, new_path_len: M::Offset, -) -> Errno { +) -> Result { let env = ctx.data(); let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; - let mut old_path_str = unsafe { get_input_str!(&memory, old_path, old_path_len) }; + let mut old_path_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) }; Span::current().record("old_path", old_path_str.as_str()); - let mut new_path_str = unsafe { get_input_str!(&memory, new_path, new_path_len) }; + let mut new_path_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) }; Span::current().record("new_path", new_path_str.as_str()); old_path_str = ctx.data().state.fs.relative_path_to_absolute(old_path_str); new_path_str = ctx.data().state.fs.relative_path_to_absolute(new_path_str); - let base_fd = wasi_try!(state.fs.get_fd(fd)); + + wasi_try_ok!(path_symlink_internal( + &mut ctx, + &old_path_str, + fd, + &new_path_str + )); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_path_symlink(&mut ctx, old_path_str, fd, new_path_str).map_err( + |err| { + tracing::error!("failed to save path symbolic link event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + }, + )?; + } + + Ok(Errno::Success) +} + +pub fn path_symlink_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + old_path: &str, + fd: WasiFd, + new_path: &str, +) -> Result<(), Errno> { + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + + let base_fd = state.fs.get_fd(fd)?; if !base_fd.rights.contains(Rights::PATH_SYMLINK) { - return Errno::Access; + return Err(Errno::Access); } // get the depth of the parent + 1 (UNDER INVESTIGATION HMMMMMMMM THINK FISH ^ THINK FISH) - let old_path_path = std::path::Path::new(&old_path_str); - let (source_inode, _) = - wasi_try!(state - .fs - .get_parent_inode_at_path(inodes, fd, old_path_path, true)); + let old_path_path = std::path::Path::new(old_path); + let (source_inode, _) = state + .fs + .get_parent_inode_at_path(inodes, fd, old_path_path, true)?; let depth = state.fs.path_depth_from_fd(fd, source_inode); // depth == -1 means folder is not relative. See issue #3233. @@ -50,11 +80,11 @@ pub fn path_symlink( Err(_) => -1, }; - let new_path_path = std::path::Path::new(&new_path_str); + let new_path_path = std::path::Path::new(new_path); let (target_parent_inode, entry_name) = - wasi_try!(state + state .fs - .get_parent_inode_at_path(inodes, fd, new_path_path, true)); + .get_parent_inode_at_path(inodes, fd, new_path_path, true)?; // short circuit if anything is wrong, before we create an inode { @@ -62,21 +92,21 @@ pub fn path_symlink( match guard.deref() { Kind::Dir { entries, .. } => { if entries.contains_key(&entry_name) { - return Errno::Exist; + return Err(Errno::Exist); } } - Kind::Root { .. } => return Errno::Notcapable, + Kind::Root { .. } => return Err(Errno::Notcapable), Kind::Socket { .. } | Kind::Pipe { .. } | Kind::EventNotifications { .. } - | Kind::Epoll { .. } => return Errno::Inval, + | Kind::Epoll { .. } => return Err(Errno::Inval), Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => { unreachable!("get_parent_inode_at_path returned something other than a Dir or Root") } } } - let mut source_path = std::path::Path::new(&old_path_str); + let mut source_path = std::path::Path::new(old_path); let mut relative_path = std::path::PathBuf::new(); for _ in 0..depth { relative_path.push(".."); @@ -85,7 +115,7 @@ pub fn path_symlink( let kind = Kind::Symlink { base_po_dir: fd, - path_to_symlink: std::path::PathBuf::from(new_path_str), + path_to_symlink: std::path::PathBuf::from(new_path), relative_path, }; let new_inode = @@ -103,5 +133,5 @@ pub fn path_symlink( } } - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasi/path_unlink_file.rs b/lib/wasix/src/syscalls/wasi/path_unlink_file.rs index c9711c70728..a0f4d15ac83 100644 --- a/lib/wasix/src/syscalls/wasi/path_unlink_file.rs +++ b/lib/wasix/src/syscalls/wasi/path_unlink_file.rs @@ -12,7 +12,7 @@ use crate::syscalls::*; /// The number of bytes in the `path` array #[instrument(level = "debug", skip_all, fields(%fd, path = field::Empty), ret)] pub fn path_unlink_file( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, path: WasmPtr, path_len: M::Offset, @@ -32,11 +32,37 @@ pub fn path_unlink_file( path_str = ctx.data().state.fs.relative_path_to_absolute(path_str); } - let inode = wasi_try_ok!(state.fs.get_inode_at_path(inodes, fd, &path_str, false)); + let ret = path_unlink_file_internal(&mut ctx, fd, &path_str)?; + let env = ctx.data(); + + if ret == Errno::Success { + #[cfg(feature = "journal")] + if env.enable_journal { + wasi_try_ok!( + JournalEffector::save_path_unlink(&mut ctx, fd, path_str).map_err(|err| { + tracing::error!("failed to save unlink event - {}", err); + Errno::Fault + }) + ) + } + } + + Ok(ret) +} + +pub(crate) fn path_unlink_file_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, + path: &str, +) -> Result { + let env = ctx.data(); + let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + + let inode = wasi_try_ok!(state.fs.get_inode_at_path(inodes, fd, path, false)); let (parent_inode, childs_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path( inodes, fd, - std::path::Path::new(&path_str), + std::path::Path::new(path), false )); diff --git a/lib/wasix/src/syscalls/wasi/poll_oneoff.rs b/lib/wasix/src/syscalls/wasi/poll_oneoff.rs index 2c9a743afb0..a4957a49d0e 100644 --- a/lib/wasix/src/syscalls/wasi/poll_oneoff.rs +++ b/lib/wasix/src/syscalls/wasi/poll_oneoff.rs @@ -64,6 +64,8 @@ pub fn poll_oneoff( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + ctx.data_mut().poll_seed += 1; let mut env = ctx.data(); let mut memory = unsafe { env.memory_view(&ctx) }; diff --git a/lib/wasix/src/syscalls/wasix/chdir.rs b/lib/wasix/src/syscalls/wasix/chdir.rs index 2816c8c7ee8..beb4b93a75d 100644 --- a/lib/wasix/src/syscalls/wasix/chdir.rs +++ b/lib/wasix/src/syscalls/wasix/chdir.rs @@ -5,20 +5,38 @@ use crate::syscalls::*; /// Sets the current working directory #[instrument(level = "debug", skip_all, fields(name = field::Empty), ret)] pub fn chdir( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, path: WasmPtr, path_len: M::Offset, -) -> Errno { +) -> Result { let env = ctx.data(); let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; - let path = unsafe { get_input_str!(&memory, path, path_len) }; + let path = unsafe { get_input_str_ok!(&memory, path, path_len) }; Span::current().record("path", path.as_str()); + wasi_try_ok!(chdir_internal(&mut ctx, &path,)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_chdir(&mut ctx, path).map_err(|err| { + tracing::error!("failed to chdir event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub fn chdir_internal(ctx: &mut FunctionEnvMut<'_, WasiEnv>, path: &str) -> Result<(), Errno> { + let env = ctx.data(); + let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(ctx, 0) }; + // Check if the directory exists - if state.fs.root_fs.read_dir(Path::new(path.as_str())).is_err() { - return Errno::Noent; + if state.fs.root_fs.read_dir(Path::new(path)).is_err() { + return Err(Errno::Noent); } - state.fs.set_current_dir(path.as_str()); - Errno::Success + state.fs.set_current_dir(path); + Ok(()) } diff --git a/lib/wasix/src/syscalls/wasix/epoll_create.rs b/lib/wasix/src/syscalls/wasix/epoll_create.rs index 9296f41792e..cf9d942176f 100644 --- a/lib/wasix/src/syscalls/wasix/epoll_create.rs +++ b/lib/wasix/src/syscalls/wasix/epoll_create.rs @@ -18,7 +18,30 @@ pub fn epoll_create( mut ctx: FunctionEnvMut<'_, WasiEnv>, ret_fd: WasmPtr, ) -> Result { - wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + let fd = wasi_try_ok!(epoll_create_internal(&mut ctx)?); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_epoll_create(&mut ctx, fd).map_err(|err| { + tracing::error!("failed to save epoll_create event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Span::current().record("fd", fd); + + let env = ctx.data(); + let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + wasi_try_mem_ok!(ret_fd.write(&memory, fd)); + + Ok(Errno::Success) +} + +pub fn epoll_create_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result, WasiError> { + wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?); let env = ctx.data(); let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; @@ -37,12 +60,9 @@ pub fn epoll_create( ); let rights = Rights::POLL_FD_READWRITE | Rights::FD_FDSTAT_SET_FLAGS; - let fd = wasi_try_ok!(state + let fd = wasi_try_ok_ok!(state .fs .create_fd(rights, rights, Fdflags::empty(), 0, inode)); - Span::current().record("fd", fd); - - wasi_try_mem_ok!(ret_fd.write(&memory, fd)); - Ok(Errno::Success) + Ok(Ok(fd)) } diff --git a/lib/wasix/src/syscalls/wasix/epoll_ctl.rs b/lib/wasix/src/syscalls/wasix/epoll_ctl.rs index 16946c5c5cc..06ff54159af 100644 --- a/lib/wasix/src/syscalls/wasix/epoll_ctl.rs +++ b/lib/wasix/src/syscalls/wasix/epoll_ctl.rs @@ -3,7 +3,7 @@ use tokio::sync::{mpsc::UnboundedSender, watch}; use virtual_mio::{InterestHandler, InterestType}; use virtual_net::net_error_into_io_err; use wasmer_wasix_types::wasi::{ - EpollCtl, EpollEvent, EpollType, SubscriptionClock, SubscriptionUnion, Userdata, + EpollCtl, EpollEvent, EpollEventCtl, EpollType, SubscriptionClock, SubscriptionUnion, Userdata, }; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; @@ -43,7 +43,43 @@ pub fn epoll_ctl( None }; - let fd_entry = wasi_try_ok!(env.state.fs.get_fd(epfd)); + let event_ctl = event.map(|evt| EpollEventCtl { + events: evt.events, + ptr: evt.data.ptr.into(), + fd: evt.data.fd, + data1: evt.data.data1, + data2: evt.data.data2, + }); + + wasi_try_ok!(epoll_ctl_internal( + &mut ctx, + epfd, + op, + fd, + event_ctl.as_ref() + )?); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_epoll_ctl(&mut ctx, epfd, op, fd, event_ctl).map_err(|err| { + tracing::error!("failed to save epoll_create event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn epoll_ctl_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + epfd: WasiFd, + op: EpollCtl, + fd: WasiFd, + event_ctl: Option<&EpollEventCtl>, +) -> Result, WasiError> { + let env = ctx.data(); + let fd_entry = wasi_try_ok_ok!(env.state.fs.get_fd(epfd)); let tasks = env.tasks().clone(); let mut inode_guard = fd_entry.inode.read(); @@ -58,22 +94,22 @@ pub fn epoll_ctl( tracing::trace!(fd, "unregistering waker"); } if let EpollCtl::Add | EpollCtl::Mod = op { - if let Some(event) = event { + if let Some(event) = event_ctl { let epoll_fd = EpollFd { events: event.events, - ptr: wasi_try_ok!(event.data.ptr.try_into().map_err(|_| Errno::Overflow)), - fd: event.data.fd, - data1: event.data.data1, - data2: event.data.data2, + ptr: event.ptr, + fd: event.fd, + data1: event.data1, + data2: event.data2, }; // Output debug tracing::trace!( peb = ?event.events, - ptr = ?event.data.ptr, - data1 = event.data.data1, - data2 = event.data.data2, - fd = event.data.fd, + ptr = ?event.ptr, + data1 = event.data1, + data2 = event.data2, + fd = event.fd, "registering waker" ); @@ -81,24 +117,24 @@ pub fn epoll_ctl( // We have to register the subscription before we register the waker // as otherwise there is a race condition let mut guard = subscriptions.lock().unwrap(); - guard.insert(event.data.fd, (epoll_fd.clone(), Vec::new())); + guard.insert(event.fd, (epoll_fd.clone(), Vec::new())); } // Now we register the epoll waker let tx = tx.clone(); let mut fd_guards = - wasi_try_ok!(register_epoll_waker(&env.state, &epoll_fd, tx)); + wasi_try_ok_ok!(register_epoll_waker(&env.state, &epoll_fd, tx)); // After the guards are created we need to attach them to the subscription let mut guard = subscriptions.lock().unwrap(); - if let Some(subs) = guard.get_mut(&event.data.fd) { + if let Some(subs) = guard.get_mut(&event.fd) { subs.1.append(&mut fd_guards); } } } - Ok(Errno::Success) + Ok(Ok(())) } - _ => Ok(Errno::Inval), + _ => Ok(Err(Errno::Inval)), } } diff --git a/lib/wasix/src/syscalls/wasix/epoll_wait.rs b/lib/wasix/src/syscalls/wasix/epoll_wait.rs index 37c7dc75671..c3ea56dea51 100644 --- a/lib/wasix/src/syscalls/wasix/epoll_wait.rs +++ b/lib/wasix/src/syscalls/wasix/epoll_wait.rs @@ -26,6 +26,8 @@ pub fn epoll_wait<'a, M: MemorySize + 'static>( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + if timeout == TIMEOUT_FOREVER { tracing::trace!(maxevents, epfd, "waiting forever on wakers"); } else { diff --git a/lib/wasix/src/syscalls/wasix/fd_pipe.rs b/lib/wasix/src/syscalls/wasix/fd_pipe.rs index 6e74b37a6d2..7752053d066 100644 --- a/lib/wasix/src/syscalls/wasix/fd_pipe.rs +++ b/lib/wasix/src/syscalls/wasix/fd_pipe.rs @@ -12,13 +12,35 @@ use crate::syscalls::*; /// Second file handle that represents the other end of the pipe #[instrument(level = "trace", skip_all, fields(fd1 = field::Empty, fd2 = field::Empty), ret)] pub fn fd_pipe( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, ro_fd1: WasmPtr, ro_fd2: WasmPtr, -) -> Errno { +) -> Result { + let (fd1, fd2) = wasi_try_ok!(fd_pipe_internal(&mut ctx)); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_fd_pipe(&mut ctx, fd1, fd2).map_err(|err| { + tracing::error!("failed to save create pipe event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + let env = ctx.data(); let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + Span::current().record("fd1", fd1).record("fd2", fd2); + + wasi_try_mem_ok!(ro_fd1.write(&memory, fd1)); + wasi_try_mem_ok!(ro_fd2.write(&memory, fd2)); + + Ok(Errno::Success) +} + +pub fn fd_pipe_internal(ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> Result<(WasiFd, WasiFd), Errno> { + let env = ctx.data(); + let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; let (pipe1, pipe2) = Pipe::channel(); let inode1 = state.fs.create_inode_with_default_stat( @@ -41,16 +63,12 @@ pub fn fd_pipe( | Rights::POLL_FD_READWRITE | Rights::SOCK_SEND | Rights::FD_FDSTAT_SET_FLAGS; - let fd1 = wasi_try!(state + let fd1 = state .fs - .create_fd(rights, rights, Fdflags::empty(), 0, inode1)); - let fd2 = wasi_try!(state + .create_fd(rights, rights, Fdflags::empty(), 0, inode1)?; + let fd2 = state .fs - .create_fd(rights, rights, Fdflags::empty(), 0, inode2)); - Span::current().record("fd1", fd1).record("fd2", fd2); - - wasi_try_mem!(ro_fd1.write(&memory, fd1)); - wasi_try_mem!(ro_fd2.write(&memory, fd2)); + .create_fd(rights, rights, Fdflags::empty(), 0, inode2)?; - Errno::Success + Ok((fd1, fd2)) } diff --git a/lib/wasix/src/syscalls/wasix/futex_wait.rs b/lib/wasix/src/syscalls/wasix/futex_wait.rs index a66889289e9..241d026d8dc 100644 --- a/lib/wasix/src/syscalls/wasix/futex_wait.rs +++ b/lib/wasix/src/syscalls/wasix/futex_wait.rs @@ -90,6 +90,8 @@ pub(super) fn futex_wait_internal( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + // If we were just restored then we were woken after a deep sleep // and thus we repeat all the checks again, we do not immediately // exit here as it could be the case that we were woken but the diff --git a/lib/wasix/src/syscalls/wasix/port_addr_add.rs b/lib/wasix/src/syscalls/wasix/port_addr_add.rs index 5feabed467b..5a9d5909a66 100644 --- a/lib/wasix/src/syscalls/wasix/port_addr_add.rs +++ b/lib/wasix/src/syscalls/wasix/port_addr_add.rs @@ -1,3 +1,5 @@ +use virtual_net::IpCidr; + use super::*; use crate::syscalls::*; @@ -18,11 +20,29 @@ pub fn port_addr_add( let cidr = wasi_try_ok!(crate::net::read_cidr(&memory, ip)); Span::current().record("ip", &format!("{:?}", cidr)); + wasi_try_ok!(port_addr_add_internal(&mut ctx, cidr)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_addr_add(&mut ctx, cidr).map_err(|err| { + tracing::error!("failed to save port_addr_add event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_addr_add_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + cidr: IpCidr, +) -> Result, WasiError> { + let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.ip_add(cidr.ip, cidr.prefix) .await .map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_addr_clear.rs b/lib/wasix/src/syscalls/wasix/port_addr_clear.rs index a1fec934330..9ceb6c45c66 100644 --- a/lib/wasix/src/syscalls/wasix/port_addr_clear.rs +++ b/lib/wasix/src/syscalls/wasix/port_addr_clear.rs @@ -5,10 +5,26 @@ use crate::syscalls::*; /// Clears all the addresses on the local port #[instrument(level = "debug", skip_all, ret)] pub fn port_addr_clear(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result { + wasi_try_ok!(port_addr_clear_internal(&mut ctx)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_addr_clear(&mut ctx).map_err(|err| { + tracing::error!("failed to save port_addr_clear event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_addr_clear_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result, WasiError> { let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.ip_clear().await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_addr_remove.rs b/lib/wasix/src/syscalls/wasix/port_addr_remove.rs index accae6cba6f..eb689243576 100644 --- a/lib/wasix/src/syscalls/wasix/port_addr_remove.rs +++ b/lib/wasix/src/syscalls/wasix/port_addr_remove.rs @@ -18,9 +18,27 @@ pub fn port_addr_remove( let ip = wasi_try_ok!(crate::net::read_ip(&memory, ip)); Span::current().record("ip", &format!("{:?}", ip)); + wasi_try_ok!(port_addr_remove_internal(&mut ctx, ip)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_addr_remove(&mut ctx, ip).map_err(|err| { + tracing::error!("failed to save port_addr_remove event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_addr_remove_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, +) -> Result, WasiError> { + let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.ip_remove(ip).await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_bridge.rs b/lib/wasix/src/syscalls/wasix/port_bridge.rs index 1a428af3c24..e686ec5d078 100644 --- a/lib/wasix/src/syscalls/wasix/port_bridge.rs +++ b/lib/wasix/src/syscalls/wasix/port_bridge.rs @@ -33,11 +33,37 @@ pub fn port_bridge( _ => return Ok(Errno::Inval), }; + wasi_try_ok!(port_bridge_internal( + &mut ctx, + network.as_str(), + token.as_str(), + security + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_bridge(&mut ctx, network, token, security).map_err(|err| { + tracing::error!("failed to save port_bridge event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_bridge_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + network: &str, + token: &str, + security: StreamSecurity, +) -> Result, WasiError> { + let env = ctx.data(); + let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async move { - net.bridge(network.as_str(), token.as_str(), security) + wasi_try_ok_ok!(__asyncify(ctx, None, async move { + net.bridge(network, token, security) .await .map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_dhcp_acquire.rs b/lib/wasix/src/syscalls/wasix/port_dhcp_acquire.rs index 3a7a534ee73..1415f76c609 100644 --- a/lib/wasix/src/syscalls/wasix/port_dhcp_acquire.rs +++ b/lib/wasix/src/syscalls/wasix/port_dhcp_acquire.rs @@ -5,11 +5,27 @@ use crate::syscalls::*; /// Acquires a set of IP addresses using DHCP #[instrument(level = "debug", skip_all, ret)] pub fn port_dhcp_acquire(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result { + wasi_try_ok!(port_dhcp_acquire_internal(&mut ctx)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_dhcp_acquire(&mut ctx).map_err(|err| { + tracing::error!("failed to save port_dhcp_acquire event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_dhcp_acquire_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result, WasiError> { let env = ctx.data(); let net = env.net().clone(); let tasks = env.tasks().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async move { + wasi_try_ok_ok!(__asyncify(ctx, None, async move { net.dhcp_acquire().await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_gateway_set.rs b/lib/wasix/src/syscalls/wasix/port_gateway_set.rs index 0c9136a4af3..fc0549ea2c1 100644 --- a/lib/wasix/src/syscalls/wasix/port_gateway_set.rs +++ b/lib/wasix/src/syscalls/wasix/port_gateway_set.rs @@ -18,9 +18,27 @@ pub fn port_gateway_set( let ip = wasi_try_ok!(crate::net::read_ip(&memory, ip)); Span::current().record("ip", &format!("{:?}", ip)); + wasi_try_ok!(port_gateway_set_internal(&mut ctx, ip)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_gateway_set(&mut ctx, ip).map_err(|err| { + tracing::error!("failed to save port_gateway_set event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_gateway_set_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, +) -> Result, WasiError> { + let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.gateway_set(ip).await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_route_add.rs b/lib/wasix/src/syscalls/wasix/port_route_add.rs index ccad1ca5a3f..00e9f781c34 100644 --- a/lib/wasix/src/syscalls/wasix/port_route_add.rs +++ b/lib/wasix/src/syscalls/wasix/port_route_add.rs @@ -1,3 +1,5 @@ +use virtual_net::IpCidr; + use super::*; use crate::syscalls::*; @@ -33,11 +35,46 @@ pub fn port_route_add( _ => return Ok(Errno::Inval), }; + wasi_try_ok!(port_route_add_internal( + &mut ctx, + cidr, + via_router, + preferred_until, + expires_at + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_route_add( + &mut ctx, + cidr, + via_router, + preferred_until, + expires_at, + ) + .map_err(|err| { + tracing::error!("failed to save port_route_add event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_route_add_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + cidr: IpCidr, + via_router: IpAddr, + preferred_until: Option, + expires_at: Option, +) -> Result, WasiError> { + let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.route_add(cidr, via_router, preferred_until, expires_at) .await .map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_route_clear.rs b/lib/wasix/src/syscalls/wasix/port_route_clear.rs index ba2ea4ab04a..3eca5de4208 100644 --- a/lib/wasix/src/syscalls/wasix/port_route_clear.rs +++ b/lib/wasix/src/syscalls/wasix/port_route_clear.rs @@ -5,10 +5,26 @@ use crate::syscalls::*; /// Clears all the routes in the local port #[instrument(level = "debug", skip_all, ret)] pub fn port_route_clear(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result { + wasi_try_ok!(port_route_clear_internal(&mut ctx)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_route_clear(&mut ctx).map_err(|err| { + tracing::error!("failed to save port_route_clear event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_route_clear_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result, WasiError> { let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.route_clear().await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_route_remove.rs b/lib/wasix/src/syscalls/wasix/port_route_remove.rs index 46e1b9d2335..0b1b5da2923 100644 --- a/lib/wasix/src/syscalls/wasix/port_route_remove.rs +++ b/lib/wasix/src/syscalls/wasix/port_route_remove.rs @@ -14,10 +14,28 @@ pub fn port_route_remove( let ip = wasi_try_ok!(crate::net::read_ip(&memory, ip)); Span::current().record("ip", &format!("{:?}", ip)); + wasi_try_ok!(port_route_remove_internal(&mut ctx, ip)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_route_remove(&mut ctx, ip).map_err(|err| { + tracing::error!("failed to save port_route_remove event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_route_remove_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + ip: IpAddr, +) -> Result, WasiError> { + let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async { + wasi_try_ok_ok!(__asyncify(ctx, None, async { net.route_remove(ip).await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/port_unbridge.rs b/lib/wasix/src/syscalls/wasix/port_unbridge.rs index 6cc4085a2a6..4495cd04862 100644 --- a/lib/wasix/src/syscalls/wasix/port_unbridge.rs +++ b/lib/wasix/src/syscalls/wasix/port_unbridge.rs @@ -5,10 +5,26 @@ use crate::syscalls::*; /// Disconnects from a remote network #[instrument(level = "debug", skip_all, ret)] pub fn port_unbridge(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result { + wasi_try_ok!(port_unbridge_internal(&mut ctx)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_port_unbridge(&mut ctx).map_err(|err| { + tracing::error!("failed to save port_unbridge event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn port_unbridge_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, +) -> Result, WasiError> { let env = ctx.data(); let net = env.net().clone(); - wasi_try_ok!(__asyncify(&mut ctx, None, async move { + wasi_try_ok_ok!(__asyncify(ctx, None, async move { net.unbridge().await.map_err(net_error_into_wasi_err) })?); - Ok(Errno::Success) + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/proc_fork.rs b/lib/wasix/src/syscalls/wasix/proc_fork.rs index 8a2571f73ac..9bcdc337312 100644 --- a/lib/wasix/src/syscalls/wasix/proc_fork.rs +++ b/lib/wasix/src/syscalls/wasix/proc_fork.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - capture_snapshot, + capture_instance_snapshot, os::task::OwnedTaskStatus, runtime::task_manager::{TaskWasm, TaskWasmRunProperties}, syscalls::*, @@ -62,7 +62,7 @@ pub fn proc_fork( // We write a zero to the PID before we capture the stack // so that this is what will be returned to the child { - let mut inner = ctx.data().process.inner.write().unwrap(); + let mut inner = ctx.data().process.lock(); inner.children.push(child_env.process.clone()); } let env = ctx.data(); @@ -84,9 +84,10 @@ pub fn proc_fork( // Perform the unwind action return unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| { // Grab all the globals and serialize them - let store_data = crate::utils::store::capture_snapshot(&mut ctx.as_store_mut()) - .serialize() - .unwrap(); + let store_data = + crate::utils::store::capture_instance_snapshot(&mut ctx.as_store_mut()) + .serialize() + .unwrap(); let store_data = Bytes::from(store_data); // We first fork the environment and replace the current environment @@ -129,7 +130,7 @@ pub fn proc_fork( let bin_factory = env.bin_factory.clone(); // Perform the unwind action - let snapshot = capture_snapshot(&mut ctx.as_store_mut()); + let snapshot = capture_instance_snapshot(&mut ctx.as_store_mut()); unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| { let tasks = ctx.data().tasks().clone(); let span = debug_span!( @@ -246,12 +247,13 @@ fn run( // If we need to rewind then do so if let Some((rewind_state, rewind_result)) = rewind_state { + let mut ctx = ctx.env.clone().into_mut(&mut store); let res = rewind_ext::( - ctx.env.clone().into_mut(&mut store), + &mut ctx, rewind_state.memory_stack, rewind_state.rewind_stack, rewind_state.store_data, - rewind_result, + Some(rewind_result), ); if res != Errno::Success { return res.into(); @@ -305,7 +307,7 @@ fn run( trace!(%pid, %tid, "child exited (code = {})", ret); // Clean up the environment and return the result - ctx.cleanup((&mut store), Some(ret)); + ctx.on_exit((&mut store), Some(ret)); // We drop the handle at the last moment which will close the thread drop(child_handle); diff --git a/lib/wasix/src/syscalls/wasix/proc_join.rs b/lib/wasix/src/syscalls/wasix/proc_join.rs index b544afab3ca..a8b29b53344 100644 --- a/lib/wasix/src/syscalls/wasix/proc_join.rs +++ b/lib/wasix/src/syscalls/wasix/proc_join.rs @@ -38,6 +38,8 @@ pub(super) fn proc_join_internal( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + // This lambda will look at what we wrote in the status variable // and use this to determine the return code sent back to the caller let ret_result = { @@ -155,7 +157,7 @@ pub(super) fn proc_join_internal( // Waiting for a process that is an explicit child will join it // meaning it will no longer be a sub-process of the main process let mut process = { - let mut inner = ctx.data().process.inner.write().unwrap(); + let mut inner = ctx.data().process.lock(); let process = inner .children .iter() diff --git a/lib/wasix/src/syscalls/wasix/proc_spawn.rs b/lib/wasix/src/syscalls/wasix/proc_spawn.rs index 2646878bf01..bbec1b3aa40 100644 --- a/lib/wasix/src/syscalls/wasix/proc_spawn.rs +++ b/lib/wasix/src/syscalls/wasix/proc_spawn.rs @@ -100,7 +100,7 @@ pub fn proc_spawn_internal( stdin: WasiStdioMode, stdout: WasiStdioMode, stderr: WasiStdioMode, -) -> Result), Errno>, WasiError> { +) -> WasiResult<(ProcessHandles, FunctionEnvMut<'_, WasiEnv>)> { let env = ctx.data(); // Build a new store that will be passed to the thread @@ -245,7 +245,7 @@ pub fn proc_spawn_internal( // Add the process to the environment state { - let mut inner = ctx.data().process.inner.write().unwrap(); + let mut inner = ctx.data().process.lock(); inner.children.push(child_process); } let env = ctx.data(); diff --git a/lib/wasix/src/syscalls/wasix/sock_accept.rs b/lib/wasix/src/syscalls/wasix/sock_accept.rs index ce17a0f6c54..9ce6b75a63c 100644 --- a/lib/wasix/src/syscalls/wasix/sock_accept.rs +++ b/lib/wasix/src/syscalls/wasix/sock_accept.rs @@ -24,12 +24,14 @@ pub fn sock_accept( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + let env = ctx.data(); let (memory, state, _) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; let nonblocking = fd_flags.contains(Fdflags::NONBLOCK); - let (fd, addr) = wasi_try_ok!(sock_accept_internal(env, sock, fd_flags, nonblocking)); + let (fd, addr) = wasi_try_ok!(sock_accept_internal(env, sock, fd_flags, nonblocking)?); wasi_try_mem_ok!(ro_fd.write(&memory, fd)); @@ -64,8 +66,19 @@ pub fn sock_accept_v2( let nonblocking = fd_flags.contains(Fdflags::NONBLOCK); - let (fd, addr) = wasi_try_ok!(sock_accept_internal(env, sock, fd_flags, nonblocking)); + let (fd, addr) = wasi_try_ok!(sock_accept_internal(env, sock, fd_flags, nonblocking)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_accepted(&mut ctx, sock, fd, addr, fd_flags, nonblocking) + .map_err(|err| { + tracing::error!("failed to save sock_accepted event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + let env = ctx.data(); + let (memory, state, _) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; wasi_try_mem_ok!(ro_fd.write(&memory, fd)); wasi_try_ok!(crate::net::write_ip_port( &memory, @@ -77,17 +90,17 @@ pub fn sock_accept_v2( Ok(Errno::Success) } -pub fn sock_accept_internal( +pub(crate) fn sock_accept_internal( env: &WasiEnv, sock: WasiFd, mut fd_flags: Fdflags, mut nonblocking: bool, -) -> Result<(WasiFd, SocketAddr), Errno> { +) -> Result, WasiError> { let state = env.state(); let inodes = &state.inodes; let tasks = env.tasks().clone(); - let (child, addr, fd_flags) = __sock_asyncify( + let (child, addr, fd_flags) = wasi_try_ok_ok!(__sock_asyncify( env, sock, Rights::SOCK_ACCEPT, @@ -106,15 +119,14 @@ pub fn sock_accept_internal( .await .map(|a| (a.0, a.1, fd_flags)) }, - )?; + )); let kind = Kind::Socket { socket: InodeSocket::new(InodeSocketKind::TcpStream { socket: child, write_timeout: None, read_timeout: None, - }) - .map_err(net_error_into_wasi_err)?, + }), }; let inode = state .fs @@ -131,8 +143,8 @@ pub fn sock_accept_internal( } let rights = Rights::all_socket(); - let fd = state.fs.create_fd(rights, rights, new_flags, 0, inode)?; + let fd = wasi_try_ok_ok!(state.fs.create_fd(rights, rights, new_flags, 0, inode)); Span::current().record("fd", fd); - Ok((fd, addr)) + Ok(Ok((fd, addr))) } diff --git a/lib/wasix/src/syscalls/wasix/sock_bind.rs b/lib/wasix/src/syscalls/wasix/sock_bind.rs index e8c187dd58f..ca030f41eda 100644 --- a/lib/wasix/src/syscalls/wasix/sock_bind.rs +++ b/lib/wasix/src/syscalls/wasix/sock_bind.rs @@ -14,22 +14,42 @@ pub fn sock_bind( mut ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Errno { +) -> Result { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; - let addr = wasi_try!(crate::net::read_ip_port(&memory, addr)); + + let addr = wasi_try_ok!(crate::net::read_ip_port(&memory, addr)); let addr = SocketAddr::new(addr.0, addr.1); Span::current().record("addr", &format!("{:?}", addr)); + wasi_try_ok!(sock_bind_internal(&mut ctx, sock, addr)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_bind(&mut ctx, sock, addr).map_err(|err| { + tracing::error!("failed to save sock_bind event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_bind_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + addr: SocketAddr, +) -> Result, WasiError> { + let env = ctx.data(); let net = env.net().clone(); let tasks = ctx.data().tasks().clone(); - wasi_try!(__sock_upgrade( - &mut ctx, + wasi_try_ok_ok!(__sock_upgrade( + ctx, sock, Rights::SOCK_BIND, move |socket| async move { socket.bind(tasks.deref(), net.deref(), addr).await } )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_connect.rs b/lib/wasix/src/syscalls/wasix/sock_connect.rs index c9f00cc8e19..03e51ec28b4 100644 --- a/lib/wasix/src/syscalls/wasix/sock_connect.rs +++ b/lib/wasix/src/syscalls/wasix/sock_connect.rs @@ -18,21 +18,40 @@ pub fn sock_connect( mut ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, addr: WasmPtr<__wasi_addr_port_t, M>, -) -> Errno { +) -> Result { let env = ctx.data(); - let net = env.net().clone(); let memory = unsafe { env.memory_view(&ctx) }; - let addr = wasi_try!(crate::net::read_ip_port(&memory, addr)); + let addr = wasi_try_ok!(crate::net::read_ip_port(&memory, addr)); let addr = SocketAddr::new(addr.0, addr.1); Span::current().record("addr", &format!("{:?}", addr)); + wasi_try_ok!(sock_connect_internal(&mut ctx, sock, addr)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_connect(&mut ctx, sock, addr).map_err(|err| { + tracing::error!("failed to save sock_connected event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_connect_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + addr: SocketAddr, +) -> Result, WasiError> { + let env = ctx.data(); + let net = env.net().clone(); let tasks = ctx.data().tasks().clone(); - wasi_try!(__sock_upgrade( - &mut ctx, + wasi_try_ok_ok!(__sock_upgrade( + ctx, sock, Rights::SOCK_CONNECT, move |mut socket| async move { socket.connect(tasks.deref(), net.deref(), addr, None).await } )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_join_multicast_v4.rs b/lib/wasix/src/syscalls/wasix/sock_join_multicast_v4.rs index e2c0d73351f..56ef67e9299 100644 --- a/lib/wasix/src/syscalls/wasix/sock_join_multicast_v4.rs +++ b/lib/wasix/src/syscalls/wasix/sock_join_multicast_v4.rs @@ -15,16 +15,38 @@ pub fn sock_join_multicast_v4( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip4_t, M>, iface: WasmPtr<__wasi_addr_ip4_t, M>, -) -> Errno { +) -> Result { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; - let multiaddr = wasi_try!(crate::net::read_ip_v4(&memory, multiaddr)); - let iface = wasi_try!(crate::net::read_ip_v4(&memory, iface)); - wasi_try!(__sock_actor_mut( - &mut ctx, - sock, - Rights::empty(), - |socket, _| socket.join_multicast_v4(multiaddr, iface) - )); - Errno::Success + let multiaddr = wasi_try_ok!(crate::net::read_ip_v4(&memory, multiaddr)); + let iface = wasi_try_ok!(crate::net::read_ip_v4(&memory, iface)); + + wasi_try_ok!(sock_join_multicast_v4_internal( + &mut ctx, sock, multiaddr, iface + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_join_ipv4_multicast(&mut ctx, sock, multiaddr, iface).map_err( + |err| { + tracing::error!("failed to save sock_join_ipv4_multicast event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + }, + )?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_join_multicast_v4_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, +) -> Result, WasiError> { + let env = ctx.data(); + wasi_try_ok_ok!(__sock_actor_mut(ctx, sock, Rights::empty(), |socket, _| { + socket.join_multicast_v4(multiaddr, iface) + })); + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_join_multicast_v6.rs b/lib/wasix/src/syscalls/wasix/sock_join_multicast_v6.rs index 53d0417420f..420ddfbbd21 100644 --- a/lib/wasix/src/syscalls/wasix/sock_join_multicast_v6.rs +++ b/lib/wasix/src/syscalls/wasix/sock_join_multicast_v6.rs @@ -15,15 +15,37 @@ pub fn sock_join_multicast_v6( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip6_t, M>, iface: u32, -) -> Errno { +) -> Result { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; - let multiaddr = wasi_try!(crate::net::read_ip_v6(&memory, multiaddr)); - wasi_try!(__sock_actor_mut( - &mut ctx, - sock, - Rights::empty(), - |socket, _| socket.join_multicast_v6(multiaddr, iface) - )); - Errno::Success + let multiaddr = wasi_try_ok!(crate::net::read_ip_v6(&memory, multiaddr)); + + wasi_try_ok!(sock_join_multicast_v6_internal( + &mut ctx, sock, multiaddr, iface + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_join_ipv6_multicast(&mut ctx, sock, multiaddr, iface).map_err( + |err| { + tracing::error!("failed to save sock_join_ipv6_multicast event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + }, + )?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_join_multicast_v6_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + multiaddr: Ipv6Addr, + iface: u32, +) -> Result, WasiError> { + let env = ctx.data(); + wasi_try_ok_ok!(__sock_actor_mut(ctx, sock, Rights::empty(), |socket, _| { + socket.join_multicast_v6(multiaddr, iface) + })); + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v4.rs b/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v4.rs index f96526c5670..b668c628aa4 100644 --- a/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v4.rs +++ b/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v4.rs @@ -15,16 +15,38 @@ pub fn sock_leave_multicast_v4( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip4_t, M>, iface: WasmPtr<__wasi_addr_ip4_t, M>, -) -> Errno { +) -> Result { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; - let multiaddr = wasi_try!(crate::net::read_ip_v4(&memory, multiaddr)); - let iface = wasi_try!(crate::net::read_ip_v4(&memory, iface)); - wasi_try!(__sock_actor_mut( - &mut ctx, - sock, - Rights::empty(), - |socket, _| socket.leave_multicast_v4(multiaddr, iface) - )); - Errno::Success + let multiaddr = wasi_try_ok!(crate::net::read_ip_v4(&memory, multiaddr)); + let iface = wasi_try_ok!(crate::net::read_ip_v4(&memory, iface)); + + wasi_try_ok!(sock_leave_multicast_v4_internal( + &mut ctx, sock, multiaddr, iface + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_leave_ipv4_multicast(&mut ctx, sock, multiaddr, iface).map_err( + |err| { + tracing::error!("failed to save sock_leave_ipv4_multicast event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + }, + )?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_leave_multicast_v4_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + multiaddr: Ipv4Addr, + iface: Ipv4Addr, +) -> Result, WasiError> { + let env = ctx.data(); + wasi_try_ok_ok!(__sock_actor_mut(ctx, sock, Rights::empty(), |socket, _| { + socket.leave_multicast_v4(multiaddr, iface) + })); + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v6.rs b/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v6.rs index 7d990c7829a..886bfd563e5 100644 --- a/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v6.rs +++ b/lib/wasix/src/syscalls/wasix/sock_leave_multicast_v6.rs @@ -15,15 +15,40 @@ pub fn sock_leave_multicast_v6( sock: WasiFd, multiaddr: WasmPtr<__wasi_addr_ip6_t, M>, iface: u32, -) -> Errno { +) -> Result { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; - let multiaddr = wasi_try!(crate::net::read_ip_v6(&memory, multiaddr)); - wasi_try!(__sock_actor_mut( - &mut ctx, + let multiaddr = wasi_try_ok!(crate::net::read_ip_v6(&memory, multiaddr)); + + wasi_try_ok!(sock_leave_multicast_v6_internal( + &mut ctx, sock, multiaddr, iface + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_leave_ipv6_multicast(&mut ctx, sock, multiaddr, iface).map_err( + |err| { + tracing::error!("failed to save sock_leave_ipv6_multicast event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + }, + )?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_leave_multicast_v6_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + multiaddr: Ipv6Addr, + iface: u32, +) -> Result, WasiError> { + let env = ctx.data(); + wasi_try_ok_ok!(__sock_actor_mut( + ctx, sock, Rights::empty(), |mut socket, _| socket.leave_multicast_v6(multiaddr, iface) )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_listen.rs b/lib/wasix/src/syscalls/wasix/sock_listen.rs index 50126ce6dce..1bc969ebc28 100644 --- a/lib/wasix/src/syscalls/wasix/sock_listen.rs +++ b/lib/wasix/src/syscalls/wasix/sock_listen.rs @@ -1,5 +1,5 @@ use super::*; -use crate::syscalls::*; +use crate::{journal::SnapshotTrigger, syscalls::*}; /// ### `sock_listen()` /// Listen for connections on a socket @@ -18,18 +18,39 @@ pub fn sock_listen( mut ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, backlog: M::Offset, -) -> Errno { +) -> Result { + ctx = wasi_try_ok!(maybe_snapshot_once::(ctx, SnapshotTrigger::FirstListen)?); + let env = ctx.data(); - let net = env.net().clone(); - let backlog: usize = wasi_try!(backlog.try_into().map_err(|_| Errno::Inval)); + let backlog: usize = wasi_try_ok!(backlog.try_into().map_err(|_| Errno::Inval)); + + wasi_try_ok!(sock_listen_internal(&mut ctx, sock, backlog)?); + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_listen(&mut ctx, sock, backlog).map_err(|err| { + tracing::error!("failed to save sock_listen event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_listen_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + backlog: usize, +) -> Result, WasiError> { + let env = ctx.data(); + let net = env.net().clone(); let tasks = ctx.data().tasks().clone(); - wasi_try!(__sock_upgrade( - &mut ctx, + wasi_try_ok_ok!(__sock_upgrade( + ctx, sock, Rights::SOCK_LISTEN, |socket| async move { socket.listen(tasks.deref(), net.deref(), backlog).await } )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_open.rs b/lib/wasix/src/syscalls/wasix/sock_open.rs index 52848d68317..dfd0678d8fd 100644 --- a/lib/wasix/src/syscalls/wasix/sock_open.rs +++ b/lib/wasix/src/syscalls/wasix/sock_open.rs @@ -22,33 +22,56 @@ use crate::syscalls::*; /// The file descriptor of the socket that has been opened. #[instrument(level = "debug", skip_all, fields(?af, ?ty, ?pt, sock = field::Empty), ret)] pub fn sock_open( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, af: Addressfamily, ty: Socktype, pt: SockProto, ro_sock: WasmPtr, -) -> Errno { - let env = ctx.data(); - let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; - +) -> Result { // only certain combinations are supported match pt { SockProto::Tcp => { if ty != Socktype::Stream { - return Errno::Notsup; + return Ok(Errno::Notsup); } } SockProto::Udp => { if ty != Socktype::Dgram { - return Errno::Notsup; + return Ok(Errno::Notsup); } } _ => {} } + let fd = wasi_try_ok!(sock_open_internal(&mut ctx, af, ty, pt)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_open(&mut ctx, af, ty, pt, fd).map_err(|err| { + tracing::error!("failed to save sock_open event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + let env = ctx.data(); + let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + wasi_try_mem_ok!(ro_sock.write(&memory, fd)); + + Ok(Errno::Success) +} + +pub(crate) fn sock_open_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + af: Addressfamily, + ty: Socktype, + pt: SockProto, +) -> Result, WasiError> { + let env = ctx.data(); + let (memory, state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + let kind = match ty { Socktype::Stream | Socktype::Dgram => Kind::Socket { - socket: wasi_try!(InodeSocket::new(InodeSocketKind::PreSocket { + socket: InodeSocket::new(InodeSocketKind::PreSocket { family: af, ty, pt, @@ -66,10 +89,9 @@ pub fn sock_open( accept_timeout: None, connect_timeout: None, handler: None, - }) - .map_err(net_error_into_wasi_err)), + }), }, - _ => return Errno::Notsup, + _ => return Ok(Err(Errno::Notsup)), }; let inode = @@ -77,12 +99,10 @@ pub fn sock_open( .fs .create_inode_with_default_stat(inodes, kind, false, "socket".to_string().into()); let rights = Rights::all_socket(); - let fd = wasi_try!(state + let fd = wasi_try_ok_ok!(state .fs .create_fd(rights, rights, Fdflags::empty(), 0, inode)); Span::current().record("sock", fd); - wasi_try_mem!(ro_sock.write(&memory, fd)); - - Errno::Success + Ok(Ok(fd)) } diff --git a/lib/wasix/src/syscalls/wasix/sock_recv.rs b/lib/wasix/src/syscalls/wasix/sock_recv.rs index 8444c675957..0dfafd09c2c 100644 --- a/lib/wasix/src/syscalls/wasix/sock_recv.rs +++ b/lib/wasix/src/syscalls/wasix/sock_recv.rs @@ -107,7 +107,7 @@ pub(super) fn sock_recv_internal( ri_flags: RiFlags, ro_data_len: WasmPtr, ro_flags: WasmPtr, -) -> Result, WasiError> { +) -> WasiResult { wasi_try_ok_ok!(WasiEnv::process_signals_and_exit(ctx)?); let mut env = ctx.data(); diff --git a/lib/wasix/src/syscalls/wasix/sock_send.rs b/lib/wasix/src/syscalls/wasix/sock_send.rs index 4c1eb1de490..c1ae7852a89 100644 --- a/lib/wasix/src/syscalls/wasix/sock_send.rs +++ b/lib/wasix/src/syscalls/wasix/sock_send.rs @@ -16,46 +16,90 @@ use crate::{net::socket::TimeType, syscalls::*}; /// ## Return /// /// Number of bytes transmitted. -#[instrument(level = "trace", skip_all, fields(%sock, nsent = field::Empty), ret)] +#[instrument(level = "trace", skip_all, fields(%fd, nsent = field::Empty), ret)] pub fn sock_send( - ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, + mut ctx: FunctionEnvMut<'_, WasiEnv>, + fd: WasiFd, si_data: WasmPtr<__wasi_ciovec_t, M>, si_data_len: M::Offset, si_flags: SiFlags, ret_data_len: WasmPtr, ) -> Result { + wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + let env = ctx.data(); - let fd_entry = env.state.fs.get_fd(sock).unwrap(); + let fd_entry = env.state.fs.get_fd(fd).unwrap(); let guard = fd_entry.inode.read(); let use_write = matches!(guard.deref(), Kind::Pipe { .. }); drop(guard); - if use_write { - fd_write(ctx, sock, si_data, si_data_len, ret_data_len) + + let bytes_written = if use_write { + let offset = { + let state = env.state.clone(); + let inodes = state.inodes.clone(); + + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); + fd_entry.offset.load(Ordering::Acquire) as usize + }; + + wasi_try_ok!(fd_write_internal::( + &ctx, + fd, + FdWriteSource::Iovs { + iovs: si_data, + iovs_len: si_data_len + }, + offset as u64, + true, + env.enable_journal, + )?) } else { - sock_send_internal(ctx, sock, si_data, si_data_len, si_flags, ret_data_len) + wasi_try_ok!(sock_send_internal::( + &ctx, + fd, + FdWriteSource::Iovs { + iovs: si_data, + iovs_len: si_data_len + }, + si_flags, + )?) + }; + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_send(&ctx, fd, bytes_written, si_data, si_data_len, si_flags) + .map_err(|err| { + tracing::error!("failed to save sock_send event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; } + + Span::current().record("nsent", bytes_written); + + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let bytes_written: M::Offset = + wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); + wasi_try_mem_ok!(ret_data_len.write(&memory, bytes_written)); + + Ok(Errno::Success) } -pub(super) fn sock_send_internal( - mut ctx: FunctionEnvMut<'_, WasiEnv>, +pub(crate) fn sock_send_internal( + ctx: &FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, - si_data: WasmPtr<__wasi_ciovec_t, M>, - si_data_len: M::Offset, + si_data: FdWriteSource<'_, M>, si_flags: SiFlags, - ret_data_len: WasmPtr, -) -> Result { +) -> Result, WasiError> { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; let runtime = env.runtime.clone(); - let res = { - __sock_asyncify(env, sock, Rights::SOCK_SEND, |socket, fd| async move { - let iovs_arr = si_data - .slice(&memory, si_data_len) - .map_err(mem_error_to_wasi)?; - let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; - + let bytes_written = wasi_try_ok_ok!(__sock_asyncify( + env, + sock, + Rights::SOCK_SEND, + |socket, fd| async move { let nonblocking = fd.flags.contains(Fdflags::NONBLOCK); let timeout = socket .opt_time(TimeType::WriteTimeout) @@ -63,57 +107,54 @@ pub(super) fn sock_send_internal( .flatten() .unwrap_or(Duration::from_secs(30)); - let mut sent = 0usize; - for iovs in iovs_arr.iter() { - let buf = WasmPtr::::new(iovs.buf) - .slice(&memory, iovs.buf_len) - .map_err(mem_error_to_wasi)? - .access() - .map_err(mem_error_to_wasi)?; - let local_sent = match socket - .send( - env.tasks().deref(), - buf.as_ref(), - Some(timeout), - nonblocking, - ) - .await - { - Ok(s) => s, - Err(_) if sent > 0 => break, - Err(err) => return Err(err), - }; - sent += local_sent; - if local_sent != buf.len() { - break; + match si_data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = iovs.slice(&memory, iovs_len).map_err(mem_error_to_wasi)?; + let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; + + let mut sent = 0usize; + for iovs in iovs_arr.iter() { + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, iovs.buf_len) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + let local_sent = match socket + .send( + env.tasks().deref(), + buf.as_ref(), + Some(timeout), + nonblocking, + ) + .await + { + Ok(s) => s, + Err(_) if sent > 0 => break, + Err(err) => return Err(err), + }; + sent += local_sent; + if local_sent != buf.len() { + break; + } + } + Ok(sent) + } + FdWriteSource::Buffer(data) => { + socket + .send( + env.tasks().deref(), + data.as_ref(), + Some(timeout), + nonblocking, + ) + .await } } - Ok(sent) - }) - }; - - let mut ret = Errno::Success; - let bytes_written = match res { - Ok(bytes_written) => { - trace!( - %bytes_written, - ); - bytes_written - } - Err(err) => { - let socket_err = err.name(); - trace!( - %socket_err, - ); - ret = err; - 0 } - }; - Span::current().record("nsent", bytes_written); + )); + trace!( + %bytes_written, + ); - let memory = unsafe { env.memory_view(&ctx) }; - let bytes_written: M::Offset = - wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); - wasi_try_mem_ok!(ret_data_len.write(&memory, bytes_written)); - Ok(ret) + Ok(Ok(bytes_written)) } diff --git a/lib/wasix/src/syscalls/wasix/sock_send_file.rs b/lib/wasix/src/syscalls/wasix/sock_send_file.rs index 253cc1f095b..70419d74cd5 100644 --- a/lib/wasix/src/syscalls/wasix/sock_send_file.rs +++ b/lib/wasix/src/syscalls/wasix/sock_send_file.rs @@ -21,201 +21,215 @@ pub fn sock_send_file( sock: WasiFd, in_fd: WasiFd, offset: Filesize, - mut count: Filesize, + count: Filesize, ret_sent: WasmPtr, ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + let total_written = wasi_try_ok!(sock_send_file_internal( + &mut ctx, sock, in_fd, offset, count + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_send_file::(&mut ctx, sock, in_fd, offset, total_written) + .map_err(|err| { + tracing::error!("failed to save sock_send_file event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Span::current().record("nsent", total_written); + + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + wasi_try_mem_ok!(ret_sent.write(&memory, total_written as Filesize)); + + Ok(Errno::Success) +} + +pub(crate) fn sock_send_file_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + in_fd: WasiFd, + offset: Filesize, + mut count: Filesize, +) -> Result, WasiError> { let mut env = ctx.data(); let net = env.net(); let tasks = env.tasks().clone(); let state = env.state.clone(); - let ret = wasi_try_ok!({ - // Set the offset of the file - { - let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&in_fd).ok_or(Errno::Badf)); - fd_entry.offset.store(offset, Ordering::Release); - } - - // Enter a loop that will process all the data - let mut total_written: Filesize = 0; - while (count > 0) { - let sub_count = count.min(4096); - count -= sub_count; - - let fd_entry = wasi_try_ok!(state.fs.get_fd(in_fd)); - let fd_flags = fd_entry.flags; - - let data = { - match in_fd { - __WASI_STDIN_FILENO => { - let mut stdin = - wasi_try_ok!(WasiInodes::stdin_mut(&state.fs.fd_map) - .map_err(fs_error_into_wasi_err)); - let data = wasi_try_ok!(__asyncify(&mut ctx, None, async move { - // TODO: optimize with MaybeUninit - let mut buf = vec![0u8; sub_count as usize]; - let amt = stdin.read(&mut buf[..]).await.map_err(map_io_err)?; - buf.truncate(amt); - Ok(buf) - })?); - env = ctx.data(); - data + // Set the offset of the file + { + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok_ok!(fd_map.get_mut(&in_fd).ok_or(Errno::Badf)); + fd_entry.offset.store(offset, Ordering::Release); + } + + // Enter a loop that will process all the data + let mut total_written: Filesize = 0; + while (count > 0) { + let sub_count = count.min(4096); + count -= sub_count; + + let fd_entry = wasi_try_ok_ok!(state.fs.get_fd(in_fd)); + let fd_flags = fd_entry.flags; + + let data = { + match in_fd { + __WASI_STDIN_FILENO => { + let mut stdin = wasi_try_ok_ok!( + WasiInodes::stdin_mut(&state.fs.fd_map).map_err(fs_error_into_wasi_err) + ); + let data = wasi_try_ok_ok!(__asyncify(ctx, None, async move { + // TODO: optimize with MaybeUninit + let mut buf = vec![0u8; sub_count as usize]; + let amt = stdin.read(&mut buf[..]).await.map_err(map_io_err)?; + buf.truncate(amt); + Ok(buf) + })?); + env = ctx.data(); + data + } + __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return Ok(Err(Errno::Inval)), + _ => { + if !fd_entry.rights.contains(Rights::FD_READ) { + // TODO: figure out the error to return when lacking rights + return Ok(Err(Errno::Access)); } - __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return Ok(Errno::Inval), - _ => { - if !fd_entry.rights.contains(Rights::FD_READ) { - // TODO: figure out the error to return when lacking rights - return Ok(Errno::Access); - } - let offset = fd_entry.offset.load(Ordering::Acquire) as usize; - let inode = fd_entry.inode; - let data = { - let mut guard = inode.write(); - match guard.deref_mut() { - Kind::File { handle, .. } => { - if let Some(handle) = handle { - let data = - wasi_try_ok!(__asyncify(&mut ctx, None, async move { - let mut buf = vec![0u8; sub_count as usize]; - - let mut handle = handle.write().unwrap(); - handle - .seek(std::io::SeekFrom::Start(offset as u64)) - .await - .map_err(map_io_err)?; - let amt = handle - .read(&mut buf[..]) - .await - .map_err(map_io_err)?; - buf.truncate(amt); - Ok(buf) - })?); - env = ctx.data(); - data - } else { - return Ok(Errno::Inval); - } - } - Kind::Socket { socket, .. } => { - let socket = socket.clone(); - let tasks = tasks.clone(); - drop(guard); - - let read_timeout = socket - .opt_time(TimeType::WriteTimeout) - .ok() - .flatten() - .unwrap_or(Duration::from_secs(30)); - - let data = wasi_try_ok!(__asyncify(&mut ctx, None, async { - let mut buf = Vec::with_capacity(sub_count as usize); - unsafe { - buf.set_len(sub_count as usize); - } - socket - .recv( - tasks.deref(), - &mut buf, - Some(read_timeout), - false, - ) - .await - .map(|amt| { - unsafe { - buf.set_len(amt); - } - let buf: Vec = - unsafe { std::mem::transmute(buf) }; - buf - }) - })?); - env = ctx.data(); - data - } - Kind::Pipe { ref mut pipe, .. } => { + let offset = fd_entry.offset.load(Ordering::Acquire) as usize; + let inode = fd_entry.inode; + let data = { + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { let data = - wasi_try_ok!(__asyncify(&mut ctx, None, async move { - // TODO: optimize with MaybeUninit + wasi_try_ok_ok!(__asyncify(ctx, None, async move { let mut buf = vec![0u8; sub_count as usize]; - let amt = - virtual_fs::AsyncReadExt::read(pipe, &mut buf[..]) - .await - .map_err(map_io_err)?; + + let mut handle = handle.write().unwrap(); + handle + .seek(std::io::SeekFrom::Start(offset as u64)) + .await + .map_err(map_io_err)?; + let amt = handle + .read(&mut buf[..]) + .await + .map_err(map_io_err)?; buf.truncate(amt); Ok(buf) })?); env = ctx.data(); data + } else { + return Ok(Err(Errno::Inval)); } - Kind::Epoll { .. } => { - return Ok(Errno::Inval); - } - Kind::Dir { .. } | Kind::Root { .. } => { - return Ok(Errno::Isdir); - } - Kind::EventNotifications { .. } => { - return Ok(Errno::Inval); - } - Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), - Kind::Buffer { buffer } => { + } + Kind::Socket { socket, .. } => { + let socket = socket.clone(); + let tasks = tasks.clone(); + drop(guard); + + let read_timeout = socket + .opt_time(TimeType::WriteTimeout) + .ok() + .flatten() + .unwrap_or(Duration::from_secs(30)); + + let data = wasi_try_ok_ok!(__asyncify(ctx, None, async { + let mut buf = Vec::with_capacity(sub_count as usize); + unsafe { + buf.set_len(sub_count as usize); + } + socket + .recv(tasks.deref(), &mut buf, Some(read_timeout), false) + .await + .map(|amt| { + unsafe { + buf.set_len(amt); + } + let buf: Vec = unsafe { std::mem::transmute(buf) }; + buf + }) + })?); + env = ctx.data(); + data + } + Kind::Pipe { ref mut pipe, .. } => { + let data = wasi_try_ok_ok!(__asyncify(ctx, None, async move { // TODO: optimize with MaybeUninit let mut buf = vec![0u8; sub_count as usize]; - - let mut buf_read = &buffer[offset..]; - let amt = wasi_try_ok!(std::io::Read::read( - &mut buf_read, - &mut buf[..] - ) - .map_err(map_io_err)); + let amt = virtual_fs::AsyncReadExt::read(pipe, &mut buf[..]) + .await + .map_err(map_io_err)?; buf.truncate(amt); - buf - } + Ok(buf) + })?); + env = ctx.data(); + data + } + Kind::Epoll { .. } => { + return Ok(Err(Errno::Inval)); + } + Kind::Dir { .. } | Kind::Root { .. } => { + return Ok(Err(Errno::Isdir)); } - }; + Kind::EventNotifications { .. } => { + return Ok(Err(Errno::Inval)); + } + Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), + Kind::Buffer { buffer } => { + // TODO: optimize with MaybeUninit + let mut buf = vec![0u8; sub_count as usize]; + + let mut buf_read = &buffer[offset..]; + let amt = wasi_try_ok_ok!(std::io::Read::read( + &mut buf_read, + &mut buf[..] + ) + .map_err(map_io_err)); + buf.truncate(amt); + buf + } + } + }; - // reborrow - let mut fd_map = state.fs.fd_map.write().unwrap(); - let fd_entry = wasi_try_ok!(fd_map.get_mut(&in_fd).ok_or(Errno::Badf)); - fd_entry - .offset - .fetch_add(data.len() as u64, Ordering::AcqRel); + // reborrow + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok_ok!(fd_map.get_mut(&in_fd).ok_or(Errno::Badf)); + fd_entry + .offset + .fetch_add(data.len() as u64, Ordering::AcqRel); - data - } + data } - }; - - // Write it down to the socket - let tasks = ctx.data().tasks().clone(); - let bytes_written = wasi_try_ok!(__sock_asyncify_mut( - &mut ctx, - sock, - Rights::SOCK_SEND, - |socket, fd| async move { - let write_timeout = socket - .opt_time(TimeType::ReadTimeout) - .ok() - .flatten() - .unwrap_or(Duration::from_secs(30)); - socket - .send(tasks.deref(), &data, Some(write_timeout), true) - .await - }, - )); - env = ctx.data(); - - total_written += bytes_written as u64; - } - Span::current().record("nsent", total_written); - - let memory = unsafe { env.memory_view(&ctx) }; - wasi_try_mem_ok!(ret_sent.write(&memory, total_written as Filesize)); - - Ok(Errno::Success) - }); - Ok(ret) + } + }; + + // Write it down to the socket + let tasks = ctx.data().tasks().clone(); + let bytes_written = wasi_try_ok_ok!(__sock_asyncify_mut( + ctx, + sock, + Rights::SOCK_SEND, + |socket, fd| async move { + let write_timeout = socket + .opt_time(TimeType::ReadTimeout) + .ok() + .flatten() + .unwrap_or(Duration::from_secs(30)); + socket + .send(tasks.deref(), &data, Some(write_timeout), true) + .await + }, + )); + env = ctx.data(); + + total_written += bytes_written as u64; + } + + Ok(Ok(total_written)) } diff --git a/lib/wasix/src/syscalls/wasix/sock_send_to.rs b/lib/wasix/src/syscalls/wasix/sock_send_to.rs index 7a345bb8504..48ec74675c9 100644 --- a/lib/wasix/src/syscalls/wasix/sock_send_to.rs +++ b/lib/wasix/src/syscalls/wasix/sock_send_to.rs @@ -19,31 +19,11 @@ use crate::{net::socket::TimeType, syscalls::*}; /// Number of bytes transmitted. #[instrument(level = "trace", skip_all, fields(%sock, ?addr, nsent = field::Empty), ret)] pub fn sock_send_to( - ctx: FunctionEnvMut<'_, WasiEnv>, - sock: WasiFd, - si_data: WasmPtr<__wasi_ciovec_t, M>, - si_data_len: M::Offset, - si_flags: SiFlags, - addr: WasmPtr<__wasi_addr_port_t, M>, - ret_data_len: WasmPtr, -) -> Result { - sock_send_to_internal( - ctx, - sock, - si_data, - si_data_len, - si_flags, - addr, - ret_data_len, - ) -} - -pub(super) fn sock_send_to_internal( mut ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, si_data: WasmPtr<__wasi_ciovec_t, M>, si_data_len: M::Offset, - _si_flags: SiFlags, + si_flags: SiFlags, addr: WasmPtr<__wasi_addr_port_t, M>, ret_data_len: WasmPtr, ) -> Result { @@ -58,17 +38,61 @@ pub(super) fn sock_send_to_internal( let addr = SocketAddr::new(addr_ip, addr_port); Span::current().record("addr", &format!("{:?}", addr)); + let bytes_written = wasi_try_ok!(sock_send_to_internal( + &ctx, + sock, + FdWriteSource::Iovs { + iovs: si_data, + iovs_len: si_data_len + }, + si_flags, + addr, + )?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_send_to::( + &ctx, + sock, + bytes_written, + si_data, + si_data_len, + addr, + si_flags, + ) + .map_err(|err| { + tracing::error!("failed to save sock_send_to event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Span::current().record("nsent", bytes_written); + + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let bytes_written: M::Offset = + wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); + wasi_try_mem_ok!(ret_data_len.write(&memory, bytes_written)); + + Ok(Errno::Success) +} + +pub(crate) fn sock_send_to_internal( + ctx: &FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + si_data: FdWriteSource<'_, M>, + _si_flags: SiFlags, + addr: SocketAddr, +) -> Result, WasiError> { + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + let bytes_written = { - wasi_try_ok!(__sock_asyncify( + wasi_try_ok_ok!(__sock_asyncify( env, sock, Rights::SOCK_SEND_TO, |socket, fd| async move { - let iovs_arr = si_data - .slice(&memory, si_data_len) - .map_err(mem_error_to_wasi)?; - let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; - let nonblocking = fd.flags.contains(Fdflags::NONBLOCK); let timeout = socket .opt_time(TimeType::WriteTimeout) @@ -76,42 +100,57 @@ pub(super) fn sock_send_to_internal( .flatten() .unwrap_or(Duration::from_secs(30)); - let mut sent = 0usize; - for iovs in iovs_arr.iter() { - let buf = WasmPtr::::new(iovs.buf) - .slice(&memory, iovs.buf_len) - .map_err(mem_error_to_wasi)? - .access() - .map_err(mem_error_to_wasi)?; - let local_sent = match socket - .send_to::( - env.tasks().deref(), - buf.as_ref(), - addr, - Some(timeout), - nonblocking, - ) - .await - { - Ok(s) => s, - Err(_) if sent > 0 => break, - Err(err) => return Err(err), - }; - sent += local_sent; - if local_sent != buf.len() { - break; + match si_data { + FdWriteSource::Iovs { iovs, iovs_len } => { + let iovs_arr = iovs.slice(&memory, iovs_len).map_err(mem_error_to_wasi)?; + let iovs_arr = iovs_arr.access().map_err(mem_error_to_wasi)?; + + let mut sent = 0usize; + for iovs in iovs_arr.iter() { + let buf = WasmPtr::::new(iovs.buf) + .slice(&memory, iovs.buf_len) + .map_err(mem_error_to_wasi)? + .access() + .map_err(mem_error_to_wasi)?; + let local_sent = match socket + .send_to::( + env.tasks().deref(), + buf.as_ref(), + addr, + Some(timeout), + nonblocking, + ) + .await + { + Ok(s) => s, + Err(_) if sent > 0 => break, + Err(err) => return Err(err), + }; + sent += local_sent; + if local_sent != buf.len() { + break; + } + } + Ok(sent) + } + FdWriteSource::Buffer(data) => { + socket + .send_to::( + env.tasks().deref(), + data.as_ref(), + addr, + Some(timeout), + nonblocking, + ) + .await } } - Ok(sent) }, )) }; - Span::current().record("nsent", bytes_written); + trace!( + %bytes_written, + ); - let memory = unsafe { env.memory_view(&ctx) }; - let bytes_written: M::Offset = - wasi_try_ok!(bytes_written.try_into().map_err(|_| Errno::Overflow)); - wasi_try_mem_ok!(ret_data_len.write(&memory, bytes_written)); - - Ok(Errno::Success) + Ok(Ok(bytes_written)) } diff --git a/lib/wasix/src/syscalls/wasix/sock_set_opt_flag.rs b/lib/wasix/src/syscalls/wasix/sock_set_opt_flag.rs index df6eb21bcfa..993779520a1 100644 --- a/lib/wasix/src/syscalls/wasix/sock_set_opt_flag.rs +++ b/lib/wasix/src/syscalls/wasix/sock_set_opt_flag.rs @@ -16,19 +16,38 @@ pub fn sock_set_opt_flag( sock: WasiFd, opt: Sockoption, flag: Bool, -) -> Errno { +) -> Result { let flag = match flag { Bool::False => false, Bool::True => true, - _ => return Errno::Inval, + _ => return Ok(Errno::Inval), }; + wasi_try_ok!(sock_set_opt_flag_internal(&mut ctx, sock, opt, flag)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_set_opt_flag(&mut ctx, sock, opt, flag).map_err(|err| { + tracing::error!("failed to save sock_set_opt_flag event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_set_opt_flag_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + opt: Sockoption, + flag: bool, +) -> Result, WasiError> { let option: crate::net::socket::WasiSocketOption = opt.into(); - wasi_try!(__sock_actor_mut( - &mut ctx, + wasi_try_ok_ok!(__sock_actor_mut( + ctx, sock, Rights::empty(), |mut socket, _| socket.set_opt_flag(option, flag) )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_set_opt_size.rs b/lib/wasix/src/syscalls/wasix/sock_set_opt_size.rs index a66291b5be1..a9bdb842b49 100644 --- a/lib/wasix/src/syscalls/wasix/sock_set_opt_size.rs +++ b/lib/wasix/src/syscalls/wasix/sock_set_opt_size.rs @@ -16,19 +16,38 @@ pub fn sock_set_opt_size( sock: WasiFd, opt: Sockoption, size: Filesize, -) -> Errno { +) -> Result { + wasi_try_ok!(sock_set_opt_size_internal(&mut ctx, sock, opt, size)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_set_opt_size(&mut ctx, sock, opt, size).map_err(|err| { + tracing::error!("failed to save sock_set_opt_size event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_set_opt_size_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + opt: Sockoption, + size: Filesize, +) -> Result, WasiError> { let ty = match opt { Sockoption::RecvTimeout => TimeType::ReadTimeout, Sockoption::SendTimeout => TimeType::WriteTimeout, Sockoption::ConnectTimeout => TimeType::ConnectTimeout, Sockoption::AcceptTimeout => TimeType::AcceptTimeout, Sockoption::Linger => TimeType::Linger, - _ => return Errno::Inval, + _ => return Ok(Err(Errno::Inval)), }; let option: crate::net::socket::WasiSocketOption = opt.into(); - wasi_try!(__sock_actor_mut( - &mut ctx, + wasi_try_ok_ok!(__sock_actor_mut( + ctx, sock, Rights::empty(), |mut socket, _| match opt { @@ -39,5 +58,5 @@ pub fn sock_set_opt_size( _ => Err(Errno::Inval), } )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_set_opt_time.rs b/lib/wasix/src/syscalls/wasix/sock_set_opt_time.rs index 49e9c037987..6196cc5e4a4 100644 --- a/lib/wasix/src/syscalls/wasix/sock_set_opt_time.rs +++ b/lib/wasix/src/syscalls/wasix/sock_set_opt_time.rs @@ -15,14 +15,14 @@ pub fn sock_set_opt_time( sock: WasiFd, opt: Sockoption, time: WasmPtr, -) -> Errno { +) -> Result { let env = ctx.data(); let memory = unsafe { env.memory_view(&ctx) }; - let time = wasi_try_mem!(time.read(&memory)); + let time = wasi_try_mem_ok!(time.read(&memory)); let time = match time.tag { OptionTag::None => None, OptionTag::Some => Some(Duration::from_nanos(time.u)), - _ => return Errno::Inval, + _ => return Ok(Errno::Inval), }; Span::current().record("time", &format!("{:?}", time)); @@ -32,15 +32,31 @@ pub fn sock_set_opt_time( Sockoption::ConnectTimeout => TimeType::ConnectTimeout, Sockoption::AcceptTimeout => TimeType::AcceptTimeout, Sockoption::Linger => TimeType::Linger, - _ => return Errno::Inval, + _ => return Ok(Errno::Inval), }; - let option: crate::net::socket::WasiSocketOption = opt.into(); - wasi_try!(__sock_actor_mut( - &mut ctx, - sock, - Rights::empty(), - |socket, _| socket.set_opt_time(ty, time) - )); - Errno::Success + wasi_try_ok!(sock_set_opt_time_internal(&mut ctx, sock, ty, time)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_set_opt_time(&mut ctx, sock, ty, time).map_err(|err| { + tracing::error!("failed to save sock_set_opt_time event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_set_opt_time_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + ty: TimeType, + time: Option, +) -> Result, WasiError> { + wasi_try_ok_ok!(__sock_actor_mut(ctx, sock, Rights::empty(), |socket, _| { + socket.set_opt_time(ty, time) + })); + + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/sock_shutdown.rs b/lib/wasix/src/syscalls/wasix/sock_shutdown.rs index 36cdd22cbcd..8ef89634e2a 100644 --- a/lib/wasix/src/syscalls/wasix/sock_shutdown.rs +++ b/lib/wasix/src/syscalls/wasix/sock_shutdown.rs @@ -1,3 +1,5 @@ +use std::net::Shutdown; + use super::*; use crate::syscalls::*; @@ -9,21 +11,43 @@ use crate::syscalls::*; /// /// * `how` - Which channels on the socket to shut down. #[instrument(level = "debug", skip_all, fields(%sock), ret)] -pub fn sock_shutdown(mut ctx: FunctionEnvMut<'_, WasiEnv>, sock: WasiFd, how: SdFlags) -> Errno { +pub fn sock_shutdown( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + how: SdFlags, +) -> Result { let both = __WASI_SHUT_RD | __WASI_SHUT_WR; - let how = match how { + let shutdown = match how { __WASI_SHUT_RD => std::net::Shutdown::Read, __WASI_SHUT_WR => std::net::Shutdown::Write, a if a == both => std::net::Shutdown::Both, - _ => return Errno::Inval, + _ => return Ok(Errno::Inval), }; - wasi_try!(__sock_actor_mut( - &mut ctx, + wasi_try_ok!(sock_shutdown_internal(&mut ctx, sock, shutdown)?); + + #[cfg(feature = "journal")] + if ctx.data().enable_journal { + JournalEffector::save_sock_shutdown(&mut ctx, sock, shutdown).map_err(|err| { + tracing::error!("failed to save sock_shutdown event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub(crate) fn sock_shutdown_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + sock: WasiFd, + shutdown: Shutdown, +) -> Result, WasiError> { + wasi_try_ok_ok!(__sock_actor_mut( + ctx, sock, Rights::SOCK_SHUTDOWN, - |mut socket, _| socket.shutdown(how) + |mut socket, _| socket.shutdown(shutdown) )); - Errno::Success + Ok(Ok(())) } diff --git a/lib/wasix/src/syscalls/wasix/stack_checkpoint.rs b/lib/wasix/src/syscalls/wasix/stack_checkpoint.rs index 59b8d3a430d..84fefad10ba 100644 --- a/lib/wasix/src/syscalls/wasix/stack_checkpoint.rs +++ b/lib/wasix/src/syscalls/wasix/stack_checkpoint.rs @@ -45,7 +45,7 @@ pub fn stack_checkpoint( // Perform the unwind action unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| { // Grab all the globals and serialize them - let store_data = crate::utils::store::capture_snapshot(&mut ctx.as_store_mut()) + let store_data = crate::utils::store::capture_instance_snapshot(&mut ctx.as_store_mut()) .serialize() .unwrap(); let env = ctx.data(); diff --git a/lib/wasix/src/syscalls/wasix/thread_join.rs b/lib/wasix/src/syscalls/wasix/thread_join.rs index 20d4a13d48d..2be67a229a3 100644 --- a/lib/wasix/src/syscalls/wasix/thread_join.rs +++ b/lib/wasix/src/syscalls/wasix/thread_join.rs @@ -27,6 +27,8 @@ pub(super) fn thread_join_internal( return Ok(Errno::Success); } + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + let env = ctx.data(); let tid: WasiThreadId = join_tid.into(); let other_thread = env.process.get_thread(&tid); diff --git a/lib/wasix/src/syscalls/wasix/thread_sleep.rs b/lib/wasix/src/syscalls/wasix/thread_sleep.rs index 5157a8befd5..1322fc640f2 100644 --- a/lib/wasix/src/syscalls/wasix/thread_sleep.rs +++ b/lib/wasix/src/syscalls/wasix/thread_sleep.rs @@ -27,6 +27,8 @@ pub(crate) fn thread_sleep_internal( return Ok(Errno::Success); } + ctx = wasi_try_ok!(maybe_snapshot::(ctx)?); + let env = ctx.data(); #[cfg(feature = "sys-thread")] diff --git a/lib/wasix/src/syscalls/wasix/thread_spawn.rs b/lib/wasix/src/syscalls/wasix/thread_spawn.rs index 67e9f30f6e2..eb6ffa382c2 100644 --- a/lib/wasix/src/syscalls/wasix/thread_spawn.rs +++ b/lib/wasix/src/syscalls/wasix/thread_spawn.rs @@ -1,8 +1,10 @@ use std::f32::consts::E; use super::*; +#[cfg(feature = "journal")] +use crate::journal::JournalEffector; use crate::{ - capture_snapshot, + capture_instance_snapshot, os::task::thread::WasiMemoryLayout, runtime::task_manager::{TaskWasm, TaskWasmRunProperties}, syscalls::*, @@ -73,7 +75,7 @@ pub(crate) fn thread_spawn_internal( tracing::trace!("spawn with layout {:?}", layout); // Create the handle that represents this thread - let mut thread_handle = match env.process.new_thread() { + let mut thread_handle = match env.process.new_thread(layout.clone()) { Ok(h) => Arc::new(h), Err(err) => { error!( @@ -119,7 +121,7 @@ pub(crate) fn thread_spawn_internal( return Err(Errno::Notcapable); } let thread_module = unsafe { env.inner() }.module_clone(); - let snapshot = capture_snapshot(&mut ctx.as_store_mut()); + let snapshot = capture_instance_snapshot(&mut ctx.as_store_mut()); let spawn_type = crate::runtime::SpawnMemoryType::ShareMemory(thread_memory, ctx.as_store_ref()); @@ -195,7 +197,7 @@ fn call_module( trace!("callback finished (ret={})", ret); // Clean up the environment - env.cleanup(store, Some(ret.into())); + env.on_exit(store, Some(ret.into())); // Return the result Ok(ret as u32) @@ -203,12 +205,13 @@ fn call_module( // If we need to rewind then do so if let Some((rewind_state, rewind_result)) = rewind_state { + let mut ctx = ctx.env.clone().into_mut(&mut store); let res = rewind_ext::( - ctx.env.clone().into_mut(&mut store), + &mut ctx, rewind_state.memory_stack, rewind_state.rewind_stack, rewind_state.store_data, - rewind_result, + Some(rewind_result), ); if res != Errno::Success { return Err(res); diff --git a/lib/wasix/src/syscalls/wasix/tty_set.rs b/lib/wasix/src/syscalls/wasix/tty_set.rs index 0f46e1e25ab..24866d9f5b6 100644 --- a/lib/wasix/src/syscalls/wasix/tty_set.rs +++ b/lib/wasix/src/syscalls/wasix/tty_set.rs @@ -1,22 +1,17 @@ use super::*; -use crate::syscalls::*; +use crate::{syscalls::*, WasiTtyState}; /// ### `tty_set()` /// Updates the properties of the rect #[instrument(level = "debug", skip_all, ret)] pub fn tty_set( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, tty_state: WasmPtr, -) -> Errno { +) -> Result { let env = ctx.data(); - let bridge = if let Some(t) = env.runtime.tty() { - t - } else { - return Errno::Notsup; - }; let memory = unsafe { env.memory_view(&ctx) }; - let state = wasi_try_mem!(tty_state.read(&memory)); + let state = wasi_try_mem_ok!(tty_state.read(&memory)); let echo = state.echo; let line_buffered = state.line_buffered; let line_feeds = true; @@ -39,7 +34,34 @@ pub fn tty_set( line_feeds, }; + wasi_try_ok!({ + #[allow(clippy::redundant_clone)] + tty_set_internal(&mut ctx, state.clone()) + }); + let env = ctx.data(); + + #[cfg(feature = "journal")] + if env.enable_journal { + JournalEffector::save_tty_set(&mut ctx, state).map_err(|err| { + tracing::error!("failed to save path symbolic link event - {}", err); + WasiError::Exit(ExitCode::Errno(Errno::Fault)) + })?; + } + + Ok(Errno::Success) +} + +pub fn tty_set_internal( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + state: WasiTtyState, +) -> Result<(), Errno> { + let env = ctx.data(); + let bridge = if let Some(t) = env.runtime.tty() { + t + } else { + return Err(Errno::Notsup); + }; bridge.tty_set(state); - Errno::Success + Ok(()) } diff --git a/lib/wasix/src/utils/mod.rs b/lib/wasix/src/utils/mod.rs index 6cf471da1d8..cd930052860 100644 --- a/lib/wasix/src/utils/mod.rs +++ b/lib/wasix/src/utils/mod.rs @@ -38,6 +38,12 @@ pub fn map_io_err(err: std::io::Error) -> Errno { From::::from(err) } +#[cfg(feature = "journal")] +pub fn map_snapshot_err(err: anyhow::Error) -> Errno { + tracing::warn!("unknown snapshot error: {}", err); + Errno::Unknown +} + /// The version of WASI. This is determined by the imports namespace /// string. #[derive(Debug, Clone, Copy, Eq)] diff --git a/lib/wasix/src/utils/store.rs b/lib/wasix/src/utils/store.rs index 047b09d9aef..1fac581ba90 100644 --- a/lib/wasix/src/utils/store.rs +++ b/lib/wasix/src/utils/store.rs @@ -15,13 +15,13 @@ impl InstanceSnapshot { } } -pub fn capture_snapshot(store: &mut impl wasmer::AsStoreMut) -> InstanceSnapshot { +pub fn capture_instance_snapshot(store: &mut impl wasmer::AsStoreMut) -> InstanceSnapshot { let objs = store.objects_mut(); let globals = objs.as_u128_globals(); InstanceSnapshot { globals } } -pub fn restore_snapshot(store: &mut impl wasmer::AsStoreMut, snapshot: &InstanceSnapshot) { +pub fn restore_instance_snapshot(store: &mut impl wasmer::AsStoreMut, snapshot: &InstanceSnapshot) { let objs = store.objects_mut(); for (index, value) in snapshot.globals.iter().enumerate() { diff --git a/lib/wasix/tests/runners.rs b/lib/wasix/tests/runners.rs index eefe9b5ca9b..f2e92a16f12 100644 --- a/lib/wasix/tests/runners.rs +++ b/lib/wasix/tests/runners.rs @@ -103,7 +103,10 @@ mod wcgi { use futures::{channel::mpsc::Sender, future::AbortHandle, SinkExt, StreamExt}; use rand::Rng; use tokio::runtime::Handle; - use wasmer_wasix::{bin_factory::BinaryPackage, runners::wcgi::WcgiRunner}; + use wasmer_wasix::{ + bin_factory::BinaryPackage, + runners::wcgi::{NoOpWcgiCallbacks, WcgiRunner}, + }; use super::*; @@ -121,7 +124,7 @@ mod wcgi { let webc = download_cached("https://wasmer.io/Michael-F-Bryan/staticserver@1.0.3").await; let (rt, tasks) = runtime(); let container = Container::from_bytes(webc).unwrap(); - let mut runner = WcgiRunner::new(); + let mut runner = WcgiRunner::new(NoOpWcgiCallbacks); let port = rand::thread_rng().gen_range(10000_u16..65535_u16); let (cb, started) = callbacks(Handle::current()); runner diff --git a/tests/integration/cli/tests/wasm/mother.wasm b/tests/integration/cli/tests/wasm/mother.wasm new file mode 100644 index 00000000000..f4fe3a48b2a Binary files /dev/null and b/tests/integration/cli/tests/wasm/mother.wasm differ diff --git a/tests/lib/wast/src/wasi_wast.rs b/tests/lib/wast/src/wasi_wast.rs index 8354ea775da..806209dbb68 100644 --- a/tests/lib/wast/src/wasi_wast.rs +++ b/tests/lib/wast/src/wasi_wast.rs @@ -14,7 +14,10 @@ use virtual_fs::{ AsyncWriteExt, FileSystem, Pipe, ReadBuf, RootFileSystemBuilder, }; use wasmer::{FunctionEnv, Imports, Module, Store}; -use wasmer_wasix::runtime::task_manager::{tokio::TokioTaskManager, InlineWaker}; +use wasmer_wasix::runtime::{ + module_cache::ModuleHash, + task_manager::{tokio::TokioTaskManager, InlineWaker}, +}; use wasmer_wasix::types::wasi::{Filesize, Timestamp}; use wasmer_wasix::{ generate_import_object_from_env, get_wasi_version, FsError, PluggableRuntime, VirtualFile, @@ -119,12 +122,16 @@ impl<'a> WasiTest<'a> { wasm_module.read_to_end(&mut out)?; out }; + let module_hash = ModuleHash::hash(&wasm_bytes); let module = Module::new(store, wasm_bytes)?; let (builder, _tempdirs, mut stdin_tx, stdout_rx, stderr_rx) = { InlineWaker::block_on(async { self.create_wasi_env(filesystem_kind).await }) }?; - let (instance, _wasi_env) = builder.runtime(Arc::new(rt)).instantiate(module, store)?; + let (instance, _wasi_env) = + builder + .runtime(Arc::new(rt)) + .instantiate_ext(module, module_hash, store)?; let start = instance.exports.get_function("_start")?;