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 ModuleInput, 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::{borrow::Cow, 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 import_package_mounts(&env, &binary).await?;
34
35 let cmd = package_command_by_name(&binary, name)?;
36 let input = ModuleInput::Command(Cow::Borrowed(cmd));
37 let module = runtime.resolve_module(input, None, None).await?;
38
39 drop(binary);
42
43 spawn_exec_module(module, env, runtime)
44}
45
46#[tracing::instrument(level = "trace", skip_all, fields(%name))]
47pub async fn spawn_exec_wasm(
48 wasm: HashedModuleData,
49 name: &str,
50 env: WasiEnv,
51 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
52) -> Result<TaskJoinHandle, SpawnError> {
53 let module = spawn_load_module(name, wasm, runtime).await?;
54
55 spawn_exec_module(module, env, runtime)
56}
57
58pub fn package_command_by_name<'a>(
59 pkg: &'a BinaryPackage,
60 name: &str,
61) -> Result<&'a BinaryPackageCommand, SpawnError> {
62 let cmd = if let Some(cmd) = pkg.get_command(name) {
68 cmd
69 } else if let Some(cmd) = pkg.get_entrypoint_command() {
70 cmd
71 } else {
72 match pkg.commands.as_slice() {
73 [first] => first,
75 _ => {
78 return Err(SpawnError::MissingEntrypoint {
79 package_id: pkg.id.clone(),
80 });
81 }
82 }
83 };
84
85 Ok(cmd)
86}
87
88pub async fn spawn_load_module(
89 name: &str,
90 wasm: HashedModuleData,
91 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
92) -> Result<Module, SpawnError> {
93 match runtime.load_hashed_module(wasm, None).await {
94 Ok(module) => Ok(module),
95 Err(err) => {
96 tracing::error!(
97 command = name,
98 error = &err as &dyn std::error::Error,
99 "Failed to compile the module",
100 );
101 Err(err)
102 }
103 }
104}
105
106pub async fn import_package_mounts(
107 env: &WasiEnv,
108 binary: &BinaryPackage,
109) -> Result<(), SpawnError> {
110 env.state
112 .fs
113 .conditional_union(binary)
114 .await
115 .map_err(|err| {
116 tracing::warn!("failed to import package mounts - {err}");
117 SpawnError::FileSystemError(crate::ExtendedFsError::with_msg(
118 err,
119 "could not import package mounts",
120 ))
121 })?;
122 tracing::debug!("{:?}", env.state.fs);
123 Ok(())
124}
125
126pub fn spawn_exec_module(
127 module: Module,
128 env: WasiEnv,
129 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
130) -> Result<TaskJoinHandle, SpawnError> {
131 let tasks = runtime.task_manager();
133
134 let pid = env.pid();
136
137 let join_handle = env.thread.join_handle();
138 {
139 let tasks_outer = tasks.clone();
141
142 tasks_outer
143 .task_wasm(
144 TaskWasm::new(Box::new(run_exec), env, module, true, true).with_pre_run(Box::new(
145 |ctx, store| {
146 let wasi_state = ctx.data(store).state.clone();
147 Box::pin(async move {
148 wasi_state.fs.close_cloexec_fds().await;
149 })
150 },
151 )),
152 )
153 .map_err(|err| {
154 error!("wasi[{}]::failed to launch module - {}", pid, err);
155 SpawnError::Other(Box::new(err))
156 })?
157 };
158
159 Ok(join_handle)
160}
161
162unsafe fn run_recycle(
166 callback: Option<Box<TaskWasmRecycle>>,
167 ctx: WasiFunctionEnv,
168 mut store: Store,
169) {
170 if let Some(callback) = callback {
171 let env = ctx.data_mut(&mut store);
172 let memory = unsafe { env.memory() }.clone();
173
174 let props = TaskWasmRecycleProperties {
175 env: env.clone(),
176 memory,
177 store,
178 };
179 callback(props);
180 }
181}
182
183pub fn run_exec(props: TaskWasmRunProperties) {
184 let ctx = props.ctx;
185 let mut store = props.store;
186
187 let thread = WasiThreadRunGuard::new(ctx.data(&store).thread.clone());
189 let recycle = props.recycle;
190
191 if let Ok(initialize) = ctx
194 .data(&store)
195 .inner()
196 .main_module_instance_handles()
197 .instance
198 .exports
199 .get_function("_initialize")
200 .cloned()
201 {
202 let result = initialize.call(&mut store, &[]);
205
206 if let Err(err) = result {
207 thread.thread.set_status_finished(Err(err.into()));
208 ctx.data(&store)
209 .blocking_on_exit(Some(Errno::Noexec.into()));
210 unsafe { run_recycle(recycle, ctx, store) };
211 return;
212 }
213 }
214
215 let rewind_state = match unsafe { ctx.bootstrap(&mut store) } {
219 Ok(r) => r,
220 Err(err) => {
221 tracing::warn!("failed to bootstrap - {}", err);
222 thread.thread.set_status_finished(Err(err));
223 ctx.data(&store)
224 .blocking_on_exit(Some(Errno::Noexec.into()));
225 unsafe { run_recycle(recycle, ctx, store) };
226 return;
227 }
228 };
229
230 debug!("wasi[{}]::called main()", ctx.data(&store).pid());
232 call_module(ctx, store, thread, rewind_state, recycle);
236}
237
238fn get_start(ctx: &WasiFunctionEnv, store: &Store) -> Option<Function> {
239 ctx.data(store)
240 .inner()
241 .main_module_instance_handles()
242 .instance
243 .exports
244 .get_function("_start")
245 .cloned()
246 .ok()
247}
248
249fn call_module(
251 ctx: WasiFunctionEnv,
252 mut store: Store,
253 handle: WasiThreadRunGuard,
254 rewind_state: Option<(RewindState, RewindResultType)>,
255 recycle: Option<Box<TaskWasmRecycle>>,
256) {
257 let env = ctx.data(&store);
258 let pid = env.pid();
259 let tasks = env.tasks().clone();
260 handle.thread.set_status_running();
261 let runtime = env.runtime.clone();
262
263 if let Some((rewind_state, rewind_result)) = rewind_state {
265 let mut ctx = ctx.env.clone().into_mut(&mut store);
266 if rewind_state.is_64bit {
267 let res = rewind_ext::<Memory64>(
268 &mut ctx,
269 Some(rewind_state.memory_stack),
270 rewind_state.rewind_stack,
271 rewind_state.store_data,
272 rewind_result,
273 );
274 if res != Errno::Success {
275 ctx.data().blocking_on_exit(Some(res.into()));
276 unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) };
277 return;
278 }
279 } else {
280 let res = rewind_ext::<Memory32>(
281 &mut ctx,
282 Some(rewind_state.memory_stack),
283 rewind_state.rewind_stack,
284 rewind_state.store_data,
285 rewind_result,
286 );
287 if res != Errno::Success {
288 ctx.data().blocking_on_exit(Some(res.into()));
289 unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) };
290 return;
291 }
292 };
293 }
294
295 let Some(start) = get_start(&ctx, &store) else {
298 debug!("wasi[{}]::exec-failed: missing _start function", pid);
299 ctx.data(&store)
300 .blocking_on_exit(Some(Errno::Noexec.into()));
301 unsafe { run_recycle(recycle, ctx, store) };
302 return;
303 };
304
305 let (mut store, mut call_ret) =
306 ContextSwitchingEnvironment::run_main_context(&ctx, store, start.clone(), vec![]);
307
308 let mut store = loop {
309 store = match resume_vfork(&ctx, store, &start, &call_ret) {
311 (store, Ok(Some(ret))) => {
313 call_ret = ret;
314 store
315 }
316
317 (store, Err(e)) => {
319 call_ret = Err(RuntimeError::user(Box::new(WasiError::Exit(e.into()))));
320 break store;
321 }
322
323 (store, Ok(None)) => break store,
325 };
326 };
327
328 let ret = if let Err(err) = call_ret {
329 match err.downcast::<WasiError>() {
330 Ok(WasiError::Exit(code)) if code.is_success() => Ok(Errno::Success),
331 Ok(WasiError::ThreadExit) => Ok(Errno::Success),
332 Ok(WasiError::Exit(code)) => {
333 runtime.on_taint(TaintReason::NonZeroExitCode(code));
334 Err(WasiError::Exit(code).into())
335 }
336 Ok(WasiError::DeepSleep(deep)) => {
337 let rewind = deep.rewind;
339 let respawn = {
340 move |ctx, store, rewind_result| {
341 call_module(
343 ctx,
344 store,
345 handle,
346 Some((rewind, RewindResultType::RewindWithResult(rewind_result))),
347 recycle,
348 );
349 }
350 };
351
352 if let Err(err) = unsafe {
354 tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger)
355 } {
356 debug!("failed to go into deep sleep - {}", err);
357 }
358 return;
359 }
360 Ok(WasiError::UnknownWasiVersion) => {
361 debug!("failed as wasi version is unknown");
362 runtime.on_taint(TaintReason::UnknownWasiVersion);
363 Ok(Errno::Noexec)
364 }
365 Ok(WasiError::DlSymbolResolutionFailed(symbol)) => {
366 debug!("failed as a needed DL symbol could not be resolved");
367 runtime.on_taint(TaintReason::DlSymbolResolutionFailed(symbol.clone()));
368 Err(WasiError::DlSymbolResolutionFailed(symbol).into())
369 }
370 Err(err) => {
371 runtime.on_taint(TaintReason::RuntimeError(err.clone()));
372 Err(WasiRuntimeError::from(err))
373 }
374 }
375 } else {
376 Ok(Errno::Success)
377 };
378
379 let code = if let Err(err) = &ret {
380 match err.as_exit_code() {
381 Some(s) => s,
382 None => {
383 let err_display = err.display(&mut store);
384 if matches!(
385 err,
386 WasiRuntimeError::Runtime(runtime_err)
387 if runtime_err.clone().to_trap() == Some(wasmer_types::TrapCode::HostInterrupt)
388 ) {
389 debug!("{err_display}");
390 } else {
391 error!("{err_display}");
392 eprintln!("{err_display}");
393 }
394 Errno::Noexec.into()
395 }
396 }
397 } else {
398 Errno::Success.into()
399 };
400
401 ctx.data(&store).blocking_on_exit(Some(code));
403 unsafe { run_recycle(recycle, ctx, store) };
404
405 debug!("wasi[{pid}]::main() has exited with {code}");
406 handle.thread.set_status_finished(ret.map(|a| a.into()));
407}
408
409#[allow(clippy::type_complexity)]
410fn resume_vfork(
411 ctx: &WasiFunctionEnv,
412 mut store: Store,
413 start: &Function,
414 call_ret: &Result<Box<[Value]>, RuntimeError>,
415) -> (
416 Store,
417 Result<Option<Result<Box<[Value]>, RuntimeError>>, Errno>,
418) {
419 let (err, code) = match call_ret {
420 Ok(_) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)),
421 Err(err) => match err.downcast_ref::<WasiError>() {
422 Some(WasiError::DeepSleep(..)) => return (store, Ok(None)),
424
425 Some(WasiError::Exit(code)) => (None, *code),
426 Some(WasiError::ThreadExit) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)),
427 Some(WasiError::UnknownWasiVersion) => (None, Errno::Noexec.into()),
428 Some(WasiError::DlSymbolResolutionFailed(_)) => (None, Errno::Nolink.into()),
429 None => (
430 Some(WasiRuntimeError::from(err.clone())),
431 Errno::Unknown.into(),
432 ),
433 },
434 };
435
436 if let Some(mut vfork) = ctx.data_mut(&mut store).vfork.take() {
437 if let Some(err) = err {
438 error!(%err, "Error from child process");
439 eprintln!("{err}");
440 }
441
442 block_on(
443 unsafe { ctx.data(&store).get_memory_and_wasi_state(&store, 0) }
444 .1
445 .fs
446 .close_all(),
447 );
448
449 tracing::debug!(
450 pid = %ctx.data_mut(&mut store).process.pid(),
451 vfork_pid = %vfork.env.process.pid(),
452 "Resuming from vfork after child process was terminated"
453 );
454
455 vfork.env.swap_inner(ctx.data_mut(&mut store));
457 std::mem::swap(vfork.env.as_mut(), ctx.data_mut(&mut store));
458 let mut child_env = *vfork.env;
459 child_env.owned_handles.push(vfork.handle);
460
461 child_env.process.terminate(code);
463
464 if ctx.data(&store).context_switching_environment.is_some() {
466 tracing::error!(
468 "Terminated a vfork in another way than exit or exec which is undefined behaviour. In this case the parent process will be terminated."
469 );
470 return (store, Err(code.into()));
471 }
472 let Some(asyncify_info) = vfork.asyncify else {
473 tracing::error!(
475 "Terminated a vfork in another way than exit or exec which is undefined behaviour. In this case the parent process will be terminated."
476 );
477 return (store, Err(code.into()));
478 };
479 let child_pid = child_env.process.pid();
484 let rewind_stack = asyncify_info.rewind_stack.freeze();
485 let store_data = asyncify_info.store_data;
486
487 let ctx_cloned = ctx.env.clone().into_mut(&mut store);
488 let rewind_result = if asyncify_info.is_64bit {
490 crate::syscalls::rewind::<Memory64, _>(
491 ctx_cloned,
492 None,
493 rewind_stack,
494 store_data,
495 crate::syscalls::ForkResult {
496 pid: child_pid.raw() as wasmer_wasix_types::wasi::Pid,
497 ret: Errno::Success,
498 },
499 )
500 } else {
501 crate::syscalls::rewind::<Memory32, _>(
502 ctx_cloned,
503 None,
504 rewind_stack,
505 store_data,
506 crate::syscalls::ForkResult {
507 pid: child_pid.raw() as wasmer_wasix_types::wasi::Pid,
508 ret: Errno::Success,
509 },
510 )
511 };
512
513 match rewind_result {
514 Errno::Success => {
515 let (store, result) = ContextSwitchingEnvironment::run_main_context(
518 ctx,
519 store,
520 start.clone(),
521 vec![],
522 );
523 (store, Ok(Some(result)))
524 }
525 err => {
526 warn!("fork failed - could not rewind the stack - errno={}", err);
527 (store, Err(err))
528 }
529 }
530 } else {
531 (store, Ok(None))
532 }
533}