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    wasi_try_mem_ok!(ret.write(&memory, pid.raw()));
144    Span::current()
145        .record("pid", pid.raw())
146        .record("tid", tid.raw());
147
148    _prepare_wasi(&mut child_env, Some(args), envs, signals);
149
150    for fd_op in fd_ops {
151        wasi_try_ok!(apply_fd_op(&mut child_env, &memory, &fd_op));
152    }
153
154    // Create the process and drop the context
155    let bin_factory = Box::new(child_env.bin_factory.clone());
156
157    let mut builder = Some(child_env);
158
159    let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
160        Ok(a) => Ok(a),
161        Err(err) => {
162            if !err.is_not_found() {
163                error!("builtin failed - {}", err);
164            }
165
166            let env = builder.take().unwrap();
167
168            // Spawn a new process with this current execution environment
169            block_on(bin_factory.spawn(name.clone(), env))
170        }
171    };
172
173    match process {
174        Ok(_) => {
175            ctx.data_mut().owned_handles.push(child_handle);
176            trace!(child_pid = %pid, "spawned sub-process");
177            Ok(Errno::Success)
178        }
179        Err(err) => {
180            let err_exit_code = conv_spawn_err_to_exit_code(&err);
181
182            debug!(child_pid = %pid, "process failed with (err={})", err_exit_code);
183
184            Ok(Errno::Noexec)
185        }
186    }
187}
188
189pub(crate) fn apply_fd_op<M: MemorySize>(
190    env: &mut WasiEnv,
191    memory: &MemoryView,
192    op: &ProcSpawnFdOp<M>,
193) -> Result<(), Errno> {
194    match op.cmd {
195        ProcSpawnFdOpName::Close => {
196            if let Ok(fd) = env.state.fs.get_fd(op.fd)
197                && !fd.is_stdio
198                && fd.inode.is_preopened
199            {
200                trace!("Skipping close FD action for pre-opened FD ({})", op.fd);
201                return Ok(());
202            }
203            env.state.fs.close_fd(op.fd)
204        }
205        ProcSpawnFdOpName::Dup2 => {
206            let flush_target = env.state.fs.dup2_at(op.src_fd, op.fd)?;
207            if let Some(file) = flush_target {
208                block_on(WasiFs::flush_file_best_effort(file));
209            }
210            Ok(())
211        }
212        ProcSpawnFdOpName::Open => {
213            let mut name = unsafe {
214                WasmPtr::<u8, M>::new(op.name)
215                    .read_utf8_string(memory, op.name_len)
216                    .map_err(mem_error_to_wasi)?
217            };
218            name = env.state.fs.relative_path_to_absolute(name.to_owned());
219            match path_open_internal(
220                env,
221                VIRTUAL_ROOT_FD,
222                op.dirflags,
223                &name,
224                op.oflags,
225                op.fs_rights_base,
226                op.fs_rights_inheriting,
227                op.fdflags,
228                op.fdflagsext,
229                Some(op.fd),
230            ) {
231                Err(e) => {
232                    tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
233                    Err(Errno::Io)
234                }
235                Ok(Err(e)) => Err(e),
236                Ok(Ok(_)) => Ok(()),
237            }
238        }
239        ProcSpawnFdOpName::Chdir => {
240            let mut path = unsafe {
241                WasmPtr::<u8, M>::new(op.name)
242                    .read_utf8_string(memory, op.name_len)
243                    .map_err(mem_error_to_wasi)?
244            };
245            path = env.state.fs.relative_path_to_absolute(path.to_owned());
246            chdir_internal(env, &path)
247        }
248        ProcSpawnFdOpName::Fchdir => {
249            let fd = env.state.fs.get_fd(op.fd)?;
250            let inode_kind = fd.inode.read();
251            match inode_kind.deref() {
252                Kind::Dir { path, .. } => {
253                    let path = path.to_str().ok_or(Errno::Notsup)?;
254                    env.state.fs.set_current_dir(path);
255                    Ok(())
256                }
257                _ => Err(Errno::Notdir),
258            }
259        }
260        _ => Err(Errno::Inval),
261    }
262}