wasmer_wasix/syscalls/wasix/
proc_spawn2.rs

1use wasmer::FromToNativeWasmType;
2use wasmer_wasix_types::wasi::ProcSpawnFdOpName;
3
4use super::*;
5use crate::{
6    VIRTUAL_ROOT_FD, WasiFs,
7    os::task::{OwnedTaskStatus, TaskStatus},
8    syscalls::*,
9};
10
11/// Replaces the current process with a new process
12///
13/// ## Parameters
14///
15/// * `name` - Name of the process to be spawned
16/// * `args` - List of the arguments to pass the process
17///   (entries are separated by line feeds)
18/// * `envs` - List of the environment variables to pass process
19///
20/// ## Return
21///
22/// If the execution fails, returns an error code. Does not return otherwise.
23#[instrument(
24    level = "trace",
25    skip_all,
26    fields(name = field::Empty, full_path = field::Empty, pid = field::Empty, tid = field::Empty, %args_len),
27    ret)]
28pub fn proc_spawn2<M: MemorySize>(
29    mut ctx: FunctionEnvMut<'_, WasiEnv>,
30    name: WasmPtr<u8, M>,
31    name_len: M::Offset,
32    args: WasmPtr<u8, M>,
33    args_len: M::Offset,
34    envs: WasmPtr<u8, M>,
35    envs_len: M::Offset,
36    fd_ops: WasmPtr<ProcSpawnFdOp<M>, M>,
37    fd_ops_len: M::Offset,
38    signal_actions: WasmPtr<SignalDisposition, M>,
39    signal_actions_len: M::Offset,
40    search_path: Bool,
41    path: WasmPtr<u8, M>,
42    path_len: M::Offset,
43    ret: WasmPtr<Pid, M>,
44) -> Result<Errno, WasiError> {
45    WasiEnv::do_pending_operations(&mut ctx)?;
46
47    let env = ctx.data();
48    let memory = unsafe { ctx.data().memory_view(&ctx) };
49    let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) };
50    Span::current().record("name", name.as_str());
51    let args = unsafe { get_input_str_ok!(&memory, args, args_len) };
52    let args: Vec<_> = args
53        .split(&['\n', '\r'])
54        .map(|a| a.to_string())
55        .filter(|a| !a.is_empty())
56        .collect();
57
58    let envs = if !envs.is_null() {
59        let envs = unsafe { get_input_str_ok!(&memory, envs, envs_len) };
60
61        let envs = envs
62            .split(&['\n', '\r'])
63            .map(|a| a.to_string())
64            .filter(|a| !a.is_empty());
65
66        let mut vec = vec![];
67        for env in envs {
68            let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval));
69            vec.push((key.to_string(), value.to_string()));
70        }
71
72        Some(vec)
73    } else {
74        None
75    };
76
77    let signals = if !signal_actions.is_null() {
78        let signal_actions = wasi_try_mem_ok!(signal_actions.slice(&memory, signal_actions_len));
79        let mut vec = Vec::with_capacity(signal_actions.len() as usize);
80        for s in wasi_try_mem_ok!(signal_actions.access()).iter() {
81            vec.push(*s);
82        }
83        Some(vec)
84    } else {
85        None
86    };
87
88    let fd_ops = if !fd_ops.is_null() {
89        let fd_ops = wasi_try_mem_ok!(fd_ops.slice(&memory, fd_ops_len));
90        let mut vec = Vec::with_capacity(fd_ops.len() as usize);
91        for s in wasi_try_mem_ok!(fd_ops.access()).iter() {
92            vec.push(*s);
93        }
94        vec
95    } else {
96        vec![]
97    };
98
99    // Convert relative paths into absolute paths
100    if search_path == Bool::True && !name.contains('/') {
101        let path_str;
102
103        let path = if path.is_null() {
104            vec!["/usr/local/bin", "/bin", "/usr/bin"]
105        } else {
106            path_str = unsafe { get_input_str_ok!(&memory, path, path_len) };
107            path_str.split(':').collect()
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);
118    }
119
120    Span::current().record("full_path", &name);
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, 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
189fn 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            if let Ok(fd) = env.state.fs.get_fd(op.fd)
207                && !fd.is_stdio
208                && fd.inode.is_preopened
209            {
210                warn!(
211                    "FD {} is a pre-open and should not be closed, \
212                        but will be closed in response to a dup2 FD action. \
213                        This will likely break stuff.",
214                    op.fd
215                );
216            }
217            if env.state.fs.get_fd(op.fd).is_ok() {
218                env.state.fs.close_fd(op.fd)?;
219            }
220
221            let mut fd_map = env.state.fs.fd_map.write().unwrap();
222            let fd_entry = fd_map.get(op.src_fd).ok_or(Errno::Badf)?;
223
224            let new_fd_entry = Fd {
225                // TODO: verify this is correct
226                inner: FdInner {
227                    offset: fd_entry.inner.offset.clone(),
228                    rights: fd_entry.inner.rights_inheriting,
229                    fd_flags: {
230                        let mut f = fd_entry.inner.fd_flags;
231                        f.set(Fdflagsext::CLOEXEC, false);
232                        f
233                    },
234                    ..fd_entry.inner
235                },
236                inode: fd_entry.inode.clone(),
237                ..*fd_entry
238            };
239
240            // Exclusive insert because we expect `to` to be empty after closing it above
241            fd_map.insert(true, op.fd, new_fd_entry);
242            Ok(())
243        }
244        ProcSpawnFdOpName::Open => {
245            let mut name = unsafe {
246                WasmPtr::<u8, M>::new(op.name)
247                    .read_utf8_string(memory, op.name_len)
248                    .map_err(mem_error_to_wasi)?
249            };
250            name = env.state.fs.relative_path_to_absolute(name.to_owned());
251            match path_open_internal(
252                env,
253                VIRTUAL_ROOT_FD,
254                op.dirflags,
255                &name,
256                op.oflags,
257                op.fs_rights_base,
258                op.fs_rights_inheriting,
259                op.fdflags,
260                op.fdflagsext,
261                Some(op.fd),
262            ) {
263                Err(e) => {
264                    tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
265                    Err(Errno::Io)
266                }
267                Ok(Err(e)) => Err(e),
268                Ok(Ok(_)) => Ok(()),
269            }
270        }
271        ProcSpawnFdOpName::Chdir => {
272            let mut path = unsafe {
273                WasmPtr::<u8, M>::new(op.name)
274                    .read_utf8_string(memory, op.name_len)
275                    .map_err(mem_error_to_wasi)?
276            };
277            path = env.state.fs.relative_path_to_absolute(path.to_owned());
278            chdir_internal(env, &path)
279        }
280        ProcSpawnFdOpName::Fchdir => {
281            let fd = env.state.fs.get_fd(op.fd)?;
282            let inode_kind = fd.inode.read();
283            match inode_kind.deref() {
284                Kind::Dir { path, .. } => {
285                    let path = path.to_str().ok_or(Errno::Notsup)?;
286                    env.state.fs.set_current_dir(path);
287                    Ok(())
288                }
289                _ => Err(Errno::Notdir),
290            }
291        }
292    }
293}