wasmer_wasix/runners/
wasi.rs

1//! WebC container support for running WASI modules
2
3use std::{path::PathBuf, sync::Arc};
4
5#[cfg(feature = "napi-v8")]
6use std::borrow::Cow;
7
8use anyhow::{Context, Error};
9use tracing::Instrument;
10use virtual_fs::{ArcBoxFile, FileSystem, TmpFileSystem, VirtualFile};
11use wasmer::{Engine, Module};
12use wasmer_types::ModuleHash;
13use webc::metadata::{Command, annotations::Wasi};
14
15use crate::{
16    Runtime, WasiEnvBuilder, WasiError, WasiRuntimeError,
17    bin_factory::BinaryPackage,
18    capabilities::Capabilities,
19    journal::{DynJournal, DynReadableJournal, SnapshotTrigger},
20    runners::{MappedDirectory, MountedDirectory, wasi_common::CommonWasiOptions},
21    runtime::task_manager::VirtualTaskManagerExt,
22};
23
24use super::wasi_common::{MAPPED_CURRENT_DIR_DEFAULT_PATH, MappedCommand};
25
26#[cfg(feature = "napi-v8")]
27use wasmer_napi::NapiCtx;
28
29#[derive(Debug, Default, Clone)]
30pub struct WasiRunner {
31    wasi: CommonWasiOptions,
32    stdin: Option<ArcBoxFile>,
33    stdout: Option<ArcBoxFile>,
34    stderr: Option<ArcBoxFile>,
35}
36
37pub enum PackageOrHash<'a> {
38    Package(&'a BinaryPackage),
39    Hash(ModuleHash),
40}
41
42pub enum RuntimeOrEngine {
43    Runtime(Arc<dyn Runtime + Send + Sync>),
44    Engine(Engine),
45}
46
47impl WasiRunner {
48    /// Constructs a new `WasiRunner`.
49    pub fn new() -> Self {
50        WasiRunner::default()
51    }
52
53    /// Returns the current entry function for this `WasiRunner`
54    pub fn entry_function(&self) -> Option<String> {
55        self.wasi.entry_function.clone()
56    }
57
58    /// Builder method to set the name of the entry function for this `WasiRunner`
59    pub fn with_entry_function<S>(&mut self, entry_function: S) -> &mut Self
60    where
61        S: Into<String>,
62    {
63        self.wasi.entry_function = Some(entry_function.into());
64        self
65    }
66
67    /// Returns the current arguments for this `WasiRunner`
68    pub fn get_args(&self) -> Vec<String> {
69        self.wasi.args.clone()
70    }
71
72    /// Builder method to provide CLI args to the runner
73    pub fn with_args<A, S>(&mut self, args: A) -> &mut Self
74    where
75        A: IntoIterator<Item = S>,
76        S: Into<String>,
77    {
78        self.wasi.args = args.into_iter().map(|s| s.into()).collect();
79        self
80    }
81
82    /// Builder method to provide environment variables to the runner.
83    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
84        self.wasi.env.insert(key.into(), value.into());
85        self
86    }
87
88    pub fn with_envs<I, K, V>(&mut self, envs: I) -> &mut Self
89    where
90        I: IntoIterator<Item = (K, V)>,
91        K: Into<String>,
92        V: Into<String>,
93    {
94        for (key, value) in envs {
95            self.wasi.env.insert(key.into(), value.into());
96        }
97        self
98    }
99
100    pub fn with_forward_host_env(&mut self, forward: bool) -> &mut Self {
101        self.wasi.forward_host_env = forward;
102        self
103    }
104
105    pub fn with_mapped_directories<I, D>(&mut self, dirs: I) -> &mut Self
106    where
107        I: IntoIterator<Item = D>,
108        D: Into<MappedDirectory>,
109    {
110        self.with_mounted_directories(dirs.into_iter().map(Into::into).map(MountedDirectory::from))
111    }
112
113    pub fn with_home_mapped(&mut self, is_home_mapped: bool) -> &mut Self {
114        self.wasi.is_home_mapped = is_home_mapped;
115        self
116    }
117
118    pub fn with_mounted_directories<I, D>(&mut self, dirs: I) -> &mut Self
119    where
120        I: IntoIterator<Item = D>,
121        D: Into<MountedDirectory>,
122    {
123        self.wasi.mounts.extend(dirs.into_iter().map(Into::into));
124        self
125    }
126
127    /// Mount a [`FileSystem`] instance at a particular location.
128    pub fn with_mount(&mut self, dest: String, fs: Arc<dyn FileSystem + Send + Sync>) -> &mut Self {
129        self.wasi.mounts.push(MountedDirectory { guest: dest, fs });
130        self
131    }
132
133    /// Override the directory the WASIX instance will start in.
134    pub fn with_current_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
135        self.wasi.current_dir = Some(dir.into());
136        self
137    }
138
139    /// Add a package that should be available to the instance at runtime.
140    pub fn with_injected_package(&mut self, pkg: BinaryPackage) -> &mut Self {
141        self.wasi.injected_packages.push(pkg);
142        self
143    }
144
145    /// Add packages that should be available to the instance at runtime.
146    pub fn with_injected_packages(
147        &mut self,
148        packages: impl IntoIterator<Item = BinaryPackage>,
149    ) -> &mut Self {
150        self.wasi.injected_packages.extend(packages);
151        self
152    }
153
154    pub fn with_mapped_host_command(
155        &mut self,
156        alias: impl Into<String>,
157        target: impl Into<String>,
158    ) -> &mut Self {
159        self.wasi.mapped_host_commands.push(MappedCommand {
160            alias: alias.into(),
161            target: target.into(),
162        });
163        self
164    }
165
166    pub fn with_mapped_host_commands(
167        &mut self,
168        commands: impl IntoIterator<Item = MappedCommand>,
169    ) -> &mut Self {
170        self.wasi.mapped_host_commands.extend(commands);
171        self
172    }
173
174    pub fn capabilities_mut(&mut self) -> &mut Capabilities {
175        &mut self.wasi.capabilities
176    }
177
178    pub fn with_capabilities(&mut self, capabilities: Capabilities) -> &mut Self {
179        self.wasi.capabilities = capabilities;
180        self
181    }
182
183    #[cfg(feature = "napi-v8")]
184    pub fn with_napi_ctx(
185        &mut self,
186        runtime: &mut crate::PluggableRuntime,
187        napi: &NapiCtx,
188    ) -> &mut Self {
189        let hooks = napi.runtime_hooks();
190        runtime.with_additional_imports({
191            let hooks = hooks.clone();
192            move |module, store| hooks.additional_imports(module, store)
193        });
194        runtime.with_instance_setup(move |module, store, instance, imported_memory| {
195            hooks.configure_instance(module, store, instance, imported_memory)
196        });
197
198        self
199    }
200
201    #[cfg(feature = "journal")]
202    pub fn with_snapshot_trigger(&mut self, on: SnapshotTrigger) -> &mut Self {
203        self.wasi.snapshot_on.push(on);
204        self
205    }
206
207    #[cfg(feature = "journal")]
208    pub fn with_default_snapshot_triggers(&mut self) -> &mut Self {
209        for on in crate::journal::DEFAULT_SNAPSHOT_TRIGGERS {
210            if !self.has_snapshot_trigger(on) {
211                self.with_snapshot_trigger(on);
212            }
213        }
214        self
215    }
216
217    #[cfg(feature = "journal")]
218    pub fn has_snapshot_trigger(&self, on: SnapshotTrigger) -> bool {
219        self.wasi.snapshot_on.contains(&on)
220    }
221
222    #[cfg(feature = "journal")]
223    pub fn with_snapshot_interval(&mut self, period: std::time::Duration) -> &mut Self {
224        if !self.has_snapshot_trigger(SnapshotTrigger::PeriodicInterval) {
225            self.with_snapshot_trigger(SnapshotTrigger::PeriodicInterval);
226        }
227        self.wasi.snapshot_interval.replace(period);
228        self
229    }
230
231    #[cfg(feature = "journal")]
232    pub fn with_stop_running_after_snapshot(&mut self, stop_running: bool) -> &mut Self {
233        self.wasi.stop_running_after_snapshot = stop_running;
234        self
235    }
236
237    #[cfg(feature = "journal")]
238    pub fn with_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) -> &mut Self {
239        self.wasi.read_only_journals.push(journal);
240        self
241    }
242
243    #[cfg(feature = "journal")]
244    pub fn with_writable_journal(&mut self, journal: Arc<DynJournal>) -> &mut Self {
245        self.wasi.writable_journals.push(journal);
246        self
247    }
248
249    pub fn with_skip_stdio_during_bootstrap(&mut self, skip: bool) -> &mut Self {
250        self.wasi.skip_stdio_during_bootstrap = skip;
251        self
252    }
253
254    pub fn with_stdin(&mut self, stdin: Box<dyn VirtualFile + Send + Sync>) -> &mut Self {
255        self.stdin = Some(ArcBoxFile::new(stdin));
256        self
257    }
258
259    pub fn with_stdout(&mut self, stdout: Box<dyn VirtualFile + Send + Sync>) -> &mut Self {
260        self.stdout = Some(ArcBoxFile::new(stdout));
261        self
262    }
263
264    pub fn with_stderr(&mut self, stderr: Box<dyn VirtualFile + Send + Sync>) -> &mut Self {
265        self.stderr = Some(ArcBoxFile::new(stderr));
266        self
267    }
268
269    fn ensure_tokio_runtime() -> Option<tokio::runtime::Runtime> {
270        #[cfg(feature = "sys-thread")]
271        {
272            if tokio::runtime::Handle::try_current().is_ok() {
273                return None;
274            }
275
276            let rt = tokio::runtime::Builder::new_multi_thread()
277                .enable_all()
278                .build()
279                .expect(
280                    "Failed to build a multi-threaded tokio runtime. This is necessary \
281                for WASIX to work. You can provide a tokio runtime by building one \
282                yourself and entering it before using WasiRunner.",
283                );
284            Some(rt)
285        }
286
287        #[cfg(not(feature = "sys-thread"))]
288        {
289            None
290        }
291    }
292
293    #[cfg(feature = "napi-v8")]
294    fn maybe_disable_async_threading_for_module(&mut self, module: &Module) {
295        if crate::module_needs_napi(module) {
296            self.wasi
297                .capabilities
298                .threading
299                .enable_asynchronous_threading = false;
300        }
301    }
302
303    #[cfg(feature = "napi-v8")]
304    fn maybe_disable_async_threading_for_command(
305        &mut self,
306        cmd: &crate::bin_factory::BinaryPackageCommand,
307        runtime_or_engine: &RuntimeOrEngine,
308    ) -> Result<(), anyhow::Error> {
309        let module = match runtime_or_engine {
310            RuntimeOrEngine::Runtime(runtime) => runtime.resolve_module_sync(
311                crate::runtime::ModuleInput::Command(Cow::Borrowed(cmd)),
312                None,
313                None,
314            )?,
315            RuntimeOrEngine::Engine(engine) => Module::new(engine, cmd.atom_ref())?,
316        };
317        self.maybe_disable_async_threading_for_module(&module);
318        Ok(())
319    }
320
321    #[tracing::instrument(level = "debug", skip_all)]
322    pub fn prepare_webc_env(
323        &self,
324        program_name: &str,
325        wasi: &Wasi,
326        pkg_or_hash: PackageOrHash,
327        runtime_or_engine: RuntimeOrEngine,
328        root_fs: Option<TmpFileSystem>,
329    ) -> Result<WasiEnvBuilder, anyhow::Error> {
330        let mut builder = WasiEnvBuilder::new(program_name);
331
332        match runtime_or_engine {
333            RuntimeOrEngine::Runtime(runtime) => {
334                builder.set_runtime(runtime);
335            }
336            RuntimeOrEngine::Engine(engine) => {
337                builder.set_engine(engine);
338            }
339        }
340
341        let container_fs = match pkg_or_hash {
342            PackageOrHash::Package(pkg) => {
343                builder.add_webc(pkg.clone());
344                builder.set_module_hash(pkg.hash());
345                builder.include_packages(pkg.package_ids.clone());
346
347                pkg.webc_fs.as_deref().map(|fs| fs.duplicate())
348            }
349            PackageOrHash::Hash(hash) => {
350                builder.set_module_hash(hash);
351                None
352            }
353        };
354
355        if self.wasi.is_home_mapped {
356            builder.set_current_dir(MAPPED_CURRENT_DIR_DEFAULT_PATH);
357        }
358
359        if let Some(current_dir) = &self.wasi.current_dir {
360            builder.set_current_dir(current_dir.clone());
361        }
362
363        if let Some(cwd) = &wasi.cwd {
364            builder.set_current_dir(cwd);
365        }
366
367        self.wasi
368            .prepare_webc_env(&mut builder, container_fs, wasi, root_fs)?;
369
370        if let Some(stdin) = &self.stdin {
371            builder.set_stdin(Box::new(stdin.clone()));
372        }
373        if let Some(stdout) = &self.stdout {
374            builder.set_stdout(Box::new(stdout.clone()));
375        }
376        if let Some(stderr) = &self.stderr {
377            builder.set_stderr(Box::new(stderr.clone()));
378        }
379
380        Ok(builder)
381    }
382
383    pub fn run_wasm(
384        &self,
385        runtime_or_engine: RuntimeOrEngine,
386        program_name: &str,
387        module: Module,
388        module_hash: ModuleHash,
389    ) -> Result<(), Error> {
390        // Just keep the runtime and enter guard alive until we're done running the module
391        let tokio_runtime = Self::ensure_tokio_runtime();
392        let _guard = tokio_runtime.as_ref().map(|rt| rt.enter());
393
394        #[cfg(feature = "napi-v8")]
395        let mut runner = self.clone();
396        #[cfg(not(feature = "napi-v8"))]
397        let runner = self.clone();
398        #[cfg(feature = "napi-v8")]
399        runner.maybe_disable_async_threading_for_module(&module);
400
401        let wasi = webc::metadata::annotations::Wasi::new(program_name);
402
403        let mut builder = runner.prepare_webc_env(
404            program_name,
405            &wasi,
406            PackageOrHash::Hash(module_hash),
407            runtime_or_engine,
408            None,
409        )?;
410
411        #[cfg(feature = "ctrlc")]
412        {
413            builder = builder.attach_ctrl_c();
414        }
415
416        #[cfg(feature = "journal")]
417        {
418            for journal in runner.wasi.read_only_journals.iter().cloned() {
419                builder.add_read_only_journal(journal);
420            }
421            for journal in runner.wasi.writable_journals.iter().cloned() {
422                builder.add_writable_journal(journal);
423            }
424
425            if !runner.wasi.snapshot_on.is_empty() {
426                for trigger in runner.wasi.snapshot_on.iter().cloned() {
427                    builder.add_snapshot_trigger(trigger);
428                }
429            } else if !runner.wasi.writable_journals.is_empty() {
430                for on in crate::journal::DEFAULT_SNAPSHOT_TRIGGERS {
431                    builder.add_snapshot_trigger(on);
432                }
433            }
434
435            if let Some(period) = runner.wasi.snapshot_interval {
436                if runner.wasi.writable_journals.is_empty() {
437                    return Err(anyhow::format_err!(
438                        "If you specify a snapshot interval then you must also specify a writable journal file"
439                    ));
440                }
441                builder.with_snapshot_interval(period);
442            }
443
444            builder.with_stop_running_after_snapshot(runner.wasi.stop_running_after_snapshot);
445            builder.with_skip_stdio_during_bootstrap(runner.wasi.skip_stdio_during_bootstrap);
446        }
447
448        let env = builder.build()?;
449        let runtime = env.runtime.clone();
450        let tasks = runtime.task_manager().clone();
451
452        let mut task_handle =
453            crate::bin_factory::spawn_exec_module(module, env, &runtime).context("Spawn failed")?;
454
455        #[cfg(feature = "ctrlc")]
456        task_handle.install_ctrlc_handler();
457        let task_handle = async move { task_handle.wait_finished().await }.in_current_span();
458
459        let result = tasks.spawn_and_block_on(task_handle)?;
460        let exit_code = result
461            .map_err(|err| {
462                // We do our best to recover the error
463                let msg = err.to_string();
464                let weak = Arc::downgrade(&err);
465                Arc::into_inner(err).unwrap_or_else(|| {
466                    weak.upgrade()
467                        .map(|err| wasi_runtime_error_to_owned(&err))
468                        .unwrap_or_else(|| {
469                            WasiRuntimeError::Anyhow(Arc::new(anyhow::format_err!("{msg}")))
470                        })
471                })
472            })
473            .context("Unable to wait for the process to exit")?;
474
475        if exit_code.raw() == 0 {
476            Ok(())
477        } else {
478            Err(WasiRuntimeError::Wasi(crate::WasiError::Exit(exit_code)).into())
479        }
480    }
481
482    pub fn run_command(
483        &mut self,
484        command_name: &str,
485        pkg: &BinaryPackage,
486        runtime_or_engine: RuntimeOrEngine,
487    ) -> Result<(), Error> {
488        // Just keep the runtime and enter guard alive until we're done running the module
489        let tokio_runtime = Self::ensure_tokio_runtime();
490        let _guard = tokio_runtime.as_ref().map(|rt| rt.enter());
491
492        let cmd = pkg
493            .get_command(command_name)
494            .with_context(|| format!("The package doesn't contain a \"{command_name}\" command"))?;
495
496        #[cfg(feature = "napi-v8")]
497        self.maybe_disable_async_threading_for_command(cmd, &runtime_or_engine)?;
498
499        let wasi = cmd
500            .metadata()
501            .annotation("wasi")?
502            .unwrap_or_else(|| Wasi::new(command_name));
503
504        let exec_name = if let Some(exec_name) = wasi.exec_name.as_ref() {
505            exec_name
506        } else {
507            command_name
508        };
509
510        #[allow(unused_mut)]
511        let mut builder = self
512            .prepare_webc_env(
513                exec_name,
514                &wasi,
515                PackageOrHash::Package(pkg),
516                runtime_or_engine,
517                None,
518            )
519            .context("Unable to prepare the WASI environment")?;
520
521        #[cfg(feature = "journal")]
522        {
523            for journal in self.wasi.read_only_journals.iter().cloned() {
524                builder.add_read_only_journal(journal);
525            }
526            for journal in self.wasi.writable_journals.iter().cloned() {
527                builder.add_writable_journal(journal);
528            }
529
530            if !self.wasi.snapshot_on.is_empty() {
531                for trigger in self.wasi.snapshot_on.iter().cloned() {
532                    builder.add_snapshot_trigger(trigger);
533                }
534            } else if !self.wasi.writable_journals.is_empty() {
535                for on in crate::journal::DEFAULT_SNAPSHOT_TRIGGERS {
536                    builder.add_snapshot_trigger(on);
537                }
538            }
539
540            if let Some(period) = self.wasi.snapshot_interval {
541                if self.wasi.writable_journals.is_empty() {
542                    return Err(anyhow::format_err!(
543                        "If you specify a snapshot interval then you must also specify a journal file"
544                    ));
545                }
546                builder.with_snapshot_interval(period);
547            }
548
549            builder.with_stop_running_after_snapshot(self.wasi.stop_running_after_snapshot);
550        }
551
552        let env = builder.build()?;
553        let runtime = env.runtime.clone();
554        let command_name = command_name.to_string();
555        let tasks = runtime.task_manager().clone();
556        let pkg = pkg.clone();
557
558        // Wrapping the call to `spawn_and_block_on` in a call to `spawn_await` could help to prevent deadlocks
559        // because then blocking in here won't block the tokio runtime
560        //
561        // See run_wasm above for a possible fix
562        let exit_code = tasks.spawn_and_block_on(
563            async move {
564                let mut task_handle =
565                    crate::bin_factory::spawn_exec(pkg, &command_name, env, &runtime)
566                        .await
567                        .context("Spawn failed")?;
568
569                #[cfg(feature = "ctrlc")]
570                task_handle.install_ctrlc_handler();
571
572                task_handle
573                    .wait_finished()
574                    .await
575                    .map_err(|err| {
576                        // We do our best to recover the error
577                        let msg = err.to_string();
578                        let weak = Arc::downgrade(&err);
579                        Arc::into_inner(err).unwrap_or_else(|| {
580                            weak.upgrade()
581                                .map(|err| wasi_runtime_error_to_owned(&err))
582                                .unwrap_or_else(|| {
583                                    WasiRuntimeError::Anyhow(Arc::new(anyhow::format_err!("{msg}")))
584                                })
585                        })
586                    })
587                    .context("Unable to wait for the process to exit")
588            }
589            .in_current_span(),
590        )??;
591
592        if exit_code.raw() == 0 {
593            Ok(())
594        } else {
595            Err(WasiRuntimeError::Wasi(crate::WasiError::Exit(exit_code)).into())
596        }
597    }
598}
599
600impl crate::runners::Runner for WasiRunner {
601    fn can_run_command(command: &Command) -> Result<bool, Error> {
602        Ok(command
603            .runner
604            .starts_with(webc::metadata::annotations::WASI_RUNNER_URI))
605    }
606
607    #[tracing::instrument(skip_all)]
608    fn run_command(
609        &mut self,
610        command_name: &str,
611        pkg: &BinaryPackage,
612        runtime: Arc<dyn Runtime + Send + Sync>,
613    ) -> Result<(), Error> {
614        self.run_command(command_name, pkg, RuntimeOrEngine::Runtime(runtime))
615    }
616}
617
618fn wasi_runtime_error_to_owned(err: &WasiRuntimeError) -> WasiRuntimeError {
619    match err {
620        WasiRuntimeError::Init(a) => WasiRuntimeError::Init(a.clone()),
621        WasiRuntimeError::Export(a) => WasiRuntimeError::Export(a.clone()),
622        WasiRuntimeError::Instantiation(a) => WasiRuntimeError::Instantiation(a.clone()),
623        WasiRuntimeError::Wasi(WasiError::Exit(a)) => WasiRuntimeError::Wasi(WasiError::Exit(*a)),
624        WasiRuntimeError::Wasi(WasiError::ThreadExit) => {
625            WasiRuntimeError::Wasi(WasiError::ThreadExit)
626        }
627        WasiRuntimeError::Wasi(WasiError::UnknownWasiVersion) => {
628            WasiRuntimeError::Wasi(WasiError::UnknownWasiVersion)
629        }
630        WasiRuntimeError::Wasi(WasiError::DeepSleep(_)) => {
631            WasiRuntimeError::Anyhow(Arc::new(anyhow::format_err!("deep-sleep")))
632        }
633        WasiRuntimeError::Wasi(WasiError::DlSymbolResolutionFailed(symbol)) => {
634            WasiRuntimeError::Wasi(WasiError::DlSymbolResolutionFailed(symbol.clone()))
635        }
636        WasiRuntimeError::ControlPlane(a) => WasiRuntimeError::ControlPlane(a.clone()),
637        WasiRuntimeError::Runtime(a) => WasiRuntimeError::Runtime(a.clone()),
638        WasiRuntimeError::Thread(a) => WasiRuntimeError::Thread(a.clone()),
639        WasiRuntimeError::Anyhow(a) => WasiRuntimeError::Anyhow(a.clone()),
640    }
641}
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646
647    #[test]
648    fn send_and_sync() {
649        fn assert_send<T: Send>() {}
650        fn assert_sync<T: Sync>() {}
651
652        assert_send::<WasiRunner>();
653        assert_sync::<WasiRunner>();
654    }
655
656    #[cfg(all(feature = "host-fs", feature = "sys"))]
657    #[tokio::test]
658    async fn test_volume_mount_without_webcs() {
659        use std::sync::Arc;
660
661        let root_fs = virtual_fs::RootFileSystemBuilder::new().build();
662
663        let tokrt = tokio::runtime::Handle::current();
664
665        let hostdir = virtual_fs::host_fs::FileSystem::new(tokrt.clone(), "/").unwrap();
666        let hostdir_dyn: Arc<dyn virtual_fs::FileSystem + Send + Sync> = Arc::new(hostdir);
667
668        root_fs
669            .mount("/host".into(), &hostdir_dyn, "/".into())
670            .unwrap();
671
672        let envb = crate::runners::wasi::WasiRunner::new();
673
674        let annotations = webc::metadata::annotations::Wasi::new("test");
675
676        let tm = Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::new(
677            tokrt.clone(),
678        ));
679        let rt = crate::PluggableRuntime::new(tm);
680
681        let envb = envb
682            .prepare_webc_env(
683                "test",
684                &annotations,
685                PackageOrHash::Hash(ModuleHash::random()),
686                RuntimeOrEngine::Runtime(Arc::new(rt)),
687                Some(root_fs),
688            )
689            .unwrap();
690
691        let init = envb.build_init().unwrap();
692
693        let fs = &init.state.fs.root_fs;
694
695        fs.read_dir(std::path::Path::new("/host")).unwrap();
696    }
697
698    #[cfg(all(feature = "host-fs", feature = "sys"))]
699    #[tokio::test]
700    async fn test_volume_mount_with_webcs() {
701        use std::sync::Arc;
702
703        use wasmer_package::utils::from_bytes;
704
705        let root_fs = virtual_fs::RootFileSystemBuilder::new().build();
706
707        let tokrt = tokio::runtime::Handle::current();
708
709        let hostdir = virtual_fs::host_fs::FileSystem::new(tokrt.clone(), "/").unwrap();
710        let hostdir_dyn: Arc<dyn virtual_fs::FileSystem + Send + Sync> = Arc::new(hostdir);
711
712        root_fs
713            .mount("/host".into(), &hostdir_dyn, "/".into())
714            .unwrap();
715
716        let envb = crate::runners::wasi::WasiRunner::new();
717
718        let annotations = webc::metadata::annotations::Wasi::new("test");
719
720        let tm = Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::new(
721            tokrt.clone(),
722        ));
723        let mut rt = crate::PluggableRuntime::new(tm);
724        rt.set_package_loader(crate::runtime::package_loader::BuiltinPackageLoader::new());
725
726        let webc_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../../tests/integration/cli/tests/webc/wasmer-tests--volume-static-webserver@0.1.0.webc");
727        let webc_data = std::fs::read(webc_path).unwrap();
728        let container = from_bytes(webc_data).unwrap();
729
730        let binpkg = crate::bin_factory::BinaryPackage::from_webc(&container, &rt)
731            .await
732            .unwrap();
733
734        let mut envb = envb
735            .prepare_webc_env(
736                "test",
737                &annotations,
738                PackageOrHash::Package(&binpkg),
739                RuntimeOrEngine::Runtime(Arc::new(rt)),
740                Some(root_fs),
741            )
742            .unwrap();
743
744        envb = envb.preopen_dir("/host").unwrap();
745
746        let init = envb.build_init().unwrap();
747
748        let fs = &init.state.fs.root_fs;
749
750        fs.read_dir(std::path::Path::new("/host")).unwrap();
751        fs.read_dir(std::path::Path::new("/settings")).unwrap();
752    }
753}