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    // A script is handed to its interpreter under the path the caller spelled.
103    // A PATH search is the exception: there the resolved path is what Unix
104    // passes on, so leave this unset and let the lookup supply it.
105    let searches_path = search_path == Bool::True && !name.contains('/');
106    let invoked_as = (!searches_path).then(|| name.clone());
107
108    // Convert relative paths into absolute paths
109    if search_path == Bool::True && !name.contains('/') {
110        let path = if let Some(path) = path {
111            path.split(':').collect::<Vec<_>>()
112        } else {
113            vec!["/usr/local/bin", "/bin", "/usr/bin"]
114        };
115        let (_, state, inodes) =
116            unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
117        match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), name) {
118            FindExecutableResult::Found(p) => *name = p,
119            FindExecutableResult::AccessError => return Ok(Errno::Access),
120            // Nothing by that name on PATH is ENOENT. ENOEXEC means the file
121            // was found but is not an executable format, which is what the
122            // spawn failure below reports. proc_exec4 already gets this right.
123            FindExecutableResult::NotFound => return Ok(Errno::Noent),
124        }
125    } else if name.starts_with("./") {
126        *name = ctx.data().state.fs.relative_path_to_absolute(name.clone());
127    }
128
129    // A caller that turned the PATH search off named a file, so resolve it
130    // against the current directory. Left relative, a name without a slash
131    // reaches the binary factory looking exactly like something to search the
132    // PATH for, and `script` would run `/bin/script`.
133    if search_path == Bool::False && !name.starts_with('/') {
134        *name = ctx.data().state.fs.relative_path_to_absolute(name.clone());
135    }
136
137    Span::current().record("full_path", name.as_str());
138
139    // Fork the environment which will copy all the open file handlers
140    // and associate a new context but otherwise shares things like the
141    // file system interface. The handle to the forked process is stored
142    // in the parent process context
143    let (mut child_env, mut child_handle) = match ctx.data().fork() {
144        Ok(p) => p,
145        Err(err) => {
146            debug!("could not fork process: {err}");
147            // TODO: evaluate the appropriate error code, document it in the spec.
148            return Ok(Errno::Perm);
149        }
150    };
151
152    {
153        let mut inner = ctx.data().process.lock();
154        inner.children.push(child_env.process.clone());
155    }
156
157    // Setup some properties in the child environment
158    let pid = child_env.pid();
159    let tid = child_env.tid();
160    let child_finished = child_env.process.finished.clone();
161    let tasks = child_env.tasks().clone();
162    wasi_try_mem_ok!(ret.write(&memory, pid.raw()));
163    Span::current()
164        .record("pid", pid.raw())
165        .record("tid", tid.raw());
166
167    _prepare_wasi(&mut child_env, Some(args), envs, signals);
168
169    for fd_op in fd_ops {
170        wasi_try_ok!(apply_fd_op(&mut child_env, &memory, &fd_op));
171    }
172
173    // Create the process and drop the context
174    let bin_factory = Box::new(child_env.bin_factory.clone());
175
176    let mut builder = Some(child_env);
177
178    let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
179        Ok(task) => {
180            if let Err(err) = propagate_virtual_task_completion(&tasks, task, child_finished) {
181                return Ok(err.into());
182            }
183            Ok(())
184        }
185        Err(err) => {
186            if !err.is_not_found() {
187                error!("builtin failed - {}", err);
188            }
189
190            let env = builder.take().unwrap();
191
192            // Spawn a new process with this current execution environment
193            block_on(bin_factory.spawn(name.clone(), invoked_as.clone(), env)).map(|_| ())
194        }
195    };
196
197    match process {
198        Ok(_) => {
199            ctx.data_mut().owned_handles.push(child_handle);
200            trace!(child_pid = %pid, "spawned sub-process");
201            Ok(Errno::Success)
202        }
203        Err(err) => {
204            let err_exit_code = conv_spawn_err_to_exit_code(&err);
205
206            debug!(child_pid = %pid, "process failed with (err={})", err_exit_code);
207
208            Ok(Errno::Noexec)
209        }
210    }
211}
212
213pub(crate) fn apply_fd_op<M: MemorySize>(
214    env: &mut WasiEnv,
215    memory: &MemoryView,
216    op: &ProcSpawnFdOp<M>,
217) -> Result<(), Errno> {
218    match op.cmd {
219        ProcSpawnFdOpName::Close => {
220            if let Ok(fd) = env.state.fs.get_fd(op.fd)
221                && !fd.is_stdio
222                && fd.inode.is_preopened
223            {
224                trace!("Skipping close FD action for pre-opened FD ({})", op.fd);
225                return Ok(());
226            }
227            env.state.fs.close_fd(op.fd)
228        }
229        ProcSpawnFdOpName::Dup2 => {
230            let flush_target = env.state.fs.dup2_at(op.src_fd, op.fd)?;
231            if let Some(file) = flush_target {
232                block_on(WasiFs::flush_file_best_effort(file));
233            }
234            Ok(())
235        }
236        ProcSpawnFdOpName::Open => {
237            let mut name = unsafe {
238                WasmPtr::<u8, M>::new(op.name)
239                    .read_utf8_string(memory, op.name_len)
240                    .map_err(mem_error_to_wasi)?
241            };
242            name = env.state.fs.relative_path_to_absolute(name.to_owned());
243            match path_open_internal(
244                env,
245                VIRTUAL_ROOT_FD,
246                op.dirflags,
247                &name,
248                op.oflags,
249                op.fs_rights_base,
250                op.fs_rights_inheriting,
251                op.fdflags,
252                op.fdflagsext,
253                Some(op.fd),
254            ) {
255                Err(e) => {
256                    tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
257                    Err(Errno::Io)
258                }
259                Ok(Err(e)) => Err(e),
260                Ok(Ok(_)) => Ok(()),
261            }
262        }
263        ProcSpawnFdOpName::Chdir => {
264            let mut path = unsafe {
265                WasmPtr::<u8, M>::new(op.name)
266                    .read_utf8_string(memory, op.name_len)
267                    .map_err(mem_error_to_wasi)?
268            };
269            path = env.state.fs.relative_path_to_absolute(path.to_owned());
270            chdir_internal(env, &path)
271        }
272        ProcSpawnFdOpName::Fchdir => {
273            let fd = env.state.fs.get_fd(op.fd)?;
274            let inode_kind = fd.inode.read();
275            match inode_kind.deref() {
276                Kind::Dir { path, .. } => {
277                    let path = path.to_str().ok_or(Errno::Notsup)?;
278                    env.state.fs.set_current_dir(path);
279                    Ok(())
280                }
281                _ => Err(Errno::Notdir),
282            }
283        }
284        _ => Err(Errno::Inval),
285    }
286}