wasmer_wasix/bin_factory/
mod.rs1#![allow(clippy::result_large_err)]
2use std::{
3 collections::HashMap,
4 future::Future,
5 ops::Deref,
6 path::Path,
7 pin::Pin,
8 sync::{Arc, RwLock},
9};
10
11use anyhow::Context;
12use shared_buffer::OwnedBuffer;
13use virtual_fs::{AsyncReadExt, FileSystem};
14use wasmer::FunctionEnvMut;
15use wasmer_package::utils::from_bytes;
16
17mod binary_package;
18mod exec;
19
20pub use self::{
21 binary_package::*,
22 exec::{
23 package_command_by_name, run_exec, spawn_exec, spawn_exec_module, spawn_exec_wasm,
24 spawn_load_module, spawn_union_fs,
25 },
26};
27use crate::{
28 Runtime, SpawnError, WasiEnv,
29 os::{command::Commands, task::TaskJoinHandle},
30 runtime::module_cache::HashedModuleData,
31};
32
33#[derive(Debug, Clone)]
34pub struct BinFactory {
35 pub(crate) commands: Commands,
36 runtime: Arc<dyn Runtime + Send + Sync + 'static>,
37 pub(crate) local: Arc<RwLock<HashMap<String, Option<Arc<BinaryPackage>>>>>,
38}
39
40impl BinFactory {
41 pub fn new(runtime: Arc<dyn Runtime + Send + Sync + 'static>) -> BinFactory {
42 BinFactory {
43 commands: Commands::new_with_builtins(runtime.clone()),
44 runtime,
45 local: Arc::new(RwLock::new(HashMap::new())),
46 }
47 }
48
49 pub fn runtime(&self) -> &(dyn Runtime + Send + Sync) {
50 self.runtime.deref()
51 }
52
53 pub fn set_binary(&self, name: &str, binary: &Arc<BinaryPackage>) {
54 let mut cache = self.local.write().unwrap();
55 cache.insert(name.to_string(), Some(binary.clone()));
56 }
57
58 #[allow(clippy::await_holding_lock)]
59 pub async fn get_binary(
60 &self,
61 name: &str,
62 fs: Option<&dyn FileSystem>,
63 ) -> Option<Arc<BinaryPackage>> {
64 self.get_executable(name, fs)
65 .await
66 .and_then(|executable| match executable {
67 Executable::Wasm(_) => None,
68 Executable::BinaryPackage(pkg) => Some(pkg),
69 })
70 }
71
72 pub fn spawn<'a>(
73 &'a self,
74 name: String,
75 env: WasiEnv,
76 ) -> Pin<Box<dyn Future<Output = Result<TaskJoinHandle, SpawnError>> + 'a>> {
77 Box::pin(async move {
78 let res = self
80 .get_executable(name.as_str(), Some(env.fs_root()))
81 .await
82 .ok_or_else(|| SpawnError::BinaryNotFound {
83 binary: name.clone(),
84 });
85 let executable = res?;
86
87 match executable {
89 Executable::Wasm(bytes) => {
90 let data = HashedModuleData::new(bytes.clone());
91 spawn_exec_wasm(data, name.as_str(), env, &self.runtime).await
92 }
93 Executable::BinaryPackage(pkg) => {
94 {
95 let cmd = package_command_by_name(&pkg, name.as_str())?;
96 env.prepare_spawn(cmd);
97 }
98
99 spawn_exec(pkg.as_ref().clone(), name.as_str(), env, &self.runtime).await
100 }
101 }
102 })
103 }
104
105 pub fn try_built_in(
106 &self,
107 name: String,
108 parent_ctx: Option<&FunctionEnvMut<'_, WasiEnv>>,
109 builder: &mut Option<WasiEnv>,
110 ) -> Result<TaskJoinHandle, SpawnError> {
111 if let Some(parent_ctx) = parent_ctx {
113 if self.commands.exists(name.as_str()) {
114 return self.commands.exec(parent_ctx, name.as_str(), builder);
115 }
116 } else if self.commands.exists(name.as_str()) {
117 tracing::warn!("builtin command without a parent ctx - {}", name);
118 }
119 Err(SpawnError::BinaryNotFound { binary: name })
120 }
121
122 #[allow(clippy::await_holding_lock)]
125 pub async fn get_executable(
126 &self,
127 name: &str,
128 fs: Option<&dyn FileSystem>,
129 ) -> Option<Executable> {
130 let name = name.to_string();
131
132 {
134 let cache = self.local.read().unwrap();
135 if let Some(data) = cache.get(&name) {
136 data.clone().map(Executable::BinaryPackage);
137 }
138 }
139
140 let mut cache = self.local.write().unwrap();
141
142 if let Some(data) = cache.get(&name) {
144 return data.clone().map(Executable::BinaryPackage);
145 }
146
147 if name.starts_with('/') {
149 if let Some(fs) = fs {
150 match load_executable_from_filesystem(fs, name.as_ref(), self.runtime()).await {
151 Ok(executable) => {
152 if let Executable::BinaryPackage(pkg) = &executable {
153 cache.insert(name, Some(pkg.clone()));
154 }
155
156 return Some(executable);
157 }
158 Err(e) => {
159 tracing::warn!(
160 path = name,
161 error = &*e,
162 "Unable to load the package from disk"
163 );
164 }
165 }
166 }
167 }
168
169 cache.insert(name, None);
171 None
172 }
173}
174
175pub enum Executable {
176 Wasm(OwnedBuffer),
177 BinaryPackage(Arc<BinaryPackage>),
178}
179
180async fn load_executable_from_filesystem(
181 fs: &dyn FileSystem,
182 path: &Path,
183 rt: &(dyn Runtime + Send + Sync),
184) -> Result<Executable, anyhow::Error> {
185 let mut f = fs
186 .new_open_options()
187 .read(true)
188 .open(path)
189 .context("Unable to open the file")?;
190
191 if let Some(buf) = f.as_owned_buffer() {
194 if wasmer_package::utils::is_container(buf.as_slice()) {
195 let bytes = buf.clone().into_bytes();
196 if let Ok(container) = from_bytes(bytes.clone()) {
197 let pkg = BinaryPackage::from_webc(&container, rt)
198 .await
199 .context("Unable to load the package")?;
200
201 return Ok(Executable::BinaryPackage(Arc::new(pkg)));
202 }
203 }
204
205 Ok(Executable::Wasm(buf))
206 } else {
207 let mut data = Vec::with_capacity(f.size() as usize);
208 f.read_to_end(&mut data).await.context("Read failed")?;
209
210 let bytes: bytes::Bytes = data.into();
211
212 if let Ok(container) = from_bytes(bytes.clone()) {
213 let pkg = BinaryPackage::from_webc(&container, rt)
214 .await
215 .context("Unable to load the package")?;
216
217 Ok(Executable::BinaryPackage(Arc::new(pkg)))
218 } else {
219 Ok(Executable::Wasm(OwnedBuffer::from_bytes(bytes)))
220 }
221 }
222}