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