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 spawn_union_fs(&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 spawn_union_fs(env: &WasiEnv, binary: &BinaryPackage) -> Result<(), SpawnError> {
107 env.state
109 .fs
110 .conditional_union(binary)
111 .await
112 .map_err(|err| {
113 tracing::warn!("failed to union file system - {err}");
114 SpawnError::FileSystemError(crate::ExtendedFsError::with_msg(
115 err,
116 "could not union filesystems",
117 ))
118 })?;
119 tracing::debug!("{:?}", env.state.fs);
120 Ok(())
121}
122
123pub fn spawn_exec_module(
124 module: Module,
125 env: WasiEnv,
126 runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
127) -> Result<TaskJoinHandle, SpawnError> {
128 let tasks = runtime.task_manager();
130
131 let pid = env.pid();
133
134 let join_handle = env.thread.join_handle();
135 {
136 let tasks_outer = tasks.clone();
138
139 tasks_outer
140 .task_wasm(
141 TaskWasm::new(Box::new(run_exec), env, module, true, true).with_pre_run(Box::new(
142 |ctx, store| {
143 Box::pin(async move {
144 ctx.data(store).state.fs.close_cloexec_fds().await;
145 })
146 },
147 )),
148 )
149 .map_err(|err| {
150 error!("wasi[{}]::failed to launch module - {}", pid, err);
151 SpawnError::Other(Box::new(err))
152 })?
153 };
154
155 Ok(join_handle)
156}
157
158unsafe fn run_recycle(
162 callback: Option<Box<TaskWasmRecycle>>,
163 ctx: WasiFunctionEnv,
164 mut store: Store,
165) {
166 if let Some(callback) = callback {
167 let env = ctx.data_mut(&mut store);
168 let memory = unsafe { env.memory() }.clone();
169
170 let props = TaskWasmRecycleProperties {
171 env: env.clone(),
172 memory,
173 store,
174 };
175 callback(props);
176 }
177}
178
179pub fn run_exec(props: TaskWasmRunProperties) {
180 let ctx = props.ctx;
181 let mut store = props.store;
182
183 let thread = WasiThreadRunGuard::new(ctx.data(&store).thread.clone());
185 let recycle = props.recycle;
186
187 if let Ok(initialize) = ctx
190 .data(&store)
191 .inner()
192 .main_module_instance_handles()
193 .instance
194 .exports
195 .get_function("_initialize")
196 .cloned()
197 {
198 let result = initialize.call(&mut store, &[]);
201
202 if let Err(err) = result {
203 thread.thread.set_status_finished(Err(err.into()));
204 ctx.data(&store)
205 .blocking_on_exit(Some(Errno::Noexec.into()));
206 unsafe { run_recycle(recycle, ctx, store) };
207 return;
208 }
209 }
210
211 let rewind_state = match unsafe { ctx.bootstrap(&mut store) } {
215 Ok(r) => r,
216 Err(err) => {
217 tracing::warn!("failed to bootstrap - {}", err);
218 thread.thread.set_status_finished(Err(err));
219 ctx.data(&store)
220 .blocking_on_exit(Some(Errno::Noexec.into()));
221 unsafe { run_recycle(recycle, ctx, store) };
222 return;
223 }
224 };
225
226 debug!("wasi[{}]::called main()", ctx.data(&store).pid());
228 call_module(ctx, store, thread, rewind_state, recycle);
232}
233
234fn get_start(ctx: &WasiFunctionEnv, store: &Store) -> Option<Function> {
235 ctx.data(store)
236 .inner()
237 .main_module_instance_handles()
238 .instance
239 .exports
240 .get_function("_start")
241 .cloned()
242 .ok()
243}
244
245fn call_module(
247 ctx: WasiFunctionEnv,
248 mut store: Store,
249 handle: WasiThreadRunGuard,
250 rewind_state: Option<(RewindState, RewindResultType)>,
251 recycle: Option<Box<TaskWasmRecycle>>,
252) {
253 let env = ctx.data(&store);
254 let pid = env.pid();
255 let tasks = env.tasks().clone();
256 handle.thread.set_status_running();
257 let runtime = env.runtime.clone();
258
259 if let Some((rewind_state, rewind_result)) = rewind_state {
261 let mut ctx = ctx.env.clone().into_mut(&mut store);
262 if rewind_state.is_64bit {
263 let res = rewind_ext::<Memory64>(
264 &mut ctx,
265 Some(rewind_state.memory_stack),
266 rewind_state.rewind_stack,
267 rewind_state.store_data,
268 rewind_result,
269 );
270 if res != Errno::Success {
271 ctx.data().blocking_on_exit(Some(res.into()));
272 unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) };
273 return;
274 }
275 } else {
276 let res = rewind_ext::<Memory32>(
277 &mut ctx,
278 Some(rewind_state.memory_stack),
279 rewind_state.rewind_stack,
280 rewind_state.store_data,
281 rewind_result,
282 );
283 if res != Errno::Success {
284 ctx.data().blocking_on_exit(Some(res.into()));
285 unsafe { run_recycle(recycle, WasiFunctionEnv { env: ctx.as_ref() }, store) };
286 return;
287 }
288 };
289 }
290
291 let Some(start) = get_start(&ctx, &store) else {
294 debug!("wasi[{}]::exec-failed: missing _start function", pid);
295 ctx.data(&store)
296 .blocking_on_exit(Some(Errno::Noexec.into()));
297 unsafe { run_recycle(recycle, ctx, store) };
298 return;
299 };
300
301 let (mut store, mut call_ret) =
302 ContextSwitchingEnvironment::run_main_context(&ctx, store, start.clone(), vec![]);
303
304 let mut store = loop {
305 store = match resume_vfork(&ctx, store, &start, &call_ret) {
307 (store, Ok(Some(ret))) => {
309 call_ret = ret;
310 store
311 }
312
313 (store, Err(e)) => {
315 call_ret = Err(RuntimeError::user(Box::new(WasiError::Exit(e.into()))));
316 break store;
317 }
318
319 (store, Ok(None)) => break store,
321 };
322 };
323
324 let ret = if let Err(err) = call_ret {
325 match err.downcast::<WasiError>() {
326 Ok(WasiError::Exit(code)) if code.is_success() => Ok(Errno::Success),
327 Ok(WasiError::ThreadExit) => Ok(Errno::Success),
328 Ok(WasiError::Exit(code)) => {
329 runtime.on_taint(TaintReason::NonZeroExitCode(code));
330 Err(WasiError::Exit(code).into())
331 }
332 Ok(WasiError::DeepSleep(deep)) => {
333 let rewind = deep.rewind;
335 let respawn = {
336 move |ctx, store, rewind_result| {
337 call_module(
339 ctx,
340 store,
341 handle,
342 Some((rewind, RewindResultType::RewindWithResult(rewind_result))),
343 recycle,
344 );
345 }
346 };
347
348 if let Err(err) = unsafe {
350 tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger)
351 } {
352 debug!("failed to go into deep sleep - {}", err);
353 }
354 return;
355 }
356 Ok(WasiError::UnknownWasiVersion) => {
357 debug!("failed as wasi version is unknown");
358 runtime.on_taint(TaintReason::UnknownWasiVersion);
359 Ok(Errno::Noexec)
360 }
361 Ok(WasiError::DlSymbolResolutionFailed(symbol)) => {
362 debug!("failed as a needed DL symbol could not be resolved");
363 runtime.on_taint(TaintReason::DlSymbolResolutionFailed(symbol.clone()));
364 Err(WasiError::DlSymbolResolutionFailed(symbol).into())
365 }
366 Err(err) => {
367 runtime.on_taint(TaintReason::RuntimeError(err.clone()));
368 Err(WasiRuntimeError::from(err))
369 }
370 }
371 } else {
372 Ok(Errno::Success)
373 };
374
375 let code = if let Err(err) = &ret {
376 match err.as_exit_code() {
377 Some(s) => s,
378 None => {
379 let err_display = err.display(&mut store);
380 error!("{err_display}");
381 eprintln!("{err_display}");
382 Errno::Noexec.into()
383 }
384 }
385 } else {
386 Errno::Success.into()
387 };
388
389 ctx.data(&store).blocking_on_exit(Some(code));
391 unsafe { run_recycle(recycle, ctx, store) };
392
393 debug!("wasi[{pid}]::main() has exited with {code}");
394 handle.thread.set_status_finished(ret.map(|a| a.into()));
395}
396
397#[allow(clippy::type_complexity)]
398fn resume_vfork(
399 ctx: &WasiFunctionEnv,
400 mut store: Store,
401 start: &Function,
402 call_ret: &Result<Box<[Value]>, RuntimeError>,
403) -> (
404 Store,
405 Result<Option<Result<Box<[Value]>, RuntimeError>>, Errno>,
406) {
407 let (err, code) = match call_ret {
408 Ok(_) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)),
409 Err(err) => match err.downcast_ref::<WasiError>() {
410 Some(WasiError::DeepSleep(..)) => return (store, Ok(None)),
412
413 Some(WasiError::Exit(code)) => (None, *code),
414 Some(WasiError::ThreadExit) => (None, wasmer_wasix_types::wasi::ExitCode::from(0u16)),
415 Some(WasiError::UnknownWasiVersion) => (None, Errno::Noexec.into()),
416 Some(WasiError::DlSymbolResolutionFailed(_)) => (None, Errno::Nolink.into()),
417 None => (
418 Some(WasiRuntimeError::from(err.clone())),
419 Errno::Unknown.into(),
420 ),
421 },
422 };
423
424 if let Some(mut vfork) = ctx.data_mut(&mut store).vfork.take() {
425 if let Some(err) = err {
426 error!(%err, "Error from child process");
427 eprintln!("{err}");
428 }
429
430 block_on(
431 unsafe { ctx.data(&store).get_memory_and_wasi_state(&store, 0) }
432 .1
433 .fs
434 .close_all(),
435 );
436
437 tracing::debug!(
438 pid = %ctx.data_mut(&mut store).process.pid(),
439 vfork_pid = %vfork.env.process.pid(),
440 "Resuming from vfork after child process was terminated"
441 );
442
443 vfork.env.swap_inner(ctx.data_mut(&mut store));
445 std::mem::swap(vfork.env.as_mut(), ctx.data_mut(&mut store));
446 let mut child_env = *vfork.env;
447 child_env.owned_handles.push(vfork.handle);
448
449 child_env.process.terminate(code);
451
452 if ctx.data(&store).context_switching_environment.is_some() {
454 tracing::error!(
456 "Terminated a vfork in another way than exit or exec which is undefined behaviour. In this case the parent process will be terminated."
457 );
458 return (store, Err(code.into()));
459 }
460 let Some(asyncify_info) = vfork.asyncify else {
461 tracing::error!(
463 "Terminated a vfork in another way than exit or exec which is undefined behaviour. In this case the parent process will be terminated."
464 );
465 return (store, Err(code.into()));
466 };
467 let child_pid = child_env.process.pid();
472 let rewind_stack = asyncify_info.rewind_stack.freeze();
473 let store_data = asyncify_info.store_data;
474
475 let ctx_cloned = ctx.env.clone().into_mut(&mut store);
476 let rewind_result = if asyncify_info.is_64bit {
478 crate::syscalls::rewind::<Memory64, _>(
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 } else {
489 crate::syscalls::rewind::<Memory32, _>(
490 ctx_cloned,
491 None,
492 rewind_stack,
493 store_data,
494 crate::syscalls::ForkResult {
495 pid: child_pid.raw() as wasmer_wasix_types::wasi::Pid,
496 ret: Errno::Success,
497 },
498 )
499 };
500
501 match rewind_result {
502 Errno::Success => {
503 let (store, result) = ContextSwitchingEnvironment::run_main_context(
506 ctx,
507 store,
508 start.clone(),
509 vec![],
510 );
511 (store, Ok(Some(result)))
512 }
513 err => {
514 warn!("fork failed - could not rewind the stack - errno={}", err);
515 (store, Err(err))
516 }
517 }
518 } else {
519 (store, Ok(None))
520 }
521}