wasmer_wasix/syscalls/wasix/
proc_spawn.rs

1use virtual_fs::Pipe;
2use wasmer_wasix_types::wasi::ProcessHandles;
3
4use super::*;
5use crate::syscalls::*;
6
7/// Spawns a new process within the context of this machine.
8///
9/// This syscall was previously used by the Rust stdlib's `Command::spawn` on WASIX.
10/// Rust now uses `posix_spawn` (backed by `proc_spawn3`) instead. This syscall
11/// remains for backwards compatibility but is otherwise unused.
12///
13/// ## Parameters
14///
15/// * `name` - Name of the process to be spawned
16/// * `chroot` - Indicates if the process will chroot or not
17/// * `args` - List of the arguments to pass the process
18///   (entries are separated by line feeds)
19/// * `preopen` - List of the preopens for this process
20///   (entries are separated by line feeds)
21/// * `stdin` - How will stdin be handled
22/// * `stdout` - How will stdout be handled
23/// * `stderr` - How will stderr be handled
24/// * `working_dir` - Working directory where this process should run
25///   (passing '.' will use the current directory)
26///
27/// ## Return
28///
29/// Returns a bus process id that can be used to invoke calls
30#[instrument(level = "trace", skip_all, fields(name = field::Empty, working_dir = field::Empty), ret)]
31pub fn proc_spawn<M: MemorySize>(
32    mut ctx: FunctionEnvMut<'_, WasiEnv>,
33    name: WasmPtr<u8, M>,
34    name_len: M::Offset,
35    chroot: Bool,
36    args: WasmPtr<u8, M>,
37    args_len: M::Offset,
38    preopen: WasmPtr<u8, M>,
39    preopen_len: M::Offset,
40    stdin: WasiStdioMode,
41    stdout: WasiStdioMode,
42    stderr: WasiStdioMode,
43    working_dir: WasmPtr<u8, M>,
44    working_dir_len: M::Offset,
45    ret_handles: WasmPtr<ProcessHandles, M>,
46) -> Result<Errno, WasiError> {
47    WasiEnv::do_pending_operations(&mut ctx)?;
48
49    let env = ctx.data();
50    let control_plane = &env.control_plane;
51    let memory = unsafe { env.memory_view(&ctx) };
52    let name = unsafe { get_input_str_ok!(&memory, name, name_len) };
53    let args = unsafe { get_input_str_ok!(&memory, args, args_len) };
54    let preopen = unsafe { get_input_str_ok!(&memory, preopen, preopen_len) };
55    let working_dir = unsafe { get_input_str_ok!(&memory, working_dir, working_dir_len) };
56
57    Span::current()
58        .record("name", name.as_str())
59        .record("working_dir", working_dir.as_str());
60
61    if chroot == Bool::True {
62        warn!("chroot is not currently supported");
63        return Ok(Errno::Notsup);
64    }
65
66    let args: Vec<_> = args
67        .split(&['\n', '\r'])
68        .map(|a| a.to_string())
69        .filter(|a| !a.is_empty())
70        .collect();
71
72    let preopen: Vec<_> = preopen
73        .split(&['\n', '\r'])
74        .map(|a| a.to_string())
75        .filter(|a| !a.is_empty())
76        .collect();
77
78    let (handles, ctx) = match proc_spawn_internal(
79        ctx,
80        name,
81        Some(args),
82        Some(preopen),
83        Some(working_dir),
84        stdin,
85        stdout,
86        stderr,
87    )? {
88        Ok(a) => a,
89        Err(err) => {
90            return Ok(err);
91        }
92    };
93
94    let env = ctx.data();
95    let memory = unsafe { env.memory_view(&ctx) };
96    wasi_try_mem_ok!(ret_handles.write(&memory, handles));
97    Ok(Errno::Success)
98}
99
100pub fn proc_spawn_internal(
101    mut ctx: FunctionEnvMut<'_, WasiEnv>,
102    name: String,
103    args: Option<Vec<String>>,
104    preopen: Option<Vec<String>>,
105    working_dir: Option<String>,
106    stdin: WasiStdioMode,
107    stdout: WasiStdioMode,
108    stderr: WasiStdioMode,
109) -> WasiResult<(ProcessHandles, FunctionEnvMut<'_, WasiEnv>)> {
110    let env = ctx.data();
111
112    // Fork the current environment and set the new arguments
113    let (mut child_env, handle) = match ctx.data().fork() {
114        Ok(x) => x,
115        Err(err) => {
116            // TODO: evaluate the appropriate error code, document it in the spec.
117            return Ok(Err(Errno::Access));
118        }
119    };
120    let child_process = child_env.process.clone();
121    if let Some(args) = args {
122        let mut child_state = env.state.fork();
123        child_state.args = std::sync::Mutex::new(args);
124        child_env.state = Arc::new(child_state);
125    }
126
127    // Take ownership of this child
128    ctx.data_mut().owned_handles.push(handle);
129    let env = ctx.data();
130
131    // Preopen
132    if let Some(preopen) = preopen
133        && !preopen.is_empty()
134    {
135        for preopen in preopen {
136            warn!(
137                "preopens are not yet supported for spawned processes [{}]",
138                preopen
139            );
140        }
141        return Ok(Err(Errno::Notsup));
142    }
143
144    // Change the current directory
145    if let Some(working_dir) = working_dir {
146        child_env.state.fs.set_current_dir(working_dir.as_str());
147    }
148
149    // Replace the STDIO
150    let (stdin, stdout, stderr) = {
151        let (child_state, child_inodes) = child_env.get_wasi_state_and_inodes();
152        let mut conv_stdio_mode = |mode: WasiStdioMode,
153                                   fd: WasiFd,
154                                   pipe_towards_child: bool|
155         -> Result<OptionFd, Errno> {
156            match mode {
157                WasiStdioMode::Piped => {
158                    let (tx, rx) = Pipe::new().split();
159                    let read_inode = child_state.fs.create_inode_with_default_stat(
160                        child_inodes,
161                        Kind::PipeRx { rx },
162                        false,
163                        "pipe".into(),
164                    );
165                    let write_inode = child_state.fs.create_inode_with_default_stat(
166                        child_inodes,
167                        Kind::PipeTx { tx },
168                        false,
169                        "pipe".into(),
170                    );
171
172                    let (parent_end, child_end) = if pipe_towards_child {
173                        (write_inode, read_inode)
174                    } else {
175                        (read_inode, write_inode)
176                    };
177
178                    let rights = crate::net::socket::all_socket_rights();
179                    let pipe = ctx.data().state.fs.create_fd(
180                        rights,
181                        rights,
182                        Fdflags::empty(),
183                        Fdflagsext::empty(),
184                        0,
185                        parent_end,
186                    )?;
187                    child_state.fs.create_fd_ext(
188                        rights,
189                        rights,
190                        Fdflags::empty(),
191                        Fdflagsext::empty(),
192                        0,
193                        child_end,
194                        Some(fd),
195                        false,
196                    )?;
197
198                    trace!("fd_pipe (fd1={}, fd2={})", pipe, fd);
199                    Ok(OptionFd {
200                        tag: OptionTag::Some,
201                        fd: pipe,
202                    })
203                }
204                WasiStdioMode::Inherit => Ok(OptionFd {
205                    tag: OptionTag::None,
206                    fd: u32::MAX,
207                }),
208                _ => {
209                    child_state.fs.close_fd(fd);
210                    Ok(OptionFd {
211                        tag: OptionTag::None,
212                        fd: u32::MAX,
213                    })
214                }
215            }
216        };
217        // TODO: proc_spawn isn't used in WASIX at the time of writing
218        // this code, so the implementation isn't tested at all
219        let stdin = match conv_stdio_mode(stdin, 0, true) {
220            Ok(a) => a,
221            Err(err) => return Ok(Err(err)),
222        };
223        let stdout = match conv_stdio_mode(stdout, 1, false) {
224            Ok(a) => a,
225            Err(err) => return Ok(Err(err)),
226        };
227        let stderr = match conv_stdio_mode(stderr, 2, false) {
228            Ok(a) => a,
229            Err(err) => return Ok(Err(err)),
230        };
231        (stdin, stdout, stderr)
232    };
233
234    // Create the new process
235    let bin_factory = Box::new(ctx.data().bin_factory.clone());
236    let child_pid = child_env.pid();
237
238    let mut builder = Some(child_env);
239
240    // First we try the built in commands
241    let mut process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
242        Ok(a) => a,
243        Err(err) => {
244            if !err.is_not_found() {
245                error!("builtin failed - {}", err);
246            }
247            // Now we actually spawn the process
248            let child_work = bin_factory.spawn(name, builder.take().unwrap());
249
250            match __asyncify(&mut ctx, None, async move { Ok(child_work.await) })?
251                .map_err(|err| Errno::Unknown)
252            {
253                Ok(Ok(a)) => a,
254                Ok(Err(err)) => return Ok(Err(conv_spawn_err_to_errno(&err))),
255                Err(err) => return Ok(Err(err)),
256            }
257        }
258    };
259
260    // Add the process to the environment state
261    {
262        let mut inner = ctx.data().process.lock();
263        inner.children.push(child_process);
264    }
265    let env = ctx.data();
266    let memory = unsafe { env.memory_view(&ctx) };
267
268    let handles = ProcessHandles {
269        pid: child_pid.raw(),
270        stdin,
271        stdout,
272        stderr,
273    };
274    Ok(Ok((handles, ctx)))
275}