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