wasmer_wasix/runners/
wasi.rs

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