Skip to main content

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    let child_finished = child_process.finished.clone();
122    let tasks = child_env.tasks().clone();
123    if let Some(args) = args {
124        let mut child_state = env.state.fork();
125        child_state.args = std::sync::Mutex::new(args);
126        child_env.state = Arc::new(child_state);
127    }
128
129    // Take ownership of this child
130    ctx.data_mut().owned_handles.push(handle);
131    let env = ctx.data();
132
133    // Preopen
134    if let Some(preopen) = preopen
135        && !preopen.is_empty()
136    {
137        for preopen in preopen {
138            warn!(
139                "preopens are not yet supported for spawned processes [{}]",
140                preopen
141            );
142        }
143        return Ok(Err(Errno::Notsup));
144    }
145
146    // Change the current directory
147    if let Some(working_dir) = working_dir {
148        child_env.state.fs.set_current_dir(working_dir.as_str());
149    }
150
151    // Replace the STDIO
152    let (stdin, stdout, stderr) = {
153        let (child_state, child_inodes) = child_env.get_wasi_state_and_inodes();
154        let mut conv_stdio_mode = |mode: WasiStdioMode,
155                                   fd: WasiFd,
156                                   pipe_towards_child: bool|
157         -> Result<OptionFd, Errno> {
158            match mode {
159                WasiStdioMode::Piped => {
160                    let (tx, rx) = Pipe::new().split();
161                    let read_inode = child_state.fs.create_inode_with_default_stat(
162                        child_inodes,
163                        Kind::PipeRx { rx },
164                        false,
165                        "pipe".into(),
166                    );
167                    let write_inode = child_state.fs.create_inode_with_default_stat(
168                        child_inodes,
169                        Kind::PipeTx { tx },
170                        false,
171                        "pipe".into(),
172                    );
173
174                    let (parent_end, child_end) = if pipe_towards_child {
175                        (write_inode, read_inode)
176                    } else {
177                        (read_inode, write_inode)
178                    };
179
180                    let rights = crate::net::socket::all_socket_rights();
181                    let pipe = ctx.data().state.fs.create_fd(
182                        rights,
183                        rights,
184                        Fdflags::empty(),
185                        Fdflagsext::empty(),
186                        0,
187                        parent_end,
188                    )?;
189                    child_state.fs.create_fd_ext(
190                        rights,
191                        rights,
192                        Fdflags::empty(),
193                        Fdflagsext::empty(),
194                        0,
195                        child_end,
196                        Some(fd),
197                        false,
198                    )?;
199
200                    trace!("fd_pipe (fd1={}, fd2={})", pipe, fd);
201                    Ok(OptionFd {
202                        tag: OptionTag::Some,
203                        fd: pipe,
204                    })
205                }
206                WasiStdioMode::Inherit => Ok(OptionFd {
207                    tag: OptionTag::None,
208                    fd: u32::MAX,
209                }),
210                _ => {
211                    child_state.fs.close_fd(fd);
212                    Ok(OptionFd {
213                        tag: OptionTag::None,
214                        fd: u32::MAX,
215                    })
216                }
217            }
218        };
219        // TODO: proc_spawn isn't used in WASIX at the time of writing
220        // this code, so the implementation isn't tested at all
221        let stdin = match conv_stdio_mode(stdin, 0, true) {
222            Ok(a) => a,
223            Err(err) => return Ok(Err(err)),
224        };
225        let stdout = match conv_stdio_mode(stdout, 1, false) {
226            Ok(a) => a,
227            Err(err) => return Ok(Err(err)),
228        };
229        let stderr = match conv_stdio_mode(stderr, 2, false) {
230            Ok(a) => a,
231            Err(err) => return Ok(Err(err)),
232        };
233        (stdin, stdout, stderr)
234    };
235
236    // Create the new process
237    let bin_factory = Box::new(ctx.data().bin_factory.clone());
238    let child_pid = child_env.pid();
239
240    let mut builder = Some(child_env);
241
242    // First we try the built in commands
243    match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
244        Ok(task) => {
245            if let Err(err) = propagate_virtual_task_completion(&tasks, task, child_finished) {
246                return Ok(Err(err.into()));
247            }
248        }
249        Err(err) => {
250            if !err.is_not_found() {
251                error!("builtin failed - {}", err);
252            }
253            // Now we actually spawn the process
254            let child_work = bin_factory.spawn(name, builder.take().unwrap());
255
256            match __asyncify(&mut ctx, None, async move { Ok(child_work.await) })?
257                .map_err(|err| Errno::Unknown)
258            {
259                Ok(Ok(_)) => {}
260                Ok(Err(err)) => return Ok(Err(conv_spawn_err_to_errno(&err))),
261                Err(err) => return Ok(Err(err)),
262            }
263        }
264    }
265
266    // Add the process to the environment state
267    {
268        let mut inner = ctx.data().process.lock();
269        inner.children.push(child_process);
270    }
271    let env = ctx.data();
272    let memory = unsafe { env.memory_view(&ctx) };
273
274    let handles = ProcessHandles {
275        pid: child_pid.raw(),
276        stdin,
277        stdout,
278        stderr,
279    };
280    Ok(Ok((handles, ctx)))
281}