wasmer_wasix/syscalls/wasix/
proc_spawn2.rs

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