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