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: Box<dyn virtual_fs::FileSystem + Send + Sync>) -> Self {
702 self.set_fs(fs);
703 self
704 }
705
706 pub fn set_fs(&mut self, fs: Box<dyn virtual_fs::FileSystem + Send + Sync>) {
707 self.fs = Some(WasiFsRoot::Backing(Arc::new(fs)));
708 }
709
710 pub fn sandbox_fs(mut self, fs: TmpFileSystem) -> Self {
714 self.fs = Some(WasiFsRoot::Sandbox(Arc::new(fs)));
715 self
716 }
717
718 pub fn setup_fs(mut self, setup_fs_fn: SetupFsFn) -> Self {
721 self.setup_fs_fn = Some(setup_fs_fn);
722
723 self
724 }
725
726 pub fn engine(mut self, engine: Engine) -> Self {
729 self.set_engine(engine);
730 self
731 }
732
733 pub fn set_engine(&mut self, engine: Engine) {
734 self.engine = Some(engine);
735 }
736
737 pub fn runtime(mut self, runtime: Arc<dyn Runtime + Send + Sync>) -> Self {
740 self.set_runtime(runtime);
741 self
742 }
743
744 pub fn set_runtime(&mut self, runtime: Arc<dyn Runtime + Send + Sync>) {
745 self.runtime = Some(runtime);
746 }
747
748 pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
749 self.set_capabilities(capabilities);
750 self
751 }
752
753 pub fn capabilities_mut(&mut self) -> &mut Capabilities {
754 &mut self.capabilites
755 }
756
757 pub fn set_capabilities(&mut self, capabilities: Capabilities) {
758 self.capabilites = capabilities;
759 }
760
761 #[cfg(feature = "journal")]
762 pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) {
763 self.snapshot_on.push(on);
764 }
765
766 #[cfg(feature = "journal")]
767 pub fn with_snapshot_interval(&mut self, interval: std::time::Duration) {
768 self.snapshot_interval.replace(interval);
769 }
770
771 #[cfg(feature = "journal")]
772 pub fn with_stop_running_after_snapshot(&mut self, stop_running: bool) {
773 self.stop_running_after_snapshot = stop_running;
774 }
775
776 pub fn with_skip_stdio_during_bootstrap(&mut self, skip: bool) {
777 self.skip_stdio_during_bootstrap = skip;
778 }
779
780 pub fn build_init(mut self) -> Result<WasiEnvInit, WasiStateCreationError> {
789 for arg in self.args.iter() {
790 for b in arg.as_bytes().iter() {
791 if *b == 0 {
792 return Err(WasiStateCreationError::ArgumentContainsNulByte(arg.clone()));
793 }
794 }
795 }
796
797 enum InvalidCharacter {
798 Nul,
799 Equal,
800 }
801
802 for (env_key, env_value) in self.envs.iter() {
803 match env_key.as_bytes().iter().find_map(|&ch| {
804 if ch == 0 {
805 Some(InvalidCharacter::Nul)
806 } else if ch == b'=' {
807 Some(InvalidCharacter::Equal)
808 } else {
809 None
810 }
811 }) {
812 Some(InvalidCharacter::Nul) => {
813 return Err(WasiStateCreationError::EnvironmentVariableFormatError(
814 format!("found nul byte in env var key \"{env_key}\" (key=value)"),
815 ));
816 }
817
818 Some(InvalidCharacter::Equal) => {
819 return Err(WasiStateCreationError::EnvironmentVariableFormatError(
820 format!("found equal sign in env var key \"{env_key}\" (key=value)"),
821 ));
822 }
823
824 None => (),
825 }
826
827 if env_value.contains(&0) {
828 return Err(WasiStateCreationError::EnvironmentVariableFormatError(
829 format!(
830 "found nul byte in env var value \"{}\" (key=value)",
831 String::from_utf8_lossy(env_value),
832 ),
833 ));
834 }
835 }
836
837 let stdin: Box<dyn VirtualFile + Send + Sync + 'static> = self
839 .stdin
840 .take()
841 .unwrap_or_else(|| Box::new(ArcFile::new(Box::<super::Stdin>::default())));
842
843 let fs_backing = self
844 .fs
845 .take()
846 .unwrap_or_else(|| WasiFsRoot::Sandbox(Arc::new(TmpFileSystem::new())));
847
848 if let Some(dir) = &self.current_dir {
849 match fs_backing.read_dir(dir) {
850 Ok(_) => {
851 }
853 Err(FsError::EntryNotFound) => {
854 fs_backing.create_dir(dir).map_err(|err| {
855 WasiStateCreationError::WasiFsSetupError(format!(
856 "Could not create specified current directory at '{}': {err}",
857 dir.display()
858 ))
859 })?;
860 }
861 Err(err) => {
862 return Err(WasiStateCreationError::WasiFsSetupError(format!(
863 "Could check specified current directory at '{}': {err}",
864 dir.display()
865 )));
866 }
867 }
868 }
869
870 let inodes = crate::state::WasiInodes::new();
872 let wasi_fs = {
873 let mut wasi_fs =
875 WasiFs::new_with_preopen(&inodes, &self.preopens, &self.vfs_preopens, fs_backing)
876 .map_err(WasiStateCreationError::WasiFsCreationError)?;
877
878 wasi_fs
880 .swap_file(__WASI_STDIN_FILENO, stdin)
881 .map_err(WasiStateCreationError::FileSystemError)?;
882
883 if let Some(stdout_override) = self.stdout.take() {
884 wasi_fs
885 .swap_file(__WASI_STDOUT_FILENO, stdout_override)
886 .map_err(WasiStateCreationError::FileSystemError)?;
887 }
888
889 if let Some(stderr_override) = self.stderr.take() {
890 wasi_fs
891 .swap_file(__WASI_STDERR_FILENO, stderr_override)
892 .map_err(WasiStateCreationError::FileSystemError)?;
893 }
894
895 if let Some(f) = &self.setup_fs_fn {
896 f(&inodes, &mut wasi_fs).map_err(WasiStateCreationError::WasiFsSetupError)?;
897 }
898 wasi_fs
899 };
900
901 if let Some(dir) = &self.current_dir {
902 let s = dir.to_str().ok_or_else(|| {
903 WasiStateCreationError::WasiFsSetupError(format!(
904 "Specified current directory is not valid UTF-8: '{}'",
905 dir.display()
906 ))
907 })?;
908 wasi_fs.set_current_dir(s);
909 }
910
911 for id in &self.included_packages {
912 wasi_fs.has_unioned.lock().unwrap().insert(id.clone());
913 }
914
915 let state = WasiState {
916 fs: wasi_fs,
917 secret: rand::thread_rng().r#gen::<[u8; 32]>(),
918 inodes,
919 args: std::sync::Mutex::new(self.args.clone()),
920 preopen: self.vfs_preopens.clone(),
921 futexs: Default::default(),
922 clock_offset: Default::default(),
923 envs: std::sync::Mutex::new(conv_env_vars(self.envs)),
924 signals: std::sync::Mutex::new(self.signals.iter().map(|s| (s.sig, s.disp)).collect()),
925 };
926
927 let runtime = self.runtime.unwrap_or_else(|| {
928 #[cfg(feature = "sys-thread")]
929 {
930 #[allow(unused_mut)]
931 let mut runtime = crate::runtime::PluggableRuntime::new(Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::default()));
932 runtime.set_engine(
933 self
934 .engine
935 .as_ref()
936 .expect(
937 "Neither a runtime nor an engine was provided to WasiEnvBuilder. \
938 This is not supported because it means the module that's going to \
939 run with the resulting WasiEnv will have been loaded using a \
940 different engine than the one that will exist within the WasiEnv. \
941 Use either `set_runtime` or `set_engine` before calling `build_init`.",
942 )
943 .clone()
944 );
945 #[cfg(feature = "journal")]
946 for journal in self.read_only_journals.clone() {
947 runtime.add_read_only_journal(journal);
948 }
949 #[cfg(feature = "journal")]
950 for journal in self.writable_journals.clone() {
951 runtime.add_writable_journal(journal);
952 }
953 Arc::new(runtime)
954 }
955
956 #[cfg(not(feature = "sys-thread"))]
957 {
958 panic!("this build does not support a default runtime - specify one with WasiEnvBuilder::runtime()");
959 }
960 });
961
962 let uses = self.uses;
963 let map_commands = self.map_commands;
964
965 let bin_factory = BinFactory::new(runtime.clone());
966
967 let capabilities = self.capabilites;
968
969 let plane_config = ControlPlaneConfig {
970 max_task_count: capabilities.threading.max_threads,
971 enable_asynchronous_threading: capabilities.threading.enable_asynchronous_threading,
972 enable_exponential_cpu_backoff: capabilities.threading.enable_exponential_cpu_backoff,
973 };
974 let control_plane = WasiControlPlane::new(plane_config);
975
976 let init = WasiEnvInit {
977 state,
978 runtime,
979 webc_dependencies: uses,
980 mapped_commands: map_commands,
981 control_plane,
982 bin_factory,
983 capabilities,
984 memory_ty: None,
985 process: None,
986 thread: None,
987 #[cfg(feature = "journal")]
988 call_initialize: self.read_only_journals.is_empty()
989 && self.writable_journals.is_empty(),
990 #[cfg(not(feature = "journal"))]
991 call_initialize: true,
992 can_deep_sleep: false,
993 extra_tracing: true,
994 #[cfg(feature = "journal")]
995 snapshot_on: self.snapshot_on,
996 #[cfg(feature = "journal")]
997 stop_running_after_snapshot: self.stop_running_after_snapshot,
998 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
999 };
1000
1001 Ok(init)
1002 }
1003
1004 #[allow(clippy::result_large_err)]
1005 pub fn build(self) -> Result<WasiEnv, WasiRuntimeError> {
1006 let module_hash = self.module_hash.unwrap_or_else(ModuleHash::random);
1007 let init = self.build_init()?;
1008 WasiEnv::from_init(init, module_hash)
1009 }
1010
1011 #[doc(hidden)]
1016 #[allow(clippy::result_large_err)]
1017 pub fn finalize(
1018 self,
1019 store: &mut impl AsStoreMut,
1020 ) -> Result<WasiFunctionEnv, WasiRuntimeError> {
1021 let module_hash = self.module_hash.unwrap_or_else(ModuleHash::random);
1022 let init = self.build_init()?;
1023 let env = WasiEnv::from_init(init, module_hash)?;
1024 let func_env = WasiFunctionEnv::new(store, env);
1025 Ok(func_env)
1026 }
1027
1028 #[allow(clippy::result_large_err)]
1034 pub fn instantiate(
1035 self,
1036 module: Module,
1037 store: &mut impl AsStoreMut,
1038 ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> {
1039 self.instantiate_ext(module, ModuleHash::random(), store)
1040 }
1041
1042 #[allow(clippy::result_large_err)]
1043 pub fn instantiate_ext(
1044 self,
1045 module: Module,
1046 module_hash: ModuleHash,
1047 store: &mut impl AsStoreMut,
1048 ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> {
1049 let init = self.build_init()?;
1050 let call_init = init.call_initialize;
1051 let env = WasiEnv::from_init(init, module_hash)?;
1052 let memory = module
1053 .imports()
1054 .find_map(|i| match i.ty() {
1055 wasmer::ExternType::Memory(ty) => Some(*ty),
1056 _ => None,
1057 })
1058 .map(|ty| wasmer::Memory::new(store, ty))
1059 .transpose()
1060 .map_err(WasiThreadError::MemoryCreateFailed)?;
1061 Ok(env.instantiate(module, store, memory, true, call_init, None)?)
1062 }
1063}
1064
1065pub(crate) fn conv_env_vars(envs: Vec<(String, Vec<u8>)>) -> Vec<Vec<u8>> {
1066 envs.into_iter()
1067 .map(|(key, value)| {
1068 let mut env = Vec::with_capacity(key.len() + value.len() + 1);
1069 env.extend_from_slice(key.as_bytes());
1070 env.push(b'=');
1071 env.extend_from_slice(&value);
1072
1073 env
1074 })
1075 .collect()
1076}
1077
1078#[derive(Debug, Default)]
1080pub struct PreopenDirBuilder {
1081 path: Option<PathBuf>,
1082 alias: Option<String>,
1083 read: bool,
1084 write: bool,
1085 create: bool,
1086}
1087
1088#[derive(Debug, Clone, Default)]
1090pub(crate) struct PreopenedDir {
1091 pub(crate) path: PathBuf,
1092 pub(crate) alias: Option<String>,
1093 pub(crate) read: bool,
1094 pub(crate) write: bool,
1095 pub(crate) create: bool,
1096}
1097
1098impl PreopenDirBuilder {
1099 pub(crate) fn new() -> Self {
1101 PreopenDirBuilder::default()
1102 }
1103
1104 pub fn directory<FilePath>(&mut self, po_dir: FilePath) -> &mut Self
1106 where
1107 FilePath: AsRef<Path>,
1108 {
1109 let path = po_dir.as_ref();
1110 self.path = Some(path.to_path_buf());
1111
1112 self
1113 }
1114
1115 pub fn alias(&mut self, alias: &str) -> &mut Self {
1117 let alias = alias.trim_start_matches('/');
1120 self.alias = Some(alias.to_string());
1121
1122 self
1123 }
1124
1125 pub fn read(&mut self, toggle: bool) -> &mut Self {
1127 self.read = toggle;
1128
1129 self
1130 }
1131
1132 pub fn write(&mut self, toggle: bool) -> &mut Self {
1134 self.write = toggle;
1135
1136 self
1137 }
1138
1139 pub fn create(&mut self, toggle: bool) -> &mut Self {
1143 self.create = toggle;
1144 if toggle {
1145 self.write = true;
1146 }
1147
1148 self
1149 }
1150
1151 pub(crate) fn build(&self) -> Result<PreopenedDir, WasiStateCreationError> {
1152 if !(self.read || self.write || self.create) {
1154 return Err(WasiStateCreationError::PreopenedDirectoryError("Preopened directories must have at least one of read, write, create permissions set".to_string()));
1155 }
1156
1157 if self.path.is_none() {
1158 return Err(WasiStateCreationError::PreopenedDirectoryError(
1159 "Preopened directories must point to a host directory".to_string(),
1160 ));
1161 }
1162 let path = self.path.clone().unwrap();
1163
1164 if let Some(alias) = &self.alias {
1171 validate_mapped_dir_alias(alias)?;
1172 }
1173
1174 Ok(PreopenedDir {
1175 path,
1176 alias: self.alias.clone(),
1177 read: self.read,
1178 write: self.write,
1179 create: self.create,
1180 })
1181 }
1182}
1183
1184#[cfg(test)]
1185mod test {
1186 use super::*;
1187
1188 #[test]
1189 fn env_var_errors() {
1190 #[cfg(not(target_arch = "wasm32"))]
1191 let runtime = tokio::runtime::Builder::new_multi_thread()
1192 .enable_all()
1193 .build()
1194 .unwrap();
1195 #[cfg(not(target_arch = "wasm32"))]
1196 let handle = runtime.handle().clone();
1197 #[cfg(not(target_arch = "wasm32"))]
1198 let _guard = handle.enter();
1199
1200 assert!(
1202 WasiEnv::builder("test_prog")
1203 .env("HOM=E", "/home/home")
1204 .build_init()
1205 .is_err(),
1206 "equal sign in key must be invalid"
1207 );
1208
1209 assert!(
1211 WasiEnvBuilder::new("test_prog")
1212 .env("HOME\0", "/home/home")
1213 .build_init()
1214 .is_err(),
1215 "nul in key must be invalid"
1216 );
1217
1218 assert!(
1220 WasiEnvBuilder::new("test_prog")
1221 .env("HOME", "/home/home\0")
1222 .build_init()
1223 .is_err(),
1224 "nul in value must be invalid"
1225 );
1226
1227 assert!(
1229 WasiEnvBuilder::new("test_prog")
1230 .env("HOME", "/home/home=home")
1231 .engine(Engine::default())
1232 .build_init()
1233 .is_ok(),
1234 "equal sign in the value must be valid"
1235 );
1236 }
1237
1238 #[test]
1239 fn nul_character_in_args() {
1240 let output = WasiEnvBuilder::new("test_prog")
1241 .arg("--h\0elp")
1242 .build_init();
1243 let err = output.expect_err("should fail");
1244 assert!(matches!(
1245 err,
1246 WasiStateCreationError::ArgumentContainsNulByte(_)
1247 ));
1248
1249 let output = WasiEnvBuilder::new("test_prog")
1250 .args(["--help", "--wat\0"])
1251 .build_init();
1252 let err = output.expect_err("should fail");
1253 assert!(matches!(
1254 err,
1255 WasiStateCreationError::ArgumentContainsNulByte(_)
1256 ));
1257 }
1258}