wasmer_wasix/syscalls/wasix/
proc_spawn3.rs1use virtual_mio::block_on;
2use wasmer_wasix_types::wasi::ProcSpawnFdOpName;
3
4use super::*;
5use crate::{VIRTUAL_ROOT_FD, WasiFs, syscalls::*};
6
7#[instrument(
13 level = "trace",
14 skip_all,
15 fields(name = field::Empty, full_path = field::Empty, pid = field::Empty, tid = field::Empty, %args_len),
16 ret)]
17pub fn proc_spawn3<M: MemorySize>(
18 mut ctx: FunctionEnvMut<'_, WasiEnv>,
19 name: WasmPtr<u8, M>,
20 name_len: M::Offset,
21 args: WasmPtr<WasmPtr<u8, M>, M>,
22 args_len: M::Offset,
23 envs: WasmPtr<WasmPtr<u8, M>, M>,
24 envs_len: M::Offset,
25 fd_ops: WasmPtr<ProcSpawnFdOp<M>, M>,
26 fd_ops_len: M::Offset,
27 signal_actions: WasmPtr<SignalDisposition, M>,
28 signal_actions_len: M::Offset,
29 search_path: Bool,
30 path: WasmPtr<u8, M>,
31 path_len: M::Offset,
32 ret: WasmPtr<Pid, M>,
33) -> Result<Errno, WasiError> {
34 WasiEnv::do_pending_operations(&mut ctx)?;
35
36 let memory = unsafe { ctx.data().memory_view(&ctx) };
37 let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) };
38 Span::current().record("name", name.as_str());
39 let args = wasi_try_ok!(read_string_array(&memory, args, args_len));
40
41 let envs = if !envs.is_null() {
42 let envs = wasi_try_ok!(read_string_array(&memory, envs, envs_len));
43 Some(wasi_try_ok!(parse_env_entries(envs)))
44 } else {
45 None
46 };
47
48 let signals = if !signal_actions.is_null() {
49 let signal_actions = wasi_try_mem_ok!(signal_actions.slice(&memory, signal_actions_len));
50 let mut vec = Vec::with_capacity(signal_actions.len() as usize);
51 for s in wasi_try_mem_ok!(signal_actions.access()).iter() {
52 vec.push(*s);
53 }
54 Some(vec)
55 } else {
56 None
57 };
58
59 let fd_ops = if !fd_ops.is_null() {
60 let fd_ops = wasi_try_mem_ok!(fd_ops.slice(&memory, fd_ops_len));
61 let mut vec = Vec::with_capacity(fd_ops.len() as usize);
62 for s in wasi_try_mem_ok!(fd_ops.access()).iter() {
63 vec.push(*s);
64 }
65 vec
66 } else {
67 vec![]
68 };
69
70 let path = if path.is_null() {
71 None
72 } else {
73 Some(unsafe { get_input_str_ok!(&memory, path, path_len) })
74 };
75
76 proc_spawn3_impl(
77 ctx,
78 &mut name,
79 args,
80 envs,
81 fd_ops,
82 signals,
83 search_path,
84 path.as_deref(),
85 ret,
86 )
87}
88
89pub(crate) fn proc_spawn3_impl<M: MemorySize>(
90 mut ctx: FunctionEnvMut<'_, WasiEnv>,
91 name: &mut String,
92 args: Vec<String>,
93 envs: Option<Vec<(String, String)>>,
94 fd_ops: Vec<ProcSpawnFdOp<M>>,
95 signals: Option<Vec<SignalDisposition>>,
96 search_path: Bool,
97 path: Option<&str>,
98 ret: WasmPtr<Pid, M>,
99) -> Result<Errno, WasiError> {
100 let memory = unsafe { ctx.data().memory_view(&ctx) };
101
102 let searches_path = search_path == Bool::True && !name.contains('/');
106 let invoked_as = (!searches_path).then(|| name.clone());
107
108 if search_path == Bool::True && !name.contains('/') {
110 let path = if let Some(path) = path {
111 path.split(':').collect::<Vec<_>>()
112 } else {
113 vec!["/usr/local/bin", "/bin", "/usr/bin"]
114 };
115 let (_, state, inodes) =
116 unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) };
117 match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), name) {
118 FindExecutableResult::Found(p) => *name = p,
119 FindExecutableResult::AccessError => return Ok(Errno::Access),
120 FindExecutableResult::NotFound => return Ok(Errno::Noent),
124 }
125 } else if name.starts_with("./") {
126 *name = ctx.data().state.fs.relative_path_to_absolute(name.clone());
127 }
128
129 if search_path == Bool::False && !name.starts_with('/') {
134 *name = ctx.data().state.fs.relative_path_to_absolute(name.clone());
135 }
136
137 Span::current().record("full_path", name.as_str());
138
139 let (mut child_env, mut child_handle) = match ctx.data().fork() {
144 Ok(p) => p,
145 Err(err) => {
146 debug!("could not fork process: {err}");
147 return Ok(Errno::Perm);
149 }
150 };
151
152 {
153 let mut inner = ctx.data().process.lock();
154 inner.children.push(child_env.process.clone());
155 }
156
157 let pid = child_env.pid();
159 let tid = child_env.tid();
160 let child_finished = child_env.process.finished.clone();
161 let tasks = child_env.tasks().clone();
162 wasi_try_mem_ok!(ret.write(&memory, pid.raw()));
163 Span::current()
164 .record("pid", pid.raw())
165 .record("tid", tid.raw());
166
167 _prepare_wasi(&mut child_env, Some(args), envs, signals);
168
169 for fd_op in fd_ops {
170 wasi_try_ok!(apply_fd_op(&mut child_env, &memory, &fd_op));
171 }
172
173 let bin_factory = Box::new(child_env.bin_factory.clone());
175
176 let mut builder = Some(child_env);
177
178 let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) {
179 Ok(task) => {
180 if let Err(err) = propagate_virtual_task_completion(&tasks, task, child_finished) {
181 return Ok(err.into());
182 }
183 Ok(())
184 }
185 Err(err) => {
186 if !err.is_not_found() {
187 error!("builtin failed - {}", err);
188 }
189
190 let env = builder.take().unwrap();
191
192 block_on(bin_factory.spawn(name.clone(), invoked_as.clone(), env)).map(|_| ())
194 }
195 };
196
197 match process {
198 Ok(_) => {
199 ctx.data_mut().owned_handles.push(child_handle);
200 trace!(child_pid = %pid, "spawned sub-process");
201 Ok(Errno::Success)
202 }
203 Err(err) => {
204 let err_exit_code = conv_spawn_err_to_exit_code(&err);
205
206 debug!(child_pid = %pid, "process failed with (err={})", err_exit_code);
207
208 Ok(Errno::Noexec)
209 }
210 }
211}
212
213pub(crate) fn apply_fd_op<M: MemorySize>(
214 env: &mut WasiEnv,
215 memory: &MemoryView,
216 op: &ProcSpawnFdOp<M>,
217) -> Result<(), Errno> {
218 match op.cmd {
219 ProcSpawnFdOpName::Close => {
220 if let Ok(fd) = env.state.fs.get_fd(op.fd)
221 && !fd.is_stdio
222 && fd.inode.is_preopened
223 {
224 trace!("Skipping close FD action for pre-opened FD ({})", op.fd);
225 return Ok(());
226 }
227 env.state.fs.close_fd(op.fd)
228 }
229 ProcSpawnFdOpName::Dup2 => {
230 let flush_target = env.state.fs.dup2_at(op.src_fd, op.fd)?;
231 if let Some(file) = flush_target {
232 block_on(WasiFs::flush_file_best_effort(file));
233 }
234 Ok(())
235 }
236 ProcSpawnFdOpName::Open => {
237 let mut name = unsafe {
238 WasmPtr::<u8, M>::new(op.name)
239 .read_utf8_string(memory, op.name_len)
240 .map_err(mem_error_to_wasi)?
241 };
242 name = env.state.fs.relative_path_to_absolute(name.to_owned());
243 match path_open_internal(
244 env,
245 VIRTUAL_ROOT_FD,
246 op.dirflags,
247 &name,
248 op.oflags,
249 op.fs_rights_base,
250 op.fs_rights_inheriting,
251 op.fdflags,
252 op.fdflagsext,
253 Some(op.fd),
254 ) {
255 Err(e) => {
256 tracing::warn!("Failed to open file for posix_spawn: {:?}", e);
257 Err(Errno::Io)
258 }
259 Ok(Err(e)) => Err(e),
260 Ok(Ok(_)) => Ok(()),
261 }
262 }
263 ProcSpawnFdOpName::Chdir => {
264 let mut path = unsafe {
265 WasmPtr::<u8, M>::new(op.name)
266 .read_utf8_string(memory, op.name_len)
267 .map_err(mem_error_to_wasi)?
268 };
269 path = env.state.fs.relative_path_to_absolute(path.to_owned());
270 chdir_internal(env, &path)
271 }
272 ProcSpawnFdOpName::Fchdir => {
273 let fd = env.state.fs.get_fd(op.fd)?;
274 let inode_kind = fd.inode.read();
275 match inode_kind.deref() {
276 Kind::Dir { path, .. } => {
277 let path = path.to_str().ok_or(Errno::Notsup)?;
278 env.state.fs.set_current_dir(path);
279 Ok(())
280 }
281 _ => Err(Errno::Notdir),
282 }
283 }
284 _ => Err(Errno::Inval),
285 }
286}