Skip to main content

wasmer_wasix/syscalls/wasix/
proc_spawn3.rs

1use virtual_mio::block_on;
2use wasmer_wasix_types::wasi::ProcSpawnFdOpName;
3
4use super::*;
5use crate::{VIRTUAL_ROOT_FD, WasiFs, syscalls::*};
6
7/// Spawns a new sub-process (posix-spawn style) with proper `WasmPtr<WasmPtr<u8>>` string lists.
8///
9/// Successor to `proc_spawn2`. `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#[instrument(
13    level = "trace",
14    skip_all,
15    fields(name = field::Empty, full_path = field::Empty, pid = field::Empty, tid = field::Empty, %args_len),
16    ret)]
17pub fn proc_spawn3<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    fd_ops: WasmPtr<ProcSpawnFdOp<M>, M>,
26    fd_ops_len: M::Offset,
27    signal_actions: WasmPtr<SignalDisposition, M>,
28    signal_actions_len: M::Offset,
29    search_path: Bool,
30    path: WasmPtr<u8, M>,
31    path_len: M::Offset,
32    ret: WasmPtr<Pid, M>,
33) -> Result<Errno, WasiError> {
34    WasiEnv::do_pending_operations(&mut ctx)?;
35
36    let memory = unsafe { ctx.data().memory_view(&ctx) };
37    let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) };
38    Span::current().record("name", name.as_str());
39    let args = wasi_try_ok!(read_string_array(&memory, args, args_len));
40
41    let envs = if !envs.is_null() {
42        let envs = wasi_try_ok!(read_string_array(&memory, envs, envs_len));
43        Some(wasi_try_ok!(parse_env_entries(envs)))
44    } else {
45        None
46    };
47
48    let signals = if !signal_actions.is_null() {
49        let signal_actions = wasi_try_mem_ok!(signal_actions.slice(&memory, signal_actions_len));
50        let mut vec = Vec::with_capacity(signal_actions.len() as usize);
51        for s in wasi_try_mem_ok!(signal_actions.access()).iter() {
52            vec.push(*s);
53        }
54        Some(vec)
55    } else {
56        None
57    };
58
59    let fd_ops = if !fd_ops.is_null() {
60        let fd_ops = wasi_try_mem_ok!(fd_ops.slice(&memory, fd_ops_len));
61        let mut vec = Vec::with_capacity(fd_ops.len() as usize);
62        for s in wasi_try_mem_ok!(fd_ops.access()).iter() {
63            vec.push(*s);
64        }
65        vec
66    } else {
67        vec![]
68    };
69
70    let path = if path.is_null() {
71        None
72    } else {
73        Some(unsafe { get_input_str_ok!(&memory, path, path_len) })
74    };
75
76    proc_spawn3_impl(
77        ctx,
78        &mut name,
79        args,
80        envs,
81        fd_ops,
82        signals,
83        search_path,
84        path.as_deref(),
85        ret,
86    )
87}
88
89pub(crate) fn proc_spawn3_impl<M: MemorySize>(
90    mut ctx: FunctionEnvMut<'_, WasiEnv>,
91    name: &mut String,
92    args: Vec<String>,
93    envs: Option<Vec<(String, String)>>,
94    fd_ops: Vec<ProcSpawnFdOp<M>>,
95    signals: Option<Vec<SignalDisposition>>,
96    search_path: Bool,
97    path: Option<&str>,
98    ret: WasmPtr<Pid, M>,
99) -> Result<Errno, WasiError> {
100    let memory = unsafe { ctx.data().memory_view(&ctx) };
101
102    // Convert relative paths into absolute paths
103    if search_path == Bool::True && !name.contains('/') {
104        let path = if let Some(path) = path {
105            path.split(':').collect::<Vec<_>>()
106        } else {
107            vec!["/usr/local/bin", "/bin", "/usr/bin"]
108        };
109        let (_, state, inodes) =
110            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
111        match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), name) {
112            FindExecutableResult::Found(p) => *name = p,
113            FindExecutableResult::AccessError => return Ok(Errno::Access),
114            FindExecutableResult::NotFound => return Ok(Errno::Noexec),
115        }
116    } else if name.starts_with("./") {
117        *name = ctx.data().state.fs.relative_path_to_absolute(name.clone());
118    }
119
120    Span::current().record("full_path", name.as_str());
121
122    // Fork the environment which will copy all the open file handlers
123    // and associate a new context but otherwise shares things like the
124    // file system interface. The handle to the forked process is stored
125    // in the parent process context
126    let (mut child_env, mut child_handle) = match ctx.data().fork() {
127        Ok(p) => p,
128        Err(err) => {
129            debug!("could not fork process: {err}");
130            // TODO: evaluate the appropriate error code, document it in the spec.
131            return Ok(Errno::Perm);
132        }
133    };
134
135    {
136        let mut inner = ctx.data().process.lock();
137        inner.children.push(child_env.process.clone());
138    }
139
140    // Setup some properties in the child environment
141    let pid = child_env.pid();
142    let tid = child_env.tid();
143    let child_finished = child_env.process.finished.clone();
144    let tasks = child_env.tasks().clone();
145    wasi_try_mem_ok!(ret.write(&memory, pid.raw()));
146    Span::current()
147        .record("pid", pid.raw())
148        .record("tid", tid.raw());
149
150    _prepare_wasi(&mut child_env, Some(args), envs, signals);
151
152    for fd_op in fd_ops {
153        wasi_try_ok!(apply_fd_op(&mut child_env, &memory, &fd_op));
154    }
155
156    // Create the process and drop the context
157    let bin_factory = Box::new(child_env.bin_factory.clone());
158
159    let mut builder = Some(child_env);
160
161    let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
162        Ok(task) => {
163            if let Err(err) = propagate_virtual_task_completion(&tasks, task, child_finished) {
164                return Ok(err.into());
165            }
166            Ok(())
167        }
168        Err(err) => {
169            if !err.is_not_found() {
170                error!("builtin failed - {}", err);
171            }
172
173            let env = builder.take().unwrap();
174
175            // Spawn a new process with this current execution environment
176            block_on(bin_factory.spawn(name.clone(), env)).map(|_| ())
177        }
178    };
179
180    match process {
181        Ok(_) => {
182            ctx.data_mut().owned_handles.push(child_handle);
183            trace!(child_pid = %pid, "spawned sub-process");
184            Ok(Errno::Success)
185        }
186        Err(err) => {
187            let err_exit_code = conv_spawn_err_to_exit_code(&err);
188
189            debug!(child_pid = %pid, "process failed with (err={})", err_exit_code);
190
191            Ok(Errno::Noexec)
192        }
193    }
194}
195
196pub(crate) fn apply_fd_op<M: MemorySize>(
197    env: &mut WasiEnv,
198    memory: &MemoryView,
199    op: &ProcSpawnFdOp<M>,
200) -> Result<(), Errno> {
201    match op.cmd {
202        ProcSpawnFdOpName::Close => {
203            if let Ok(fd) = env.state.fs.get_fd(op.fd)
204                && !fd.is_stdio
205                && fd.inode.is_preopened
206            {
207                trace!("Skipping close FD action for pre-opened FD ({})", op.fd);
208                return Ok(());
209            }
210            env.state.fs.close_fd(op.fd)
211        }
212        ProcSpawnFdOpName::Dup2 => {
213            let flush_target = env.state.fs.dup2_at(op.src_fd, op.fd)?;
214            if let Some(file) = flush_target {
215                block_on(WasiFs::flush_file_best_effort(file));
216            }
217            Ok(())
218        }
219        ProcSpawnFdOpName::Open => {
220            let mut name = unsafe {
221                WasmPtr::<u8, M>::new(op.name)
222                    .read_utf8_string(memory, op.name_len)
223                    .map_err(mem_error_to_wasi)?
224            };
225            name = env.state.fs.relative_path_to_absolute(name.to_owned());
226            match path_open_internal(
227                env,
228                VIRTUAL_ROOT_FD,
229                op.dirflags,
230                &name,
231                op.oflags,
232                op.fs_rights_base,
233                op.fs_rights_inheriting,
234                op.fdflags,
235                op.fdflagsext,
236                Some(op.fd),
237            ) {
238                Err(e) => {
239                    tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
240                    Err(Errno::Io)
241                }
242                Ok(Err(e)) => Err(e),
243                Ok(Ok(_)) => Ok(()),
244            }
245        }
246        ProcSpawnFdOpName::Chdir => {
247            let mut path = unsafe {
248                WasmPtr::<u8, M>::new(op.name)
249                    .read_utf8_string(memory, op.name_len)
250                    .map_err(mem_error_to_wasi)?
251            };
252            path = env.state.fs.relative_path_to_absolute(path.to_owned());
253            chdir_internal(env, &path)
254        }
255        ProcSpawnFdOpName::Fchdir => {
256            let fd = env.state.fs.get_fd(op.fd)?;
257            let inode_kind = fd.inode.read();
258            match inode_kind.deref() {
259                Kind::Dir { path, .. } => {
260                    let path = path.to_str().ok_or(Errno::Notsup)?;
261                    env.state.fs.set_current_dir(path);
262                    Ok(())
263                }
264                _ => Err(Errno::Notdir),
265            }
266        }
267        _ => Err(Errno::Inval),
268    }
269}