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        // Recrod 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 vfork back
199                child_env.swap_inner(ctx.data_mut());
200                std::mem::swap(child_env.as_mut(), ctx.data_mut());
201
202                ctx.data_mut().vfork = Some(vfork);
203                return Ok(e);
204            }
205            Ok(()) => {
206                // Jump back to the vfork point and current on execution
207                // note: fork does not return any values hence passing `()`
208                let rewind_stack = vfork.rewind_stack.freeze();
209                let store_data = vfork.store_data;
210                unwind::<M, _>(ctx, move |mut ctx, _, _| {
211                    // Rewind the stack
212                    match rewind::<M, _>(
213                        ctx,
214                        None,
215                        rewind_stack,
216                        store_data,
217                        ForkResult {
218                            pid: child_pid.raw() as Pid,
219                            ret: Errno::Success,
220                        },
221                    ) {
222                        Errno::Success => OnCalledAction::InvokeAgain,
223                        err => {
224                            warn!("fork failed - could not rewind the stack - errno={}", err);
225                            OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
226                        }
227                    }
228                })?;
229                Ok(Errno::Success)
230            }
231        }
232    }
233    // Otherwise we need to unwind the stack to get out of the current executing
234    // callstack, steal the memory/WasiEnv and switch it over to a new thread
235    // on the new module
236    else {
237        // Prepare the environment
238        let mut wasi_env = ctx.data().clone();
239        _prepare_wasi(&mut wasi_env, Some(args), envs, None);
240
241        // Get a reference to the runtime
242        let bin_factory = ctx.data().bin_factory.clone();
243        let tasks = wasi_env.tasks().clone();
244
245        // Create the process and drop the context
246        let bin_factory = Box::new(ctx.data().bin_factory.clone());
247
248        let mut builder = Some(wasi_env);
249
250        let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
251            Ok(a) => Ok(a),
252            Err(err) => {
253                if !err.is_not_found() {
254                    error!("builtin failed - {}", err);
255                }
256
257                let env = builder.take().unwrap();
258
259                // Spawn a new process with this current execution environment
260                InlineWaker::block_on(bin_factory.spawn(name, env))
261            }
262        };
263
264        match process {
265            Ok(mut process) => {
266                // If we support deep sleeping then we switch to deep sleep mode
267                let env = ctx.data();
268
269                let thread = env.thread.clone();
270
271                // The poller will wait for the process to actually finish
272                let res = __asyncify_with_deep_sleep::<M, _, _>(ctx, async move {
273                    process
274                        .wait_finished()
275                        .await
276                        .unwrap_or_else(|_| Errno::Child.into())
277                        .to_native()
278                })?;
279                match res {
280                    AsyncifyAction::Finish(mut ctx, result) => {
281                        // When we arrive here the process should already be terminated
282                        let exit_code = ExitCode::from_native(result);
283                        ctx.data().process.terminate(exit_code);
284                        WasiEnv::process_signals_and_exit(&mut ctx)?;
285                        Err(WasiError::Exit(Errno::Unknown.into()))
286                    }
287                    AsyncifyAction::Unwind => Ok(Errno::Success),
288                }
289            }
290            Err(err) => {
291                warn!(
292                    "failed to execve as the process could not be spawned (fork)[0] - {}",
293                    err
294                );
295                Ok(Errno::Noexec)
296            }
297        }
298    }
299}
300
301pub(crate) enum FindExecutableResult {
302    Found(String),
303    AccessError,
304    NotFound,
305}
306
307pub(crate) fn find_executable_in_path<'a>(
308    fs: &WasiFs,
309    inodes: &WasiInodes,
310    path: impl IntoIterator<Item = &'a str>,
311    file_name: &str,
312) -> FindExecutableResult {
313    let mut encountered_eaccess = false;
314    for p in path {
315        let full_path = format!("{}/{}", p.trim_end_matches('/'), file_name);
316        match fs.get_inode_at_path(inodes, VIRTUAL_ROOT_FD, &full_path, true) {
317            Ok(_) => return FindExecutableResult::Found(full_path),
318            Err(Errno::Access) => encountered_eaccess = true,
319            Err(_) => (),
320        }
321    }
322
323    if encountered_eaccess {
324        FindExecutableResult::AccessError
325    } else {
326        FindExecutableResult::NotFound
327    }
328}