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 os::task::{OwnedTaskStatus, TaskStatus},
8 syscalls::*,
9};
10
11#[instrument(
24 level = "trace",
25 skip_all,
26 fields(name = field::Empty, full_path = field::Empty, pid = field::Empty, tid = field::Empty, %args_len),
27 ret)]
28pub fn proc_spawn2<M: MemorySize>(
29 mut ctx: FunctionEnvMut<'_, WasiEnv>,
30 name: WasmPtr<u8, M>,
31 name_len: M::Offset,
32 args: WasmPtr<u8, M>,
33 args_len: M::Offset,
34 envs: WasmPtr<u8, M>,
35 envs_len: M::Offset,
36 fd_ops: WasmPtr<ProcSpawnFdOp<M>, M>,
37 fd_ops_len: M::Offset,
38 signal_actions: WasmPtr<SignalDisposition, M>,
39 signal_actions_len: M::Offset,
40 search_path: Bool,
41 path: WasmPtr<u8, M>,
42 path_len: M::Offset,
43 ret: WasmPtr<Pid, M>,
44) -> Result<Errno, WasiError> {
45 WasiEnv::do_pending_operations(&mut ctx)?;
46
47 let env = ctx.data();
48 let memory = unsafe { ctx.data().memory_view(&ctx) };
49 let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) };
50 Span::current().record("name", name.as_str());
51 let args = unsafe { get_input_str_ok!(&memory, args, args_len) };
52 let args: Vec<_> = args
53 .split(&['\n', '\r'])
54 .map(|a| a.to_string())
55 .filter(|a| !a.is_empty())
56 .collect();
57
58 let envs = if !envs.is_null() {
59 let envs = unsafe { get_input_str_ok!(&memory, envs, envs_len) };
60
61 let envs = envs
62 .split(&['\n', '\r'])
63 .map(|a| a.to_string())
64 .filter(|a| !a.is_empty());
65
66 let mut vec = vec![];
67 for env in envs {
68 let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval));
69 vec.push((key.to_string(), value.to_string()));
70 }
71
72 Some(vec)
73 } else {
74 None
75 };
76
77 let signals = if !signal_actions.is_null() {
78 let signal_actions = wasi_try_mem_ok!(signal_actions.slice(&memory, signal_actions_len));
79 let mut vec = Vec::with_capacity(signal_actions.len() as usize);
80 for s in wasi_try_mem_ok!(signal_actions.access()).iter() {
81 vec.push(*s);
82 }
83 Some(vec)
84 } else {
85 None
86 };
87
88 let fd_ops = if !fd_ops.is_null() {
89 let fd_ops = wasi_try_mem_ok!(fd_ops.slice(&memory, fd_ops_len));
90 let mut vec = Vec::with_capacity(fd_ops.len() as usize);
91 for s in wasi_try_mem_ok!(fd_ops.access()).iter() {
92 vec.push(*s);
93 }
94 vec
95 } else {
96 vec![]
97 };
98
99 if search_path == Bool::True && !name.contains('/') {
101 let path_str;
102
103 let path = if path.is_null() {
104 vec!["/usr/local/bin", "/bin", "/usr/bin"]
105 } else {
106 path_str = unsafe { get_input_str_ok!(&memory, path, path_len) };
107 path_str.split(':').collect()
108 };
109 let (_, state, inodes) =
110 unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
111 match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), &name) {
112 FindExecutableResult::Found(p) => name = p,
113 FindExecutableResult::AccessError => return Ok(Errno::Access),
114 FindExecutableResult::NotFound => return Ok(Errno::Noexec),
115 }
116 } else if name.starts_with("./") {
117 name = ctx.data().state.fs.relative_path_to_absolute(name);
118 }
119
120 Span::current().record("full_path", &name);
121
122 let (mut child_env, mut child_handle) = match ctx.data().fork() {
127 Ok(p) => p,
128 Err(err) => {
129 debug!("could not fork process: {err}");
130 return Ok(Errno::Perm);
132 }
133 };
134
135 {
136 let mut inner = ctx.data().process.lock();
137 inner.children.push(child_env.process.clone());
138 }
139
140 let pid = child_env.pid();
142 let tid = child_env.tid();
143 wasi_try_mem_ok!(ret.write(&memory, pid.raw()));
144 Span::current()
145 .record("pid", pid.raw())
146 .record("tid", tid.raw());
147
148 _prepare_wasi(&mut child_env, Some(args), envs, signals);
149
150 for fd_op in fd_ops {
151 wasi_try_ok!(apply_fd_op(&mut child_env, &memory, &fd_op));
152 }
153
154 let bin_factory = Box::new(child_env.bin_factory.clone());
156
157 let mut builder = Some(child_env);
158
159 let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
160 Ok(a) => Ok(a),
161 Err(err) => {
162 if !err.is_not_found() {
163 error!("builtin failed - {}", err);
164 }
165
166 let env = builder.take().unwrap();
167
168 block_on(bin_factory.spawn(name, env))
170 }
171 };
172
173 match process {
174 Ok(_) => {
175 ctx.data_mut().owned_handles.push(child_handle);
176 trace!(child_pid = %pid, "spawned sub-process");
177 Ok(Errno::Success)
178 }
179 Err(err) => {
180 let err_exit_code = conv_spawn_err_to_exit_code(&err);
181
182 debug!(child_pid = %pid, "process failed with (err={})", err_exit_code);
183
184 Ok(Errno::Noexec)
185 }
186 }
187}
188
189fn apply_fd_op<M: MemorySize>(
190 env: &mut WasiEnv,
191 memory: &MemoryView,
192 op: &ProcSpawnFdOp<M>,
193) -> Result<(), Errno> {
194 match op.cmd {
195 ProcSpawnFdOpName::Close => {
196 if let Ok(fd) = env.state.fs.get_fd(op.fd)
197 && !fd.is_stdio
198 && fd.inode.is_preopened
199 {
200 trace!("Skipping close FD action for pre-opened FD ({})", op.fd);
201 return Ok(());
202 }
203 env.state.fs.close_fd(op.fd)
204 }
205 ProcSpawnFdOpName::Dup2 => {
206 if let Ok(fd) = env.state.fs.get_fd(op.fd)
207 && !fd.is_stdio
208 && fd.inode.is_preopened
209 {
210 warn!(
211 "FD {} is a pre-open and should not be closed, \
212 but will be closed in response to a dup2 FD action. \
213 This will likely break stuff.",
214 op.fd
215 );
216 }
217 if env.state.fs.get_fd(op.fd).is_ok() {
218 env.state.fs.close_fd(op.fd)?;
219 }
220
221 let mut fd_map = env.state.fs.fd_map.write().unwrap();
222 let fd_entry = fd_map.get(op.src_fd).ok_or(Errno::Badf)?;
223
224 let new_fd_entry = Fd {
225 inner: FdInner {
227 offset: fd_entry.inner.offset.clone(),
228 rights: fd_entry.inner.rights_inheriting,
229 fd_flags: {
230 let mut f = fd_entry.inner.fd_flags;
231 f.set(Fdflagsext::CLOEXEC, false);
232 f
233 },
234 ..fd_entry.inner
235 },
236 inode: fd_entry.inode.clone(),
237 ..*fd_entry
238 };
239
240 fd_map.insert(true, op.fd, new_fd_entry);
242 Ok(())
243 }
244 ProcSpawnFdOpName::Open => {
245 let mut name = unsafe {
246 WasmPtr::<u8, M>::new(op.name)
247 .read_utf8_string(memory, op.name_len)
248 .map_err(mem_error_to_wasi)?
249 };
250 name = env.state.fs.relative_path_to_absolute(name.to_owned());
251 match path_open_internal(
252 env,
253 VIRTUAL_ROOT_FD,
254 op.dirflags,
255 &name,
256 op.oflags,
257 op.fs_rights_base,
258 op.fs_rights_inheriting,
259 op.fdflags,
260 op.fdflagsext,
261 Some(op.fd),
262 ) {
263 Err(e) => {
264 tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
265 Err(Errno::Io)
266 }
267 Ok(Err(e)) => Err(e),
268 Ok(Ok(_)) => Ok(()),
269 }
270 }
271 ProcSpawnFdOpName::Chdir => {
272 let mut path = unsafe {
273 WasmPtr::<u8, M>::new(op.name)
274 .read_utf8_string(memory, op.name_len)
275 .map_err(mem_error_to_wasi)?
276 };
277 path = env.state.fs.relative_path_to_absolute(path.to_owned());
278 chdir_internal(env, &path)
279 }
280 ProcSpawnFdOpName::Fchdir => {
281 let fd = env.state.fs.get_fd(op.fd)?;
282 let inode_kind = fd.inode.read();
283 match inode_kind.deref() {
284 Kind::Dir { path, .. } => {
285 let path = path.to_str().ok_or(Errno::Notsup)?;
286 env.state.fs.set_current_dir(path);
287 Ok(())
288 }
289 _ => Err(Errno::Notdir),
290 }
291 }
292 }
293}