1use std::{
4 collections::{HashMap, HashSet},
5 path::{Path, PathBuf},
6 sync::Arc,
7};
8
9use rand::RngExt;
10use thiserror::Error;
11use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile};
12use wasmer::{AsStoreMut, Engine, Instance, Module};
13use wasmer_config::package::PackageId;
14
15#[cfg(feature = "journal")]
16use crate::journal::{DynJournal, DynReadableJournal, SnapshotTrigger};
17use crate::{
18 Runtime, WasiEnv, WasiFunctionEnv, WasiRuntimeError, WasiThreadError,
19 bin_factory::{BinFactory, BinaryPackage},
20 capabilities::Capabilities,
21 fs::{WasiFs, WasiFsRoot, WasiInodes},
22 os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane},
23 state::WasiState,
24 syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO},
25};
26use wasmer_types::ModuleHash;
27use wasmer_wasix_types::wasi::SignalDisposition;
28
29use super::env::WasiEnvInit;
30
31#[derive(Default)]
48pub struct WasiEnvBuilder {
49 pub(super) entry_function: Option<String>,
51 pub(super) args: Vec<String>,
53 pub(super) envs: Vec<(String, Vec<u8>)>,
55 pub(super) signals: Vec<SignalDisposition>,
57 pub(super) preopens: Vec<PreopenedDir>,
59 vfs_preopens: Vec<String>,
61 #[allow(clippy::type_complexity)]
62 pub(super) setup_fs_fn:
63 Option<Box<dyn Fn(&WasiInodes, &mut WasiFs) -> Result<(), String> + Send>>,
64 pub(super) stdout: Option<Box<dyn VirtualFile + Send + Sync + 'static>>,
65 pub(super) stderr: Option<Box<dyn VirtualFile + Send + Sync + 'static>>,
66 pub(super) stdin: Option<Box<dyn VirtualFile + Send + Sync + 'static>>,
67 pub(super) fs: Option<WasiFsRoot>,
68 pub(super) engine: Option<Engine>,
69 pub(super) runtime: Option<Arc<dyn crate::Runtime + Send + Sync + 'static>>,
70 pub(super) current_dir: Option<PathBuf>,
71
72 pub(super) uses: Vec<BinaryPackage>,
74
75 pub(super) included_packages: HashSet<PackageId>,
76
77 pub(super) module_hash: Option<ModuleHash>,
78
79 pub(super) map_commands: HashMap<String, PathBuf>,
81
82 pub(super) capabilites: Capabilities,
83
84 #[cfg(feature = "journal")]
85 pub(super) snapshot_on: Vec<SnapshotTrigger>,
86
87 #[cfg(feature = "journal")]
88 pub(super) snapshot_interval: Option<std::time::Duration>,
89
90 #[cfg(feature = "journal")]
91 pub(super) stop_running_after_snapshot: bool,
92
93 #[cfg(feature = "journal")]
94 pub(super) read_only_journals: Vec<Arc<DynReadableJournal>>,
95
96 #[cfg(feature = "journal")]
97 pub(super) writable_journals: Vec<Arc<DynJournal>>,
98
99 pub(super) skip_stdio_during_bootstrap: bool,
100
101 #[cfg(feature = "ctrlc")]
102 pub(super) attach_ctrl_c: bool,
103}
104
105impl std::fmt::Debug for WasiEnvBuilder {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("WasiEnvBuilder")
109 .field("entry_function", &self.entry_function)
110 .field("args", &self.args)
111 .field("envs", &self.envs)
112 .field("signals", &self.signals)
113 .field("preopens", &self.preopens)
114 .field("uses", &self.uses)
115 .field("setup_fs_fn exists", &self.setup_fs_fn.is_some())
116 .field("stdout_override exists", &self.stdout.is_some())
117 .field("stderr_override exists", &self.stderr.is_some())
118 .field("stdin_override exists", &self.stdin.is_some())
119 .field("engine_override_exists", &self.engine.is_some())
120 .field("runtime_override_exists", &self.runtime.is_some())
121 .finish()
122 }
123}
124
125#[derive(Error, Debug, Clone, PartialEq, Eq)]
127pub enum WasiStateCreationError {
128 #[error("bad environment variable format: `{0}`")]
129 EnvironmentVariableFormatError(String),
130 #[error("argument contains null byte: `{0}`")]
131 ArgumentContainsNulByte(String),
132 #[error("preopened directory not found: `{0}`")]
133 PreopenedDirectoryNotFound(PathBuf),
134 #[error("preopened directory error: `{0}`")]
135 PreopenedDirectoryError(String),
136 #[error("mapped dir alias has wrong format: `{0}`")]
137 MappedDirAliasFormattingError(String),
138 #[error("wasi filesystem creation error: `{0}`")]
139 WasiFsCreationError(String),
140 #[error("wasi filesystem setup error: `{0}`")]
141 WasiFsSetupError(String),
142 #[error(transparent)]
143 FileSystemError(#[from] FsError),
144 #[error("wasi inherit error: `{0}`")]
145 WasiInheritError(String),
146 #[error("wasi include package: `{0}`")]
147 WasiIncludePackageError(String),
148 #[error("control plane error")]
149 ControlPlane(#[from] ControlPlaneError),
150}
151
152fn validate_mapped_dir_alias(alias: &str) -> Result<(), WasiStateCreationError> {
153 if !alias.bytes().all(|b| b != b'\0') {
154 return Err(WasiStateCreationError::MappedDirAliasFormattingError(
155 format!("Alias \"{alias}\" contains a nul byte"),
156 ));
157 }
158
159 Ok(())
160}
161
162pub type SetupFsFn = Box<dyn Fn(&WasiInodes, &mut WasiFs) -> Result<(), String> + Send>;
163
164impl WasiEnvBuilder {
167 pub fn new(program_name: impl Into<String>) -> Self {
169 WasiEnvBuilder {
170 args: vec![program_name.into()],
171 ..WasiEnvBuilder::default()
172 }
173 }
174
175 #[cfg(feature = "ctrlc")]
178 pub fn attach_ctrl_c(mut self) -> Self {
179 self.attach_ctrl_c = true;
180 self
181 }
182
183 pub fn env<Key, Value>(mut self, key: Key, value: Value) -> Self
189 where
190 Key: AsRef<[u8]>,
191 Value: AsRef<[u8]>,
192 {
193 self.add_env(key, value);
194 self
195 }
196
197 pub fn add_env<Key, Value>(&mut self, key: Key, value: Value)
203 where
204 Key: AsRef<[u8]>,
205 Value: AsRef<[u8]>,
206 {
207 self.envs.push((
208 String::from_utf8_lossy(key.as_ref()).to_string(),
209 value.as_ref().to_vec(),
210 ));
211 }
212
213 pub fn envs<I, Key, Value>(mut self, env_pairs: I) -> Self
219 where
220 I: IntoIterator<Item = (Key, Value)>,
221 Key: AsRef<[u8]>,
222 Value: AsRef<[u8]>,
223 {
224 self.add_envs(env_pairs);
225
226 self
227 }
228
229 pub fn add_envs<I, Key, Value>(&mut self, env_pairs: I)
235 where
236 I: IntoIterator<Item = (Key, Value)>,
237 Key: AsRef<[u8]>,
238 Value: AsRef<[u8]>,
239 {
240 for (key, value) in env_pairs {
241 self.add_env(key, value);
242 }
243 }
244
245 pub fn get_env(&self) -> &[(String, Vec<u8>)] {
247 &self.envs
248 }
249
250 pub fn get_env_mut(&mut self) -> &mut Vec<(String, Vec<u8>)> {
252 &mut self.envs
253 }
254
255 pub fn signal(mut self, sig_action: SignalDisposition) -> Self {
257 self.add_signal(sig_action);
258 self
259 }
260
261 pub fn add_signal(&mut self, sig_action: SignalDisposition) {
263 self.signals.push(sig_action);
264 }
265
266 pub fn signals<I>(mut self, signal_pairs: I) -> Self
268 where
269 I: IntoIterator<Item = SignalDisposition>,
270 {
271 self.add_signals(signal_pairs);
272
273 self
274 }
275
276 pub fn add_signals<I>(&mut self, signal_pairs: I)
278 where
279 I: IntoIterator<Item = SignalDisposition>,
280 {
281 for sig in signal_pairs {
282 self.add_signal(sig);
283 }
284 }
285
286 pub fn get_signals(&self) -> &[SignalDisposition] {
288 &self.signals
289 }
290
291 pub fn get_signals_mut(&mut self) -> &mut Vec<SignalDisposition> {
293 &mut self.signals
294 }
295
296 pub fn entry_function<S>(mut self, entry_function: S) -> Self
297 where
298 S: AsRef<str>,
299 {
300 self.set_entry_function(entry_function);
301 self
302 }
303
304 pub fn set_entry_function<S>(&mut self, entry_function: S)
305 where
306 S: AsRef<str>,
307 {
308 self.entry_function = Some(entry_function.as_ref().to_owned());
309 }
310
311 pub fn arg<V>(mut self, arg: V) -> Self
316 where
317 V: AsRef<[u8]>,
318 {
319 self.add_arg(arg);
320 self
321 }
322
323 pub fn add_arg<V>(&mut self, arg: V)
328 where
329 V: AsRef<[u8]>,
330 {
331 self.args
332 .push(String::from_utf8_lossy(arg.as_ref()).to_string());
333 }
334
335 pub fn args<I, Arg>(mut self, args: I) -> Self
339 where
340 I: IntoIterator<Item = Arg>,
341 Arg: AsRef<[u8]>,
342 {
343 self.add_args(args);
344
345 self
346 }
347
348 pub fn add_args<I, Arg>(&mut self, args: I)
352 where
353 I: IntoIterator<Item = Arg>,
354 Arg: AsRef<[u8]>,
355 {
356 for arg in args {
357 self.add_arg(arg);
358 }
359 }
360
361 pub fn get_args(&self) -> &[String] {
363 &self.args
364 }
365
366 pub fn get_args_mut(&mut self) -> &mut Vec<String> {
368 &mut self.args
369 }
370
371 pub fn use_webc(mut self, pkg: BinaryPackage) -> Self {
376 self.add_webc(pkg);
377 self
378 }
379
380 pub fn set_module_hash(&mut self, hash: ModuleHash) -> &mut Self {
384 self.module_hash.replace(hash);
385 self
386 }
387
388 pub fn add_webc(&mut self, pkg: BinaryPackage) -> &mut Self {
393 self.uses.push(pkg);
394 self
395 }
396
397 pub fn include_package(&mut self, pkg_id: PackageId) -> &mut Self {
400 self.included_packages.insert(pkg_id);
401 self
402 }
403
404 pub fn include_packages(&mut self, pkg_ids: impl IntoIterator<Item = PackageId>) -> &mut Self {
407 self.included_packages.extend(pkg_ids);
408
409 self
410 }
411
412 pub fn uses<I>(mut self, uses: I) -> Self
417 where
418 I: IntoIterator<Item = BinaryPackage>,
419 {
420 for pkg in uses {
421 self.add_webc(pkg);
422 }
423 self
424 }
425
426 pub fn map_command<Name, Target>(mut self, name: Name, target: Target) -> Self
428 where
429 Name: AsRef<str>,
430 Target: AsRef<str>,
431 {
432 self.add_mapped_command(name, target);
433 self
434 }
435
436 pub fn add_mapped_command<Name, Target>(&mut self, name: Name, target: Target)
438 where
439 Name: AsRef<str>,
440 Target: AsRef<str>,
441 {
442 let path_buf = PathBuf::from(target.as_ref().to_string());
443 self.map_commands
444 .insert(name.as_ref().to_string(), path_buf);
445 }
446
447 pub fn map_commands<I, Name, Target>(mut self, map_commands: I) -> Self
449 where
450 I: IntoIterator<Item = (Name, Target)>,
451 Name: AsRef<str>,
452 Target: AsRef<str>,
453 {
454 self.add_mapped_commands(map_commands);
455 self
456 }
457
458 pub fn add_mapped_commands<I, Name, Target>(&mut self, map_commands: I)
460 where
461 I: IntoIterator<Item = (Name, Target)>,
462 Name: AsRef<str>,
463 Target: AsRef<str>,
464 {
465 for (alias, target) in map_commands {
466 self.add_mapped_command(alias, target);
467 }
468 }
469
470 pub fn preopen_dir<P>(mut self, po_dir: P) -> Result<Self, WasiStateCreationError>
475 where
476 P: AsRef<Path>,
477 {
478 self.add_preopen_dir(po_dir)?;
479 Ok(self)
480 }
481
482 pub fn add_preopen_dir<P>(&mut self, po_dir: P) -> Result<(), WasiStateCreationError>
487 where
488 P: AsRef<Path>,
489 {
490 let mut pdb = PreopenDirBuilder::new();
491 let path = po_dir.as_ref();
492 pdb.directory(path).read(true).write(true).create(true);
493 let preopen = pdb.build()?;
494
495 self.preopens.push(preopen);
496
497 Ok(())
498 }
499
500 pub fn preopen_dirs<I, P>(mut self, dirs: I) -> Result<Self, WasiStateCreationError>
505 where
506 I: IntoIterator<Item = P>,
507 P: AsRef<Path>,
508 {
509 for po_dir in dirs {
510 self.add_preopen_dir(po_dir)?;
511 }
512
513 Ok(self)
514 }
515
516 pub fn preopen_build<F>(mut self, inner: F) -> Result<Self, WasiStateCreationError>
531 where
532 F: Fn(&mut PreopenDirBuilder) -> &mut PreopenDirBuilder,
533 {
534 self.add_preopen_build(inner)?;
535 Ok(self)
536 }
537
538 pub fn add_preopen_build<F>(&mut self, inner: F) -> Result<(), WasiStateCreationError>
553 where
554 F: Fn(&mut PreopenDirBuilder) -> &mut PreopenDirBuilder,
555 {
556 let mut pdb = PreopenDirBuilder::new();
557 let po_dir = inner(&mut pdb).build()?;
558
559 self.preopens.push(po_dir);
560
561 Ok(())
562 }
563
564 pub fn preopen_vfs_dirs<I>(&mut self, po_dirs: I) -> Result<&mut Self, WasiStateCreationError>
567 where
568 I: IntoIterator<Item = String>,
569 {
570 for po_dir in po_dirs {
571 self.vfs_preopens.push(po_dir);
572 }
573
574 Ok(self)
575 }
576
577 pub fn map_dir<P>(mut self, alias: &str, po_dir: P) -> Result<Self, WasiStateCreationError>
579 where
580 P: AsRef<Path>,
581 {
582 self.add_map_dir(alias, po_dir)?;
583 Ok(self)
584 }
585
586 pub fn add_map_dir<P>(&mut self, alias: &str, po_dir: P) -> Result<(), WasiStateCreationError>
588 where
589 P: AsRef<Path>,
590 {
591 let mut pdb = PreopenDirBuilder::new();
592 let path = po_dir.as_ref();
593 pdb.directory(path)
594 .alias(alias)
595 .read(true)
596 .write(true)
597 .create(true);
598 let preopen = pdb.build()?;
599
600 self.preopens.push(preopen);
601
602 Ok(())
603 }
604
605 pub fn map_dirs<I, P>(mut self, mapped_dirs: I) -> Result<Self, WasiStateCreationError>
607 where
608 I: IntoIterator<Item = (String, P)>,
609 P: AsRef<Path>,
610 {
611 for (alias, dir) in mapped_dirs {
612 self.add_map_dir(&alias, dir)?;
613 }
614
615 Ok(self)
616 }
617
618 #[cfg(feature = "journal")]
624 pub fn add_read_only_journal(&mut self, journal: Arc<DynReadableJournal>) {
625 self.read_only_journals.push(journal);
626 }
627
628 #[cfg(feature = "journal")]
638 pub fn add_writable_journal(&mut self, journal: Arc<DynJournal>) {
639 self.writable_journals.push(journal);
640 }
641
642 pub fn get_current_dir(&mut self) -> Option<PathBuf> {
643 self.current_dir.clone()
644 }
645
646 pub fn set_current_dir(&mut self, dir: impl Into<PathBuf>) {
647 self.current_dir = Some(dir.into());
648 }
649
650 pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
651 self.set_current_dir(dir);
652 self
653 }
654
655 pub fn stdout(mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) -> Self {
658 self.stdout = Some(new_file);
659
660 self
661 }
662
663 pub fn set_stdout(&mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) {
666 self.stdout = Some(new_file);
667 }
668
669 pub fn stderr(mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) -> Self {
672 self.set_stderr(new_file);
673 self
674 }
675
676 pub fn set_stderr(&mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) {
679 self.stderr = Some(new_file);
680 }
681
682 pub fn stdin(mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) -> Self {
685 self.stdin = Some(new_file);
686
687 self
688 }
689
690 pub fn set_stdin(&mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) {
693 self.stdin = Some(new_file);
694 }
695
696 pub fn fs(mut self, fs: impl Into<Arc<dyn virtual_fs::FileSystem + Send + Sync>>) -> Self {
700 self.set_fs(fs);
701 self
702 }
703
704 pub fn set_fs(&mut self, fs: impl Into<Arc<dyn virtual_fs::FileSystem + Send + Sync>>) {
705 self.fs = Some(WasiFsRoot::Backing(fs.into()));
706 }
707
708 pub(crate) fn set_fs_root(&mut self, fs: WasiFsRoot) {
709 self.fs = Some(fs);
710 }
711
712 pub fn sandbox_fs(mut self, fs: TmpFileSystem) -> Self {
716 self.fs = Some(WasiFsRoot::Sandbox(fs));
717 self
718 }
719
720 pub fn setup_fs(mut self, setup_fs_fn: SetupFsFn) -> Self {
723 self.setup_fs_fn = Some(setup_fs_fn);
724
725 self
726 }
727
728 pub fn engine(mut self, engine: Engine) -> Self {
731 self.set_engine(engine);
732 self
733 }
734
735 pub fn set_engine(&mut self, engine: Engine) {
736 self.engine = Some(engine);
737 }
738
739 pub fn runtime(mut self, runtime: Arc<dyn Runtime + Send + Sync>) -> Self {
742 self.set_runtime(runtime);
743 self
744 }
745
746 pub fn set_runtime(&mut self, runtime: Arc<dyn Runtime + Send + Sync>) {
747 self.runtime = Some(runtime);
748 }
749
750 pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
751 self.set_capabilities(capabilities);
752 self
753 }
754
755 pub fn capabilities_mut(&mut self) -> &mut Capabilities {
756 &mut self.capabilites
757 }
758
759 pub fn set_capabilities(&mut self, capabilities: Capabilities) {
760 self.capabilites = capabilities;
761 }
762
763 #[cfg(feature = "journal")]
764 pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) {
765 self.snapshot_on.push(on);
766 }
767
768 #[cfg(feature = "journal")]
769 pub fn with_snapshot_interval(&mut self, interval: std::time::Duration) {
770 self.snapshot_interval.replace(interval);
771 }
772
773 #[cfg(feature = "journal")]
774 pub fn with_stop_running_after_snapshot(&mut self, stop_running: bool) {
775 self.stop_running_after_snapshot = stop_running;
776 }
777
778 pub fn with_skip_stdio_during_bootstrap(&mut self, skip: bool) {
779 self.skip_stdio_during_bootstrap = skip;
780 }
781
782 pub fn build_init(mut self) -> Result<WasiEnvInit, WasiStateCreationError> {
791 for arg in self.args.iter() {
792 for b in arg.as_bytes().iter() {
793 if *b == 0 {
794 return Err(WasiStateCreationError::ArgumentContainsNulByte(arg.clone()));
795 }
796 }
797 }
798
799 enum InvalidCharacter {
800 Nul,
801 Equal,
802 }
803
804 for (env_key, env_value) in self.envs.iter() {
805 match env_key.as_bytes().iter().find_map(|&ch| {
806 if ch == 0 {
807 Some(InvalidCharacter::Nul)
808 } else if ch == b'=' {
809 Some(InvalidCharacter::Equal)
810 } else {
811 None
812 }
813 }) {
814 Some(InvalidCharacter::Nul) => {
815 return Err(WasiStateCreationError::EnvironmentVariableFormatError(
816 format!("found nul byte in env var key \"{env_key}\" (key=value)"),
817 ));
818 }
819
820 Some(InvalidCharacter::Equal) => {
821 return Err(WasiStateCreationError::EnvironmentVariableFormatError(
822 format!("found equal sign in env var key \"{env_key}\" (key=value)"),
823 ));
824 }
825
826 None => (),
827 }
828
829 if env_value.contains(&0) {
830 return Err(WasiStateCreationError::EnvironmentVariableFormatError(
831 format!(
832 "found nul byte in env var value \"{}\" (key=value)",
833 String::from_utf8_lossy(env_value),
834 ),
835 ));
836 }
837 }
838
839 let stdin: Box<dyn VirtualFile + Send + Sync + 'static> = self
841 .stdin
842 .take()
843 .unwrap_or_else(|| Box::new(ArcFile::new(Box::<super::Stdin>::default())));
844
845 let fs_backing = self
846 .fs
847 .take()
848 .unwrap_or_else(|| WasiFsRoot::Sandbox(TmpFileSystem::new()));
849
850 if let Some(dir) = &self.current_dir {
851 match fs_backing.read_dir(dir) {
852 Ok(_) => {
853 }
855 Err(FsError::EntryNotFound) => {
856 fs_backing.create_dir(dir).map_err(|err| {
857 WasiStateCreationError::WasiFsSetupError(format!(
858 "Could not create specified current directory at '{}': {err}",
859 dir.display()
860 ))
861 })?;
862 }
863 Err(err) => {
864 return Err(WasiStateCreationError::WasiFsSetupError(format!(
865 "Could not check specified current directory at '{}': {err}",
866 dir.display()
867 )));
868 }
869 }
870 }
871
872 let inodes = crate::state::WasiInodes::new();
874 let wasi_fs = {
875 let mut wasi_fs =
877 WasiFs::new_with_preopen(&inodes, &self.preopens, &self.vfs_preopens, fs_backing)
878 .map_err(WasiStateCreationError::WasiFsCreationError)?;
879
880 wasi_fs
882 .swap_file(__WASI_STDIN_FILENO, stdin)
883 .map_err(WasiStateCreationError::FileSystemError)?;
884
885 if let Some(stdout_override) = self.stdout.take() {
886 wasi_fs
887 .swap_file(__WASI_STDOUT_FILENO, stdout_override)
888 .map_err(WasiStateCreationError::FileSystemError)?;
889 }
890
891 if let Some(stderr_override) = self.stderr.take() {
892 wasi_fs
893 .swap_file(__WASI_STDERR_FILENO, stderr_override)
894 .map_err(WasiStateCreationError::FileSystemError)?;
895 }
896
897 if let Some(f) = &self.setup_fs_fn {
898 f(&inodes, &mut wasi_fs).map_err(WasiStateCreationError::WasiFsSetupError)?;
899 }
900 wasi_fs
901 };
902
903 if let Some(dir) = &self.current_dir {
904 let s = dir.to_str().ok_or_else(|| {
905 WasiStateCreationError::WasiFsSetupError(format!(
906 "Specified current directory is not valid UTF-8: '{}'",
907 dir.display()
908 ))
909 })?;
910 wasi_fs.set_current_dir(s);
911 }
912
913 for id in &self.included_packages {
914 wasi_fs.has_unioned.lock().unwrap().insert(id.clone());
915 }
916
917 let state = WasiState {
918 fs: wasi_fs,
919 secret: rand::rng().random::<[u8; 32]>(),
920 inodes,
921 args: std::sync::Mutex::new(self.args.clone()),
922 preopen: self.vfs_preopens.clone(),
923 futexs: Default::default(),
924 clock_offset: Default::default(),
925 envs: std::sync::Mutex::new(conv_env_vars(self.envs)),
926 signals: std::sync::Mutex::new(self.signals.iter().map(|s| (s.sig, s.disp)).collect()),
927 };
928
929 let runtime = self.runtime.unwrap_or_else(|| {
930 #[cfg(feature = "sys-thread")]
931 {
932 #[allow(unused_mut)]
933 let mut runtime = crate::runtime::PluggableRuntime::new(Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::default()));
934 runtime.set_engine(
935 self
936 .engine
937 .as_ref()
938 .expect(
939 "Neither a runtime nor an engine was provided to WasiEnvBuilder. \
940 This is not supported because it means the module that's going to \
941 run with the resulting WasiEnv will have been loaded using a \
942 different engine than the one that will exist within the WasiEnv. \
943 Use either `set_runtime` or `set_engine` before calling `build_init`.",
944 )
945 .clone()
946 );
947 #[cfg(feature = "journal")]
948 for journal in self.read_only_journals.clone() {
949 runtime.add_read_only_journal(journal);
950 }
951 #[cfg(feature = "journal")]
952 for journal in self.writable_journals.clone() {
953 runtime.add_writable_journal(journal);
954 }
955 Arc::new(runtime)
956 }
957
958 #[cfg(not(feature = "sys-thread"))]
959 {
960 panic!("this build does not support a default runtime - specify one with WasiEnvBuilder::runtime()");
961 }
962 });
963
964 let uses = self.uses;
965 let map_commands = self.map_commands;
966
967 let bin_factory = BinFactory::new(runtime.clone());
968
969 let capabilities = self.capabilites;
970
971 let plane_config = ControlPlaneConfig {
972 max_task_count: capabilities.threading.max_threads,
973 enable_asynchronous_threading: capabilities.threading.enable_asynchronous_threading,
974 enable_exponential_cpu_backoff: capabilities.threading.enable_exponential_cpu_backoff,
975 };
976 let control_plane = WasiControlPlane::new(plane_config);
977
978 let init = WasiEnvInit {
979 state,
980 runtime,
981 webc_dependencies: uses,
982 mapped_commands: map_commands,
983 control_plane,
984 bin_factory,
985 capabilities,
986 memory_ty: None,
987 process: None,
988 thread: None,
989 #[cfg(feature = "journal")]
990 call_initialize: self.read_only_journals.is_empty()
991 && self.writable_journals.is_empty(),
992 #[cfg(not(feature = "journal"))]
993 call_initialize: true,
994 can_deep_sleep: false,
995 extra_tracing: true,
996 #[cfg(feature = "journal")]
997 snapshot_on: self.snapshot_on,
998 #[cfg(feature = "journal")]
999 stop_running_after_snapshot: self.stop_running_after_snapshot,
1000 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
1001 };
1002
1003 Ok(init)
1004 }
1005
1006 #[allow(clippy::result_large_err)]
1007 pub fn build(self) -> Result<WasiEnv, WasiRuntimeError> {
1008 let module_hash = self.module_hash.unwrap_or_else(ModuleHash::random);
1009 let init = self.build_init()?;
1010 WasiEnv::from_init(init, module_hash)
1011 }
1012
1013 #[doc(hidden)]
1018 #[allow(clippy::result_large_err)]
1019 pub fn finalize(
1020 self,
1021 store: &mut impl AsStoreMut,
1022 ) -> Result<WasiFunctionEnv, WasiRuntimeError> {
1023 let module_hash = self.module_hash.unwrap_or_else(ModuleHash::random);
1024 let init = self.build_init()?;
1025 let env = WasiEnv::from_init(init, module_hash)?;
1026 let func_env = WasiFunctionEnv::new(store, env);
1027 Ok(func_env)
1028 }
1029
1030 #[allow(clippy::result_large_err)]
1036 pub fn instantiate(
1037 self,
1038 module: Module,
1039 store: &mut impl AsStoreMut,
1040 ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> {
1041 self.instantiate_ext(module, ModuleHash::random(), store)
1042 }
1043
1044 #[allow(clippy::result_large_err)]
1045 pub fn instantiate_ext(
1046 self,
1047 module: Module,
1048 module_hash: ModuleHash,
1049 store: &mut impl AsStoreMut,
1050 ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> {
1051 let init = self.build_init()?;
1052 let call_init = init.call_initialize;
1053 let env = WasiEnv::from_init(init, module_hash)?;
1054 let memory = module
1055 .imports()
1056 .find_map(|i| match i.ty() {
1057 wasmer::ExternType::Memory(ty) => Some(*ty),
1058 _ => None,
1059 })
1060 .map(|ty| wasmer::Memory::new(store, ty))
1061 .transpose()
1062 .map_err(WasiThreadError::MemoryCreateFailed)?;
1063 Ok(env.instantiate(module, store, memory, true, call_init, None)?)
1064 }
1065}
1066
1067pub(crate) fn conv_env_vars(envs: Vec<(String, Vec<u8>)>) -> Vec<Vec<u8>> {
1068 envs.into_iter()
1069 .map(|(key, value)| {
1070 let mut env = Vec::with_capacity(key.len() + value.len() + 1);
1071 env.extend_from_slice(key.as_bytes());
1072 env.push(b'=');
1073 env.extend_from_slice(&value);
1074
1075 env
1076 })
1077 .collect()
1078}
1079
1080#[derive(Debug, Default)]
1082pub struct PreopenDirBuilder {
1083 path: Option<PathBuf>,
1084 alias: Option<String>,
1085 read: bool,
1086 write: bool,
1087 create: bool,
1088}
1089
1090#[derive(Debug, Clone, Default)]
1092pub(crate) struct PreopenedDir {
1093 pub(crate) path: PathBuf,
1094 pub(crate) alias: Option<String>,
1095 pub(crate) read: bool,
1096 pub(crate) write: bool,
1097 pub(crate) create: bool,
1098}
1099
1100impl PreopenDirBuilder {
1101 pub(crate) fn new() -> Self {
1103 PreopenDirBuilder::default()
1104 }
1105
1106 pub fn directory<FilePath>(&mut self, po_dir: FilePath) -> &mut Self
1108 where
1109 FilePath: AsRef<Path>,
1110 {
1111 let path = po_dir.as_ref();
1112 self.path = Some(path.to_path_buf());
1113
1114 self
1115 }
1116
1117 pub fn alias(&mut self, alias: &str) -> &mut Self {
1119 let alias = alias.trim_start_matches('/');
1122 self.alias = Some(alias.to_string());
1123
1124 self
1125 }
1126
1127 pub fn read(&mut self, toggle: bool) -> &mut Self {
1129 self.read = toggle;
1130
1131 self
1132 }
1133
1134 pub fn write(&mut self, toggle: bool) -> &mut Self {
1136 self.write = toggle;
1137
1138 self
1139 }
1140
1141 pub fn create(&mut self, toggle: bool) -> &mut Self {
1145 self.create = toggle;
1146 if toggle {
1147 self.write = true;
1148 }
1149
1150 self
1151 }
1152
1153 pub(crate) fn build(&self) -> Result<PreopenedDir, WasiStateCreationError> {
1154 if !(self.read || self.write || self.create) {
1156 return Err(WasiStateCreationError::PreopenedDirectoryError("Preopened directories must have at least one of read, write, create permissions set".to_string()));
1157 }
1158
1159 if self.path.is_none() {
1160 return Err(WasiStateCreationError::PreopenedDirectoryError(
1161 "Preopened directories must point to a host directory".to_string(),
1162 ));
1163 }
1164 let path = self.path.clone().unwrap();
1165
1166 if let Some(alias) = &self.alias {
1173 validate_mapped_dir_alias(alias)?;
1174 }
1175
1176 Ok(PreopenedDir {
1177 path,
1178 alias: self.alias.clone(),
1179 read: self.read,
1180 write: self.write,
1181 create: self.create,
1182 })
1183 }
1184}
1185
1186#[cfg(test)]
1187mod test {
1188 use super::*;
1189
1190 #[test]
1191 fn env_var_errors() {
1192 #[cfg(not(target_arch = "wasm32"))]
1193 let runtime = tokio::runtime::Builder::new_multi_thread()
1194 .enable_all()
1195 .build()
1196 .unwrap();
1197 #[cfg(not(target_arch = "wasm32"))]
1198 let handle = runtime.handle().clone();
1199 #[cfg(not(target_arch = "wasm32"))]
1200 let _guard = handle.enter();
1201
1202 assert!(
1204 WasiEnv::builder("test_prog")
1205 .env("HOM=E", "/home/home")
1206 .build_init()
1207 .is_err(),
1208 "equal sign in key must be invalid"
1209 );
1210
1211 assert!(
1213 WasiEnvBuilder::new("test_prog")
1214 .env("HOME\0", "/home/home")
1215 .build_init()
1216 .is_err(),
1217 "nul in key must be invalid"
1218 );
1219
1220 assert!(
1222 WasiEnvBuilder::new("test_prog")
1223 .env("HOME", "/home/home\0")
1224 .build_init()
1225 .is_err(),
1226 "nul in value must be invalid"
1227 );
1228
1229 assert!(
1231 WasiEnvBuilder::new("test_prog")
1232 .env("HOME", "/home/home=home")
1233 .engine(Engine::default())
1234 .build_init()
1235 .is_ok(),
1236 "equal sign in the value must be valid"
1237 );
1238 }
1239
1240 #[test]
1241 fn nul_character_in_args() {
1242 let output = WasiEnvBuilder::new("test_prog")
1243 .arg("--h\0elp")
1244 .build_init();
1245 let err = output.expect_err("should fail");
1246 assert!(matches!(
1247 err,
1248 WasiStateCreationError::ArgumentContainsNulByte(_)
1249 ));
1250
1251 let output = WasiEnvBuilder::new("test_prog")
1252 .args(["--help", "--wat\0"])
1253 .build_init();
1254 let err = output.expect_err("should fail");
1255 assert!(matches!(
1256 err,
1257 WasiStateCreationError::ArgumentContainsNulByte(_)
1258 ));
1259 }
1260}