1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use crate::{WasiEnv, WasiError, WasiVFork};
use wasmer::{FunctionEnvMut, Memory, MemorySize, WasmPtr};
use wasmer_wasix_types::wasi::{Errno, Pid};
/// ### `proc_fork_env()`
/// Forks the environment of the current process into a new child process.
/// The child process will start with the same memory and execution context
/// as the parent process, similar to `fork()`.
///
/// Most syscalls are undefined behavior in the child process, except for
/// `proc_exit2()` and `proc_exec3()`. `proc_exit2()` will terminate the
/// child process and set the environment back to the parent process.
/// `proc_exec3()` will exec the module in the child process and promote
/// the child process to a real process. Then it will return with the
/// environment back to the parent process.
///
/// This function differs from a traditional `vfork` in that it does not
/// modify the control flow of the program. Instead, it only forks the
/// WasiEnv, but leaves everything else (memory, call stack, store etc.)
/// untouched.
///
/// This function is intended to be used in conjunction with
/// setjmp/longjmp to build a lightweight implementation of vforking.
///
/// The value at child_pid_ptr will only be modified by a successful
/// call to this function. It will contain the process id of the
/// child process.
#[tracing::instrument(level = "trace", skip_all, fields(pid = ctx.data().process.pid().raw()), ret)]
pub fn proc_fork_env<M: MemorySize>(
mut ctx: FunctionEnvMut<'_, WasiEnv>,
child_pid_ptr: WasmPtr<Pid, M>,
) -> Result<Errno, WasiError> {
WasiEnv::do_pending_operations(&mut ctx)?;
let env = ctx.data();
if let Some(vfork) = env.vfork.as_ref() {
tracing::warn!("Nested vforks are not supported");
return Ok(Errno::Notsup);
}
// Fork the environment which will copy all the open file handlers
// and associate a new context but otherwise shares things like the
// file system interface. The handle to the forked process is stored
// in the parent process context
let (mut child_env, child_handle) = match env.fork() {
Ok(p) => p,
Err(err) => {
tracing::error!("Could not fork process: {err}");
// TODO: evaluate the appropriate error code, document it in the spec.
return Ok(Errno::Perm);
}
};
if let Some(memory) = env.process.lock().memory.clone() {
child_env.process.register_memory(memory);
}
// Write the child's PID to the provided pointer
let memory = unsafe { env.memory_view(&ctx) };
wasi_try_mem_ok!(child_pid_ptr.write(&memory, child_env.pid().raw()));
let parent_env = ctx.data_mut();
// Add the child to the parent's list of children
parent_env
.process
.lock()
.children
.push(child_env.process.clone());
// Swap the current environment with the child environment
child_env.swap_inner(parent_env);
std::mem::swap(parent_env, &mut child_env);
let previous_vfork = parent_env.vfork.replace(WasiVFork {
asyncify: None,
env: Box::new(child_env),
handle: child_handle,
});
assert!(previous_vfork.is_none()); // Already checked at the start of the function
Ok(Errno::Success)
}