1#![allow(clippy::result_large_err)]
2use super::{BinaryPackage, BinaryPackageCommand};
3use crate::{
4 RewindState, SpawnError, WasiError, WasiRuntimeError,
5 os::task::{
6 TaskJoinHandle,
7 thread::{RewindResultType, WasiThreadRunGuard},
8 },
9 runtime::{
10 TaintReason,
11 module_cache::HashedModuleData,
12 task_manager::{
13 TaskWasm, TaskWasmRecycle, TaskWasmRecycleProperties, TaskWasmRunProperties,
14 },
15 },
16 state::context_switching::ContextSwitchingEnvironment,
17 syscalls::rewind_ext,
18};
19use crate::{Runtime, WasiEnv, WasiFunctionEnv};
20use std::sync::Arc;
21use tracing::*;
22use virtual_mio::block_on;
23use wasmer::{Function, Memory32, Memory64, Module, RuntimeError, Store, Value};
24use wasmer_wasix_types::wasi::Errno;
25
26#[tracing::instrument(level = "trace", skip_all, fields(%name, package_id=%binary.id))]
27pub async fn spawn_exec(
28 binary: BinaryPackage,
29 name: &str,
30 env: WasiEnv,
31 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
32) -> Result<TaskJoinHandle, SpawnError> {
33 spawn_union_fs(&env, &binary).await?;
34
35 let cmd = package_command_by_name(&binary, name)?;
36 let module = runtime.load_command_module(cmd).await?;
37
38 drop(binary);
41
42 spawn_exec_module(module, env, runtime)
43}
44
45#[tracing::instrument(level = "trace", skip_all, fields(%name))]
46pub async fn spawn_exec_wasm(
47 wasm: HashedModuleData,
48 name: &str,
49 env: WasiEnv,
50 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
51) -> Result<TaskJoinHandle, SpawnError> {
52 let module = spawn_load_module(name, wasm, runtime).await?;
53
54 spawn_exec_module(module, env, runtime)
55}
56
57pub fn package_command_by_name<'a>(
58 pkg: &'a BinaryPackage,
59 name: &str,
60) -> Result<&'a BinaryPackageCommand, SpawnError> {
61 let cmd = if let Some(cmd) = pkg.get_command(name) {
67 cmd
68 } else if let Some(cmd) = pkg.get_entrypoint_command() {
69 cmd
70 } else {
71 match pkg.commands.as_slice() {
72 [first] => first,
74 _ => {
77 return Err(SpawnError::MissingEntrypoint {
78 package_id: pkg.id.clone(),
79 });
80 }
81 }
82 };
83
84 Ok(cmd)
85}
86
87pub async fn spawn_load_module(
88 name: &str,
89 wasm: HashedModuleData,
90 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
91) -> Result<Module, SpawnError> {
92 match runtime.load_hashed_module(wasm, None).await {
93 Ok(module) => Ok(module),
94 Err(err) => {
95 tracing::error!(
96 command = name,
97 error = &err as &dyn std::error::Error,
98 "Failed to compile the module",
99 );
100 Err(err)
101 }
102 }
103}
104
105pub async fn spawn_union_fs(env: &WasiEnv, binary: &BinaryPackage) -> Result<(), SpawnError> {
106 env.state
108 .fs
109 .conditional_union(binary)
110 .await
111 .map_err(|err| {
112 tracing::warn!("failed to union file system - {err}");
113 SpawnError::FileSystemError(crate::ExtendedFsError::with_msg(
114 err,
115 "could not union filesystems",
116 ))
117 })?;
118 tracing::debug!("{:?}", env.state.fs);
119 Ok(())
120}
121
122pub fn spawn_exec_module(
123 module: Module,
124 env: WasiEnv,
125 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
126) -> Result<TaskJoinHandle, SpawnError> {
127 let tasks = runtime.task_manager();
129
130 let pid = env.pid();
132
133 let join_handle = env.thread.join_handle();
134 {
135 let tasks_outer = tasks.clone();
137
138 tasks_outer
139 .task_wasm(
140 TaskWasm::new(Box::new(run_exec), env, module, true, true).with_pre_run(Box::new(
141 |ctx, store| {
142 Box::pin(async move {
143 ctx.data(store).state.fs.close_cloexec_fds().await;
144 })
145 },
146 )),
147 )
148 .map_err(|err| {
149 error!("wasi[{}]::failed to launch module - {}", pid, err);
150 SpawnError::Other(Box::new(err))
151 })?
152 };
153
154 Ok(join_handle)
155}
156
157unsafe fn run_recycle(
161 callback: Option<Box<TaskWasmRecycle>>,
162 ctx: WasiFunctionEnv,
163 mut store: Store,
164) {
165 if let Some(callback) = callback {
166 let env = ctx.data_mut(&mut store);
167 let memory = unsafe { env.memory() }.clone();
168
169 let props = TaskWasmRecycleProperties {
170 env: env.clone(),
171 memory,
172 store,
173 };
174 callback(props);
175 }
176}
177
178pub fn run_exec(props: TaskWasmRunProperties) {
179 let ctx = props.ctx;
180 let mut store = props.store;
181
182 let thread = WasiThreadRunGuard::new(ctx.data(&store).thread.clone());
184 let recycle = props.recycle;
185
186 if let Ok(initialize) = ctx
189 .data(&store)
190 .inner()
191 .main_module_instance_handles()
192 .instance
193 .exports
194 .get_function("_initialize")
195 .cloned()
196 {
197 let result = initialize.call(&mut store, &[]);
200
201 if let Err(err) = result {
202 thread.thread.set_status_finished(Err(err.into()));
203 ctx.data(&store)
204 .blocking_on_exit(Some(Errno::Noexec.into()));
205 unsafe { run_recycle(recycle, ctx, store) };
206 return;
207 }
208 }
209
210 let rewind_state = match unsafe { ctx.bootstrap(&mut store) } {
214 Ok(r) => r,
215 Err(err) => {
216 tracing::warn!("failed to bootstrap - {}", err);
217 thread.thread.set_status_finished(Err(err));
218 ctx.data(&store)
219 .blocking_on_exit(Some(Errno::Noexec.into()));
220 unsafe { run_recycle(recycle, ctx, store) };
221 return;
222 }
223 };
224
225 debug!("wasi[{}]::called main()", ctx.data(&store).pid());
227 call_module(ctx, store, thread, rewind_state, recycle);
231}
232
233fn get_start(ctx: &WasiFunctionEnv, store: &Store) -> Option<Function> {
234 ctx.data(store)
235 .inner()
236 .main_module_instance_handles()
237 .instance
238 .exports
239 .get_function("_start")
240 .cloned()
241 .ok()
242}
243
244fn call_module(
246 ctx: WasiFunctionEnv,
247 mut store: Store,
248 handle: WasiThreadRunGuard,
249 rewind_state: Option<(RewindState, RewindResultType)>,
250 recycle: Option<Box<TaskWasmRecycle>>,
251) {
252 let env = ctx.data(&store);
253 let pid = env.pid();
254 let tasks = env.tasks().clone();
255 handle.thread.set_status_running();
256 let runtime = env.runtime.clone();
257
258 if let Some((rewind_state, rewind_result)) = rewind_state {
260 let mut ctx = ctx.env.clone().into_mut(&mut store);
261 if rewind_state.is_64bit {
262 let res = rewind_ext::<Memory64>(
263 &mut ctx,
264 Some(rewind_state.memory_stack),
265 rewind_state.rewind_stack,
266 rewind_state.store_data,
267 rewind_result,
268 );
269 if res != Errno::Success {
270 ctx.data().blocking_on_exit(Some(res.into()));
271 unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) };
272 return;
273 }
274 } else {
275 let res = rewind_ext::<Memory32>(
276 &mut ctx,
277 Some(rewind_state.memory_stack),
278 rewind_state.rewind_stack,
279 rewind_state.store_data,
280 rewind_result,
281 );
282 if res != Errno::Success {
283 ctx.data().blocking_on_exit(Some(res.into()));
284 unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) };
285 return;
286 }
287 };
288 }
289
290 let Some(start) = get_start(&ctx, &store) else {
293 debug!("wasi[{}]::exec-failed: missing _start function", pid);
294 ctx.data(&store)
295 .blocking_on_exit(Some(Errno::Noexec.into()));
296 unsafe { run_recycle(recycle, ctx, store) };
297 return;
298 };
299
300 let (mut store, mut call_ret) =
301 ContextSwitchingEnvironment::run_main_context(&ctx, store, start.clone(), vec![]);
302
303 let mut store = loop {
304 store = match resume_vfork(&ctx, store, &start, &call_ret) {
306 (store, Ok(Some(ret))) => {
308 call_ret = ret;
309 store
310 }
311
312 (store, Err(e)) => {
314 call_ret = Err(RuntimeError::user(Box::new(WasiError::Exit(e.into()))));
315 break store;
316 }
317
318 (store, Ok(None)) => break store,
320 };
321 };
322
323 let ret = if let Err(err) = call_ret {
324 match err.downcast::<WasiError>() {
325 Ok(WasiError::Exit(code)) if code.is_success() => Ok(Errno::Success),
326 Ok(WasiError::ThreadExit) => Ok(Errno::Success),
327 Ok(WasiError::Exit(code)) => {
328 runtime.on_taint(TaintReason::NonZeroExitCode(code));
329 Err(WasiError::Exit(code).into())
330 }
331 Ok(WasiError::DeepSleep(deep)) => {
332 let rewind = deep.rewind;
334 let respawn = {
335 move |ctx, store, rewind_result| {
336 call_module(
338 ctx,
339 store,
340 handle,
341 Some((rewind, RewindResultType::RewindWithResult(rewind_result))),
342 recycle,
343 );
344 }
345 };
346
347 if let Err(err) = unsafe {
349 tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger)
350 } {
351 debug!("failed to go into deep sleep - {}", err);
352 }
353 return;
354 }
355 Ok(WasiError::UnknownWasiVersion) => {
356 debug!("failed as wasi version is unknown");
357 runtime.on_taint(TaintReason::UnknownWasiVersion);
358 Ok(Errno::Noexec)
359 }
360 Ok(WasiError::DlSymbolResolutionFailed(symbol)) => {
361 debug!("failed as a needed DL symbol could not be resolved");
362 runtime.on_taint(TaintReason::DlSymbolResolutionFailed(symbol.clone()));
363 Err(WasiError::DlSymbolResolutionFailed(symbol).into())
364 }
365 Err(err) => {
366 runtime.on_taint(TaintReason::RuntimeError(err.clone()));
367 Err(WasiRuntimeError::from(err))
368 }
369 }
370 } else {
371 Ok(Errno::Success)
372 };
373
374 let code = if let Err(err) = &ret {
375 match err.as_exit_code() {
376 Some(s) => s,
377 None => {
378 let err_display = err.display(&mut store);
379 error!("{err_display}");
380 eprintln!("{err_display}");
381 Errno::Noexec.into()
382 }
383 }
384 } else {
385 Errno::Success.into()
386 };
387
388 ctx.data(&store).blocking_on_exit(Some(code));
390 unsafe { run_recycle(recycle, ctx, store) };
391
392 debug!("wasi[{pid}]::main() has exited with {code}");
393 handle.thread.set_status_finished(ret.map(|a| a.into()));
394}
395
396#[allow(clippy::type_complexity)]
397fn resume_vfork(
398 ctx: &WasiFunctionEnv,
399 mut store: Store,
400 start: &Function,
401 call_ret: &Result<Box<[Value]>, RuntimeError>,
402) -> (
403 Store,
404 Result<Option<Result<Box<[Value]>, RuntimeError>>, Errno>,
405) {
406 let (err, code) = match call_ret {
407 Ok(_) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)),
408 Err(err) => match err.downcast_ref::<WasiError>() {
409 Some(WasiError::DeepSleep(..)) => return (store, Ok(None)),
411
412 Some(WasiError::Exit(code)) => (None, *code),
413 Some(WasiError::ThreadExit) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)),
414 Some(WasiError::UnknownWasiVersion) => (None, Errno::Noexec.into()),
415 Some(WasiError::DlSymbolResolutionFailed(_)) => (None, Errno::Nolink.into()),
416 None => (
417 Some(WasiRuntimeError::from(err.clone())),
418 Errno::Unknown.into(),
419 ),
420 },
421 };
422
423 if let Some(mut vfork) = ctx.data_mut(&mut store).vfork.take() {
424 if let Some(err) = err {
425 error!(%err, "Error from child process");
426 eprintln!("{err}");
427 }
428
429 block_on(
430 unsafe { ctx.data(&store).get_memory_and_wasi_state(&store, 0) }
431 .1
432 .fs
433 .close_all(),
434 );
435
436 tracing::debug!(
437 pid = %ctx.data_mut(&mut store).process.pid(),
438 vfork_pid = %vfork.env.process.pid(),
439 "Resuming from vfork after child process was terminated"
440 );
441
442 vfork.env.swap_inner(ctx.data_mut(&mut store));
444 std::mem::swap(vfork.env.as_mut(), ctx.data_mut(&mut store));
445 let mut child_env = *vfork.env;
446 child_env.owned_handles.push(vfork.handle);
447
448 child_env.process.terminate(code);
450
451 if ctx.data(&store).context_switching_environment.is_some() {
453 tracing::error!(
454 "Terminated a vfork in another way than exit or exec which is undefined behaviour. In this case the parent parent process will be terminated."
455 );
456 return (store, Err(code.into()));
457 }
458
459 let child_pid = child_env.process.pid();
461 let rewind_stack = vfork.rewind_stack.freeze();
462 let store_data = vfork.store_data;
463
464 let ctx_cloned = ctx.env.clone().into_mut(&mut store);
465 let rewind_result = if vfork.is_64bit {
467 crate::syscalls::rewind::<Memory64, _>(
468 ctx_cloned,
469 None,
470 rewind_stack,
471 store_data,
472 crate::syscalls::ForkResult {
473 pid: child_pid.raw() as wasmer_wasix_types::wasi::Pid,
474 ret: Errno::Success,
475 },
476 )
477 } else {
478 crate::syscalls::rewind::<Memory32, _>(
479 ctx_cloned,
480 None,
481 rewind_stack,
482 store_data,
483 crate::syscalls::ForkResult {
484 pid: child_pid.raw() as wasmer_wasix_types::wasi::Pid,
485 ret: Errno::Success,
486 },
487 )
488 };
489
490 match rewind_result {
491 Errno::Success => {
492 let (store, result) = ContextSwitchingEnvironment::run_main_context(
495 ctx,
496 store,
497 start.clone(),
498 vec![],
499 );
500 (store, Ok(Some(result)))
501 }
502 err => {
503 warn!("fork failed - could not rewind the stack - errno={}", err);
504 (store, Err(err))
505 }
506 }
507 } else {
508 (store, Ok(None))
509 }
510}