wasmer_wasix/syscalls/wasix/
proc_fork.rs

1use super::*;
2use crate::{
3    WasiThreadHandle, WasiVForkAsyncify, capture_store_snapshot,
4    os::task::OwnedTaskStatus,
5    runtime::task_manager::{TaskWasm, TaskWasmRunProperties},
6    state::context_switching::ContextSwitchingEnvironment,
7    syscalls::*,
8};
9use serde::{Deserialize, Serialize};
10use wasmer::Memory;
11
12#[derive(Serialize, Deserialize)]
13pub(crate) struct ForkResult {
14    pub pid: Pid,
15    pub ret: Errno,
16}
17
18/// ### `proc_fork()`
19/// Forks the current process into a new subprocess. If the function
20/// returns a zero then its the new subprocess. If it returns a positive
21/// number then its the current process and the $pid represents the child.
22#[instrument(level = "trace", skip_all, fields(pid = ctx.data().process.pid().raw()), ret)]
23pub fn proc_fork<M: MemorySize>(
24    mut ctx: FunctionEnvMut<'_, WasiEnv>,
25    mut copy_memory: Bool,
26    pid_ptr: WasmPtr<Pid, M>,
27) -> Result<Errno, WasiError> {
28    WasiEnv::do_pending_operations(&mut ctx)?;
29
30    wasi_try_ok!(ctx.data().ensure_static_module().map_err(|_| {
31        warn!("process forking not supported for dynamically linked modules");
32        Errno::Notsup
33    }));
34
35    if let Some(context_switching_environment) = ctx.data().context_switching_environment.as_ref()
36        && context_switching_environment.active_context_id()
37            != context_switching_environment.main_context_id()
38    {
39        warn!(
40            "process forking is only supported from the main context when using WASIX context-switching features"
41        );
42        return Ok(Errno::Notsup);
43    }
44
45    // If we were just restored then we need to return the value instead
46    if let Some(result) = unsafe { handle_rewind::<M, ForkResult>(&mut ctx) } {
47        if result.pid == 0 {
48            trace!("handle_rewind - i am child (ret={})", result.ret);
49        } else {
50            trace!(
51                "handle_rewind - i am parent (child={}, ret={})",
52                result.pid, result.ret
53            );
54        }
55        let memory = unsafe { ctx.data().memory_view(&ctx) };
56        wasi_try_mem_ok!(pid_ptr.write(&memory, result.pid));
57        return Ok(result.ret);
58    }
59    trace!(%copy_memory, "capturing");
60
61    if let Some(vfork) = ctx.data().vfork.as_ref() {
62        warn!("process forking not supported in an active vfork");
63        return Ok(Errno::Notsup);
64    }
65
66    // Fork the environment which will copy all the open file handlers
67    // and associate a new context but otherwise shares things like the
68    // file system interface. The handle to the forked process is stored
69    // in the parent process context
70    let (mut child_env, mut child_handle) = match ctx.data().fork() {
71        Ok(p) => p,
72        Err(err) => {
73            debug!("could not fork process: {err}");
74            // TODO: evaluate the appropriate error code, document it in the spec.
75            return Ok(Errno::Perm);
76        }
77    };
78    let child_pid = child_env.process.pid();
79    let child_finished = child_env.process.finished.clone();
80
81    // We write a zero to the PID before we capture the stack
82    // so that this is what will be returned to the child
83    {
84        let mut inner = ctx.data().process.lock();
85        inner.children.push(child_env.process.clone());
86    }
87    let env = ctx.data();
88    let memory = unsafe { env.memory_view(&ctx) };
89
90    // Setup some properties in the child environment
91    wasi_try_mem_ok!(pid_ptr.write(&memory, 0));
92    let pid = child_env.pid();
93    let tid = child_env.tid();
94
95    // Pass some offsets to the unwind function
96    let pid_offset = pid_ptr.offset();
97
98    // If we are not copying the memory then we act like a `vfork`
99    // instead which will pretend to be the new process for a period
100    // of time until `proc_exec` is called at which point the fork
101    // actually occurs
102    if copy_memory == Bool::False {
103        // Perform the unwind action
104        return unwind::<M, _>(ctx, move |mut ctx, mut memory_stack, rewind_stack| {
105            // Grab all the globals and serialize them
106            let store_data = crate::utils::store::capture_store_snapshot(&mut ctx.as_store_mut())
107                .serialize()
108                .unwrap();
109            let store_data = Bytes::from(store_data);
110
111            // We first fork the environment and replace the current environment
112            // so that the process can continue to prepare for the real fork as
113            // if it had actually forked
114            if let Some(memory) = ctx.data().process.lock().memory.clone() {
115                child_env.process.register_memory(memory);
116            }
117            child_env.swap_inner(ctx.data_mut());
118            std::mem::swap(ctx.data_mut(), &mut child_env);
119            let previous_vfork = ctx.data_mut().vfork.replace(WasiVFork {
120                asyncify: Some(WasiVForkAsyncify {
121                    rewind_stack: rewind_stack.clone(),
122                    store_data: store_data.clone(),
123                    is_64bit: M::is_64bit(),
124                }),
125                env: Box::new(child_env),
126                handle: child_handle,
127            });
128            assert!(previous_vfork.is_none()); // Already checked above
129
130            // Carry on as if the fork had taken place (which basically means
131            // it prevents to be the new process with the old one suspended)
132            // Rewind the stack and carry on
133            match rewind::<M, _>(
134                ctx,
135                Some(memory_stack.freeze()),
136                rewind_stack.freeze(),
137                store_data,
138                ForkResult {
139                    pid: 0,
140                    ret: Errno::Success,
141                },
142            ) {
143                Errno::Success => OnCalledAction::InvokeAgain,
144                err => {
145                    warn!("failed - could not rewind the stack - errno={}", err);
146                    OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
147                }
148            }
149        });
150    }
151
152    // Create the thread that will back this forked process
153    let state = env.state.clone();
154    let bin_factory = env.bin_factory.clone();
155
156    // Perform the unwind action
157    let snapshot = capture_store_snapshot(&mut ctx.as_store_mut());
158    unwind::<M, _>(ctx, move |mut ctx, mut memory_stack, rewind_stack| {
159        let tasks = ctx.data().tasks().clone();
160        let span = debug_span!(
161            "unwind",
162            memory_stack_len = memory_stack.len(),
163            rewind_stack_len = rewind_stack.len()
164        );
165        let _span_guard = span.enter();
166        let memory_stack = memory_stack.freeze();
167        let rewind_stack = rewind_stack.freeze();
168
169        // Grab all the globals and serialize them
170        let store_data = snapshot.serialize().unwrap();
171        let store_data = Bytes::from(store_data);
172
173        // Now we use the environment and memory references
174        let runtime = child_env.runtime.clone();
175        let tasks = child_env.tasks().clone();
176        let child_memory_stack = memory_stack.clone();
177        let child_rewind_stack = rewind_stack.clone();
178
179        let env_inner = ctx.data().inner();
180        let instance_handles = env_inner.static_module_instance_handles().unwrap();
181        let module = instance_handles.module_clone();
182        let memory = instance_handles.memory_clone();
183        let spawn_type = SpawnType::AttachMemory(match memory.copy(&ctx.as_store_ref()) {
184            Ok(shared) => shared,
185            Err(e) => return OnCalledAction::Trap(Box::new(e)),
186        });
187
188        // Spawn a new process with this current execution environment
189        let signaler = Box::new(child_env.process.clone());
190        {
191            let runtime = runtime.clone();
192            let tasks = tasks.clone();
193            let tasks_outer = tasks.clone();
194            let store_data = store_data.clone();
195
196            let run = move |mut props: TaskWasmRunProperties| {
197                let ctx = props.ctx;
198                let mut store = props.store;
199
200                // Rewind the stack and carry on
201                {
202                    trace!("rewinding child");
203                    let mut ctx = ctx.env.clone().into_mut(&mut store);
204                    let (data, mut store) = ctx.data_and_store_mut();
205                    match rewind::<M, _>(
206                        ctx,
207                        Some(child_memory_stack),
208                        child_rewind_stack,
209                        store_data.clone(),
210                        ForkResult {
211                            pid: 0,
212                            ret: Errno::Success,
213                        },
214                    ) {
215                        Errno::Success => OnCalledAction::InvokeAgain,
216                        err => {
217                            warn!(
218                                "wasm rewind failed - could not rewind the stack - errno={}",
219                                err
220                            );
221                            return;
222                        }
223                    };
224                }
225
226                // Invoke the start function
227                run::<M>(ctx, store, child_handle, None);
228            };
229
230            tasks_outer
231                .task_wasm(
232                    TaskWasm::new(Box::new(run), child_env, module, false, false)
233                        .with_globals(snapshot)
234                        .with_memory(spawn_type),
235                )
236                .map_err(|err| {
237                    warn!(
238                        "failed to fork as the process could not be spawned - {}",
239                        err
240                    );
241                    err
242                })
243                .ok();
244        };
245
246        // Rewind the stack and carry on
247        match rewind::<M, _>(
248            ctx,
249            Some(memory_stack),
250            rewind_stack,
251            store_data,
252            ForkResult {
253                pid: child_pid.raw() as Pid,
254                ret: Errno::Success,
255            },
256        ) {
257            Errno::Success => OnCalledAction::InvokeAgain,
258            err => {
259                warn!("failed - could not rewind the stack - errno={}", err);
260                OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
261            }
262        }
263    })
264}
265
266fn run<M: MemorySize>(
267    ctx: WasiFunctionEnv,
268    mut store: Store,
269    child_handle: WasiThreadHandle,
270    rewind_state: Option<(RewindState, RewindResultType)>,
271) -> ExitCode {
272    let env = ctx.data(&store);
273    let tasks = env.tasks().clone();
274    let pid = env.pid();
275    let tid = env.tid();
276
277    // If we need to rewind then do so
278    if let Some((rewind_state, rewind_result)) = rewind_state {
279        let mut ctx = ctx.env.clone().into_mut(&mut store);
280        let res = rewind_ext::<M>(
281            &mut ctx,
282            Some(rewind_state.memory_stack),
283            rewind_state.rewind_stack,
284            rewind_state.store_data,
285            rewind_result,
286        );
287        if res != Errno::Success {
288            return res.into();
289        }
290    }
291
292    let mut ret: ExitCode = Errno::Success.into();
293    let (mut store, err) = if ctx.data(&store).thread.is_main() {
294        trace!(%pid, %tid, "re-invoking main");
295        let start = ctx
296            .data(&store)
297            .inner()
298            .static_module_instance_handles()
299            .unwrap()
300            .start
301            .clone()
302            .unwrap();
303        ContextSwitchingEnvironment::run_main_context(&ctx, store, start.into(), vec![])
304    } else {
305        trace!(%pid, %tid, "re-invoking thread_spawn");
306        let start = ctx
307            .data(&store)
308            .inner()
309            .static_module_instance_handles()
310            .unwrap()
311            .thread_spawn
312            .clone()
313            .unwrap();
314        let params = vec![0i32.into(), 0i32.into()];
315        ContextSwitchingEnvironment::run_main_context(&ctx, store, start.into(), params)
316    };
317    if let Err(err) = err {
318        match err.downcast::<WasiError>() {
319            Ok(WasiError::Exit(exit_code)) => {
320                ret = exit_code;
321            }
322            Ok(WasiError::DeepSleep(deep)) => {
323                trace!(%pid, %tid, "entered a deep sleep");
324
325                // Create the respawn function
326                let respawn = {
327                    let tasks = tasks.clone();
328                    let rewind_state = deep.rewind;
329                    move |ctx, store, rewind_result| {
330                        run::<M>(
331                            ctx,
332                            store,
333                            child_handle,
334                            Some((
335                                rewind_state,
336                                RewindResultType::RewindWithResult(rewind_result),
337                            )),
338                        );
339                    }
340                };
341
342                /// Spawns the WASM process after a trigger
343                unsafe {
344                    tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger)
345                };
346                return Errno::Success.into();
347            }
348            _ => {}
349        }
350    }
351    trace!(%pid, %tid, "child exited (code = {})", ret);
352
353    // Clean up the environment and return the result
354    ctx.on_exit((&mut store), Some(ret));
355
356    // We drop the handle at the last moment which will close the thread
357    drop(child_handle);
358    ret
359}