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