wasmer_wasix/syscalls/wasix/
proc_fork.rs

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