wasmer_wasix/syscalls/wasix/
proc_exec3.rs

1use wasmer::FromToNativeWasmType;
2
3use super::*;
4use crate::{
5    VIRTUAL_ROOT_FD, WasiFs,
6    os::task::{OwnedTaskStatus, TaskStatus},
7    syscalls::*,
8};
9
10/// Replaces the current process with a new process
11///
12/// ## Parameters
13///
14/// * `name` - Name of the process to be spawned
15/// * `args` - List of the arguments to pass the process
16///   (entries are separated by line feeds)
17/// * `envs` - List of the environment variables to pass process
18///
19/// ## Return
20///
21/// If the execution fails, returns an error code. Does not return otherwise.
22#[instrument(level = "trace", skip_all, fields(name = field::Empty, %args_len), ret)]
23pub fn proc_exec3<M: MemorySize>(
24    mut ctx: FunctionEnvMut<'_, WasiEnv>,
25    name: WasmPtr<u8, M>,
26    name_len: M::Offset,
27    args: WasmPtr<u8, M>,
28    args_len: M::Offset,
29    envs: WasmPtr<u8, M>,
30    envs_len: M::Offset,
31    search_path: Bool,
32    path: WasmPtr<u8, M>,
33    path_len: M::Offset,
34) -> Result<Errno, WasiError> {
35    WasiEnv::do_pending_operations(&mut ctx)?;
36
37    // If we were just restored the stack then we were woken after a deep sleep
38    if let Some(exit_code) = unsafe { handle_rewind::<M, i32>(&mut ctx) } {
39        // We should never get here as the process will be termined
40        // in the `WasiEnv::do_pending_operations()` call
41        let exit_code = ExitCode::from_native(exit_code);
42        ctx.data().process.terminate(exit_code);
43        return Err(WasiError::Exit(exit_code));
44    }
45
46    let memory = unsafe { ctx.data().memory_view(&ctx) };
47    let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| {
48        warn!("failed to execve as the name could not be read - {}", err);
49        WasiError::Exit(Errno::Inval.into())
50    })?;
51    Span::current().record("name", name.as_str());
52    let args = args.read_utf8_string(&memory, args_len).map_err(|err| {
53        warn!("failed to execve as the args could not be read - {}", err);
54        WasiError::Exit(Errno::Inval.into())
55    })?;
56    let args: Vec<_> = args
57        .split(&['\n', '\r'])
58        .map(|a| a.to_string())
59        .filter(|a| !a.is_empty())
60        .collect();
61
62    let envs = if !envs.is_null() {
63        let envs = envs.read_utf8_string(&memory, envs_len).map_err(|err| {
64            warn!("failed to execve as the envs could not be read - {}", err);
65            WasiError::Exit(Errno::Inval.into())
66        })?;
67
68        let envs = envs
69            .split(&['\n', '\r'])
70            .map(|a| a.to_string())
71            .filter(|a| !a.is_empty());
72
73        let mut vec = vec![];
74        for env in envs {
75            let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval));
76
77            vec.push((key.to_string(), value.to_string()));
78        }
79
80        Some(vec)
81    } else {
82        None
83    };
84
85    // Convert relative paths into absolute paths
86    if search_path == Bool::True && !name.contains('/') {
87        let path_str;
88
89        let path = if path.is_null() {
90            vec!["/usr/local/bin", "/bin", "/usr/bin"]
91        } else {
92            path_str = path.read_utf8_string(&memory, path_len).map_err(|err| {
93                warn!("failed to execve as the path could not be read - {}", err);
94                WasiError::Exit(Errno::Inval.into())
95            })?;
96            path_str.split(':').collect()
97        };
98        let (_, state, inodes) =
99            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
100        match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), &name) {
101            FindExecutableResult::Found(p) => name = p,
102            FindExecutableResult::AccessError => return Ok(Errno::Access),
103            FindExecutableResult::NotFound => return Ok(Errno::Noexec),
104        }
105    } else if name.starts_with("./") {
106        name = ctx.data().state.fs.relative_path_to_absolute(name);
107    }
108
109    trace!(name);
110
111    // Convert the preopen directories
112    let preopen = ctx.data().state.preopen.clone();
113
114    // Get the current working directory
115    let (_, cur_dir) = {
116        let (memory, state, inodes) =
117            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
118        match state.fs.get_current_dir(inodes, crate::VIRTUAL_ROOT_FD) {
119            Ok(a) => a,
120            Err(err) => {
121                warn!("failed to create subprocess for fork - {}", err);
122                return Err(WasiError::Exit(err.into()));
123            }
124        }
125    };
126
127    let new_store = ctx.data().runtime.new_store();
128
129    // If we are in a vfork we need to first spawn a subprocess of this type
130    // with the forked WasiEnv, then do a longjmp back to the vfork point.
131    if let Some(mut vfork) = ctx.data_mut().vfork.take() {
132        // Needed in case an error happens and we need to get back into the child process
133        let mut child_env = Box::new(ctx.data().clone());
134
135        // We will need the child pid later
136        let child_pid = ctx.data().process.pid();
137
138        tracing::debug!(
139            %child_pid,
140            vfork_pid = %vfork.env.process.pid(),
141            "proc_exec in presence of vfork"
142        );
143
144        // Restore the WasiEnv to the point when we vforked
145        let mut vfork_env = vfork.env.clone();
146        vfork_env.swap_inner(ctx.data_mut());
147        std::mem::swap(vfork_env.as_mut(), ctx.data_mut());
148        let mut wasi_env = *vfork_env;
149        wasi_env.owned_handles.push(vfork.handle.clone());
150        _prepare_wasi(&mut wasi_env, Some(args), envs, None);
151
152        // Record the stack offsets before we give up ownership of the wasi_env
153        let stack_lower = wasi_env.layout.stack_lower;
154        let stack_upper = wasi_env.layout.stack_upper;
155
156        // Spawn a new process with this current execution environment
157        let mut err_exit_code: ExitCode = Errno::Success.into();
158
159        let spawn_result = {
160            let bin_factory = Box::new(ctx.data().bin_factory.clone());
161            let tasks = wasi_env.tasks().clone();
162
163            let mut config = Some(wasi_env);
164
165            match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut config) {
166                Ok(a) => Ok(()),
167                Err(err) => {
168                    if !err.is_not_found() {
169                        error!("builtin failed - {}", err);
170                    }
171
172                    let env = config.take().unwrap();
173
174                    let name_inner = name.clone();
175                    __asyncify_light(ctx.data(), None, async {
176                        let ret = bin_factory.spawn(name_inner, env).await;
177                        match ret {
178                            Ok(ret) => {
179                                trace!(%child_pid, "spawned sub-process");
180                                Ok(())
181                            }
182                            Err(err) => {
183                                err_exit_code = conv_spawn_err_to_exit_code(&err);
184
185                                debug!(%child_pid, "process failed with (err={})", err_exit_code);
186
187                                Err(Errno::Noexec)
188                            }
189                        }
190                    })
191                    .unwrap()
192                }
193            }
194        };
195
196        match spawn_result {
197            Err(e) => {
198                // We failed to spawn a new process - put the child env back
199                child_env.swap_inner(ctx.data_mut());
200                std::mem::swap(child_env.as_mut(), ctx.data_mut());
201
202                // Put back the vfork we previously took from here
203                ctx.data_mut().vfork = Some(vfork);
204                return Ok(e);
205            }
206            Ok(()) => {
207                // We spawned a new process - put the parent env back
208                ctx.data_mut().swap_inner(&mut vfork.env);
209                std::mem::swap(ctx.data_mut(), &mut vfork.env);
210
211                assert!(vfork.env.context_switching_environment.is_none());
212                assert!(ctx.data().context_switching_environment.is_some());
213
214                let Some(asyncify_info) = vfork.asyncify else {
215                    // vfork without asyncify only forks the WasiEnv, which we have restored
216                    // above. Restoring the control flow is done on the guest side.
217                    // See `proc_fork_env()` for information about this.
218
219                    return Ok(Errno::Success);
220                };
221
222                // Jump back to the vfork point and continue execution
223                // note: fork does not return any values hence passing `()`
224                let rewind_stack = asyncify_info.rewind_stack.freeze();
225                let store_data = asyncify_info.store_data;
226                unwind::<M, _>(ctx, move |mut ctx, _, _| {
227                    // Rewind the stack
228                    match rewind::<M, _>(
229                        ctx,
230                        None,
231                        rewind_stack,
232                        store_data,
233                        ForkResult {
234                            pid: child_pid.raw() as Pid,
235                            ret: Errno::Success,
236                        },
237                    ) {
238                        Errno::Success => OnCalledAction::InvokeAgain,
239                        err => {
240                            warn!("fork failed - could not rewind the stack - errno={}", err);
241                            OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
242                        }
243                    }
244                })?;
245                Ok(Errno::Success)
246            }
247        }
248    }
249    // Otherwise we need to unwind the stack to get out of the current executing
250    // callstack, steal the memory/WasiEnv and switch it over to a new thread
251    // on the new module
252    else {
253        // Prepare the environment
254        let mut wasi_env = ctx.data().clone();
255        _prepare_wasi(&mut wasi_env, Some(args), envs, None);
256
257        // Get a reference to the runtime
258        let bin_factory = ctx.data().bin_factory.clone();
259        let tasks = wasi_env.tasks().clone();
260
261        // Create the process and drop the context
262        let bin_factory = Box::new(ctx.data().bin_factory.clone());
263
264        let mut builder = Some(wasi_env);
265
266        let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
267            Ok(a) => Ok(a),
268            Err(err) => {
269                if !err.is_not_found() {
270                    error!("builtin failed - {}", err);
271                }
272
273                let env = builder.take().unwrap();
274
275                // Spawn a new process with this current execution environment
276                block_on(bin_factory.spawn(name, env))
277            }
278        };
279
280        match process {
281            Ok(mut process) => {
282                // If we support deep sleeping then we switch to deep sleep mode
283                let env = ctx.data();
284
285                let thread = env.thread.clone();
286
287                // The poller will wait for the process to actually finish
288                let res = __asyncify_with_deep_sleep::<M, _, _>(ctx, async move {
289                    process
290                        .wait_finished()
291                        .await
292                        .unwrap_or_else(|_| Errno::Child.into())
293                        .to_native()
294                })?;
295                match res {
296                    AsyncifyAction::Finish(mut ctx, result) => {
297                        // When we arrive here the process should already be terminated
298                        let exit_code = ExitCode::from_native(result);
299                        ctx.data().process.terminate(exit_code);
300                        WasiEnv::process_signals_and_exit(&mut ctx)?;
301                        Err(WasiError::Exit(Errno::Unknown.into()))
302                    }
303                    AsyncifyAction::Unwind => Ok(Errno::Success),
304                }
305            }
306            Err(err) => {
307                warn!(
308                    "failed to execve as the process could not be spawned (fork)[0] - {}",
309                    err
310                );
311                Ok(Errno::Noexec)
312            }
313        }
314    }
315}
316
317pub(crate) enum FindExecutableResult {
318    Found(String),
319    AccessError,
320    NotFound,
321}
322
323pub(crate) fn find_executable_in_path<'a>(
324    fs: &WasiFs,
325    inodes: &WasiInodes,
326    path: impl IntoIterator<Item = &'a str>,
327    file_name: &str,
328) -> FindExecutableResult {
329    let mut encountered_eaccess = false;
330    for p in path {
331        let full_path = format!("{}/{}", p.trim_end_matches('/'), file_name);
332        match fs.get_inode_at_path(inodes, VIRTUAL_ROOT_FD, &full_path, true) {
333            Ok(_) => return FindExecutableResult::Found(full_path),
334            Err(Errno::Access) => encountered_eaccess = true,
335            Err(_) => (),
336        }
337    }
338
339    if encountered_eaccess {
340        FindExecutableResult::AccessError
341    } else {
342        FindExecutableResult::NotFound
343    }
344}