Skip to main content

wasmer_wasix/syscalls/wasix/
proc_exec4.rs

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