wasmer_wasix/bin_factory/
mod.rs

1#![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            // Find the binary (or die trying) and make the spawn type
79            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            // Execute
88            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        // We check for built in commands
112        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    // TODO: remove allow once BinFactory is refactored
123    // currently fine because a BinFactory is only used by a single process tree
124    #[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        // Return early if the path is already cached
133        {
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        // Check the cache again to avoid a race condition where the cache was populated inbetween the fast path and here
143        if let Some(data) = cache.get(&name) {
144            return data.clone().map(Executable::BinaryPackage);
145        }
146
147        // Check the filesystem for the file
148        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        // NAK
170        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    // Fast path if the file is fully available in memory.
192    // Prevents redundant copying of the file data.
193    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}