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 mut args: Vec<_> = args
57        .trim_end_matches(['\r', '\n'])
58        .lines()
59        .map(str::to_owned)
60        .collect();
61    if args.is_empty() {
62        // POSIX expects argv[0] to be present even if caller passed empty argv.
63        args.push(name.clone());
64    }
65
66    let envs = if !envs.is_null() {
67        let envs = envs.read_utf8_string(&memory, envs_len).map_err(|err| {
68            warn!("failed to execve as the envs could not be read - {}", err);
69            WasiError::Exit(Errno::Inval.into())
70        })?;
71
72        let envs = envs
73            .split(&['\n', '\r'])
74            .map(|a| a.to_string())
75            .filter(|a| !a.is_empty());
76
77        let mut vec = vec![];
78        for env in envs {
79            let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval));
80
81            vec.push((key.to_string(), value.to_string()));
82        }
83
84        Some(vec)
85    } else {
86        None
87    };
88
89    // Convert relative paths into absolute paths
90    if search_path == Bool::True && !name.contains('/') {
91        let path_str;
92
93        let path = if path.is_null() {
94            vec!["/usr/local/bin", "/bin", "/usr/bin"]
95        } else {
96            path_str = path.read_utf8_string(&memory, path_len).map_err(|err| {
97                warn!("failed to execve as the path could not be read - {}", err);
98                WasiError::Exit(Errno::Inval.into())
99            })?;
100            path_str.split(':').collect()
101        };
102        let (_, state, inodes) =
103            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
104        match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), &name) {
105            FindExecutableResult::Found(p) => name = p,
106            FindExecutableResult::AccessError => return Ok(Errno::Access),
107            FindExecutableResult::NotFound => return Ok(Errno::Noent),
108        }
109    } else if name.starts_with("./") {
110        name = ctx.data().state.fs.relative_path_to_absolute(name);
111    }
112
113    if search_path == Bool::False && !name.starts_with('/') {
114        name = ctx.data().state.fs.relative_path_to_absolute(name);
115    }
116
117    trace!(name);
118
119    // ENAMETOOLONG: any path component > 255 bytes
120    if name.split('/').any(|seg| seg.len() > 255) {
121        return Ok(Errno::Nametoolong);
122    }
123
124    if name.contains('/') {
125        let (_, state, inodes) =
126            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
127        match state
128            .fs
129            .get_inode_at_path(inodes, VIRTUAL_ROOT_FD, &name, true)
130        {
131            Ok(_) => (),
132            Err(Errno::Notdir) => return Ok(Errno::Notdir),
133            Err(Errno::Noent) => return Ok(Errno::Noent),
134            Err(Errno::Access) => return Ok(Errno::Access),
135            Err(_) => (),
136        }
137    }
138
139    // Convert the preopen directories
140    let preopen = ctx.data().state.preopen.clone();
141
142    // Get the current working directory
143    let (_, cur_dir) = {
144        let (memory, state, inodes) =
145            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
146        match state.fs.get_current_dir(inodes, crate::VIRTUAL_ROOT_FD) {
147            Ok(a) => a,
148            Err(err) => {
149                warn!("failed to create subprocess for fork - {}", err);
150                return Err(WasiError::Exit(err.into()));
151            }
152        }
153    };
154
155    let new_store = ctx.data().runtime.new_store();
156
157    // If we are in a vfork we need to first spawn a subprocess of this type
158    // with the forked WasiEnv, then do a longjmp back to the vfork point.
159    if let Some(mut vfork) = ctx.data_mut().vfork.take() {
160        // Needed in case an error happens and we need to get back into the child process
161        let mut child_env = Box::new(ctx.data().clone());
162
163        // We will need the child pid later
164        let child_pid = ctx.data().process.pid();
165
166        tracing::debug!(
167            %child_pid,
168            vfork_pid = %vfork.env.process.pid(),
169            "proc_exec in presence of vfork"
170        );
171
172        // Restore the WasiEnv to the point when we vforked
173        let mut vfork_env = vfork.env.clone();
174        vfork_env.swap_inner(ctx.data_mut());
175        std::mem::swap(vfork_env.as_mut(), ctx.data_mut());
176        let mut wasi_env = *vfork_env;
177        wasi_env.owned_handles.push(vfork.handle.clone());
178        _prepare_wasi(&mut wasi_env, Some(args), envs, None);
179
180        // Record the stack offsets before we give up ownership of the wasi_env
181        let stack_lower = wasi_env.layout.stack_lower;
182        let stack_upper = wasi_env.layout.stack_upper;
183
184        // Spawn a new process with this current execution environment
185        let mut err_exit_code: ExitCode = Errno::Success.into();
186
187        let spawn_result = {
188            let bin_factory = Box::new(ctx.data().bin_factory.clone());
189            let tasks = wasi_env.tasks().clone();
190
191            let mut config = Some(wasi_env);
192
193            match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut config) {
194                Ok(a) => Ok(()),
195                Err(err) => {
196                    if !err.is_not_found() {
197                        error!("builtin failed - {}", err);
198                    }
199
200                    let env = config.take().unwrap();
201
202                    let name_inner = name.clone();
203                    __asyncify_light(ctx.data(), None, async {
204                        let ret = bin_factory.spawn(name_inner, env).await;
205                        match ret {
206                            Ok(ret) => {
207                                trace!(%child_pid, "spawned sub-process");
208                                Ok(())
209                            }
210                            Err(err) => {
211                                err_exit_code = conv_spawn_err_to_exit_code(&err);
212
213                                debug!(%child_pid, "process failed with (err={})", err_exit_code);
214
215                                Err(Errno::Noexec)
216                            }
217                        }
218                    })
219                    .unwrap()
220                }
221            }
222        };
223
224        match spawn_result {
225            Err(e) => {
226                // We failed to spawn a new process - put the child env back
227                child_env.swap_inner(ctx.data_mut());
228                std::mem::swap(child_env.as_mut(), ctx.data_mut());
229
230                // Put back the vfork we previously took from here
231                ctx.data_mut().vfork = Some(vfork);
232                return Ok(e);
233            }
234            Ok(()) => {
235                // We spawned a new process - put the parent env back
236                ctx.data_mut().swap_inner(&mut vfork.env);
237                std::mem::swap(ctx.data_mut(), &mut vfork.env);
238
239                let Some(asyncify_info) = vfork.asyncify else {
240                    // vfork without asyncify only forks the WasiEnv, which we have restored
241                    // above. Restoring the control flow is done on the guest side.
242                    // See `proc_fork_env()` for information about this.
243
244                    return Ok(Errno::Success);
245                };
246
247                // Jump back to the vfork point and continue execution
248                // note: fork does not return any values hence passing `()`
249                let rewind_stack = asyncify_info.rewind_stack.freeze();
250                let store_data = asyncify_info.store_data;
251                unwind::<M, _>(ctx, move |mut ctx, _, _| {
252                    // Rewind the stack
253                    match rewind::<M, _>(
254                        ctx,
255                        None,
256                        rewind_stack,
257                        store_data,
258                        ForkResult {
259                            pid: child_pid.raw() as Pid,
260                            ret: Errno::Success,
261                        },
262                    ) {
263                        Errno::Success => OnCalledAction::InvokeAgain,
264                        err => {
265                            warn!("fork failed - could not rewind the stack - errno={}", err);
266                            OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
267                        }
268                    }
269                })?;
270                Ok(Errno::Success)
271            }
272        }
273    }
274    // Otherwise we need to unwind the stack to get out of the current executing
275    // callstack, steal the memory/WasiEnv and switch it over to a new thread
276    // on the new module
277    else {
278        // Prepare the environment
279        let mut wasi_env = ctx.data().clone();
280        _prepare_wasi(&mut wasi_env, Some(args), envs, None);
281
282        // Get a reference to the runtime
283        let bin_factory = ctx.data().bin_factory.clone();
284        let tasks = wasi_env.tasks().clone();
285
286        // Create the process and drop the context
287        let bin_factory = Box::new(ctx.data().bin_factory.clone());
288
289        let mut builder = Some(wasi_env);
290
291        let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
292            Ok(a) => Ok(a),
293            Err(err) => {
294                if !err.is_not_found() {
295                    error!("builtin failed - {}", err);
296                }
297
298                let env = builder.take().unwrap();
299
300                // Spawn a new process with this current execution environment
301                block_on(bin_factory.spawn(name, env))
302            }
303        };
304
305        match process {
306            Ok(mut process) => {
307                // If we support deep sleeping then we switch to deep sleep mode
308                let env = ctx.data();
309
310                let thread = env.thread.clone();
311
312                // The poller will wait for the process to actually finish
313                let res = __asyncify_with_deep_sleep::<M, _, _>(ctx, async move {
314                    process
315                        .wait_finished()
316                        .await
317                        .unwrap_or_else(|_| Errno::Child.into())
318                        .to_native()
319                })?;
320                match res {
321                    AsyncifyAction::Finish(mut ctx, result) => {
322                        // When we arrive here the process should already be terminated
323                        let exit_code = ExitCode::from_native(result);
324                        ctx.data().process.terminate(exit_code);
325                        WasiEnv::process_signals_and_exit(&mut ctx)?;
326                        Err(WasiError::Exit(Errno::Unknown.into()))
327                    }
328                    AsyncifyAction::Unwind => Ok(Errno::Success),
329                }
330            }
331            Err(err) => {
332                warn!(
333                    "failed to execve as the process could not be spawned (fork)[0] - {}",
334                    err
335                );
336                Ok(Errno::Noexec)
337            }
338        }
339    }
340}
341
342pub(crate) enum FindExecutableResult {
343    Found(String),
344    AccessError,
345    NotFound,
346}
347
348pub(crate) fn find_executable_in_path<'a>(
349    fs: &WasiFs,
350    inodes: &WasiInodes,
351    path: impl IntoIterator<Item = &'a str>,
352    file_name: &str,
353) -> FindExecutableResult {
354    let mut encountered_eaccess = false;
355    for p in path {
356        let full_path = format!("{}/{}", p.trim_end_matches('/'), file_name);
357        match fs.get_inode_at_path(inodes, VIRTUAL_ROOT_FD, &full_path, true) {
358            Ok(_) => return FindExecutableResult::Found(full_path),
359            Err(Errno::Access) => encountered_eaccess = true,
360            Err(_) => (),
361        }
362    }
363
364    if encountered_eaccess {
365        FindExecutableResult::AccessError
366    } else {
367        FindExecutableResult::NotFound
368    }
369}