wasmer_wasix/syscalls/wasix/
proc_spawn2.rs1use wasmer::FromToNativeWasmType;
2use wasmer_wasix_types::wasi::ProcSpawnFdOpName;
3
4use super::*;
5use crate::{
6 VIRTUAL_ROOT_FD, WasiFs,
7 fs::MAX_FD,
8 os::task::{OwnedTaskStatus, TaskStatus},
9 syscalls::*,
10};
11
12#[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 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 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 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 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 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 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 if op.fd > MAX_FD {
208 return Err(Errno::Badf);
209 }
210
211 let target_fd = env.state.fs.get_fd(op.fd).ok();
212 if let Some(fd) = target_fd.as_ref()
213 && !fd.is_stdio
214 && fd.inode.is_preopened
215 {
216 warn!("Refusing dup2 FD action over pre-opened FD ({})", op.fd);
217 return Err(Errno::Notsup);
218 }
219
220 if op.src_fd != op.fd && target_fd.is_some() {
223 env.state.fs.close_fd(op.fd)?;
224 }
225
226 let mut fd_map = env.state.fs.fd_map.write().unwrap();
227 let fd_entry = fd_map.get(op.src_fd).ok_or(Errno::Badf)?;
228
229 let new_fd_entry = Fd {
230 inner: FdInner {
232 offset: fd_entry.inner.offset.clone(),
233 rights: fd_entry.inner.rights_inheriting,
234 fd_flags: {
235 let mut f = fd_entry.inner.fd_flags;
236 f.set(Fdflagsext::CLOEXEC, false);
237 f
238 },
239 ..fd_entry.inner
240 },
241 inode: fd_entry.inode.clone(),
242 ..*fd_entry
243 };
244
245 fd_map.insert(true, op.fd, new_fd_entry);
247 Ok(())
248 }
249 ProcSpawnFdOpName::Open => {
250 let mut name = unsafe {
251 WasmPtr::<u8, M>::new(op.name)
252 .read_utf8_string(memory, op.name_len)
253 .map_err(mem_error_to_wasi)?
254 };
255 name = env.state.fs.relative_path_to_absolute(name.to_owned());
256 match path_open_internal(
257 env,
258 VIRTUAL_ROOT_FD,
259 op.dirflags,
260 &name,
261 op.oflags,
262 op.fs_rights_base,
263 op.fs_rights_inheriting,
264 op.fdflags,
265 op.fdflagsext,
266 Some(op.fd),
267 ) {
268 Err(e) => {
269 tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
270 Err(Errno::Io)
271 }
272 Ok(Err(e)) => Err(e),
273 Ok(Ok(_)) => Ok(()),
274 }
275 }
276 ProcSpawnFdOpName::Chdir => {
277 let mut path = unsafe {
278 WasmPtr::<u8, M>::new(op.name)
279 .read_utf8_string(memory, op.name_len)
280 .map_err(mem_error_to_wasi)?
281 };
282 path = env.state.fs.relative_path_to_absolute(path.to_owned());
283 chdir_internal(env, &path)
284 }
285 ProcSpawnFdOpName::Fchdir => {
286 let fd = env.state.fs.get_fd(op.fd)?;
287 let inode_kind = fd.inode.read();
288 match inode_kind.deref() {
289 Kind::Dir { path, .. } => {
290 let path = path.to_str().ok_or(Errno::Notsup)?;
291 env.state.fs.set_current_dir(path);
292 Ok(())
293 }
294 _ => Err(Errno::Notdir),
295 }
296 }
297 _ => Err(Errno::Inval),
298 }
299}