wasmer_cache/
filesystem.rs

1#![cfg_attr(not(feature = "filesystem"), allow(unused))]
2use crate::cache::Cache;
3use crate::hash::Hash;
4use std::fs::{create_dir_all, File};
5use std::io::{self, Write};
6use std::path::PathBuf;
7use wasmer::{AsEngineRef, DeserializeError, Module, SerializeError};
8
9/// Representation of a directory that contains compiled wasm artifacts.
10///
11/// The `FileSystemCache` type implements the [`Cache`] trait, which allows it to be used
12/// generically when some sort of cache is required.
13///
14/// # Usage
15///
16/// ```
17/// use wasmer::{DeserializeError, SerializeError};
18/// use wasmer_cache::{Cache, FileSystemCache, Hash};
19///
20/// # use wasmer::{Module};
21/// fn store_module(module: &Module, bytes: &[u8]) -> Result<(), SerializeError> {
22///     // Create a new file system cache.
23///     let mut fs_cache = FileSystemCache::new("some/directory/goes/here")?;
24///
25///     // Compute a key for a given WebAssembly binary
26///     let key = Hash::generate(bytes);
27///
28///     // Store a module into the cache given a key
29///     fs_cache.store(key, module)?;
30///
31///     Ok(())
32/// }
33/// ```
34#[derive(Debug, Clone)]
35pub struct FileSystemCache {
36    path: PathBuf,
37    ext: Option<String>,
38}
39
40#[cfg(feature = "filesystem")]
41impl FileSystemCache {
42    /// Construct a new `FileSystemCache` around the specified directory.
43    pub fn new<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
44        let path: PathBuf = path.into();
45        if path.exists() {
46            let metadata = path.metadata()?;
47            if metadata.is_dir() {
48                if !metadata.permissions().readonly() {
49                    Ok(Self { path, ext: None })
50                } else {
51                    // This directory is readonly.
52                    Err(io::Error::new(
53                        io::ErrorKind::PermissionDenied,
54                        format!("the supplied path is readonly: {}", path.display()),
55                    ))
56                }
57            } else {
58                // This path points to a file.
59                Err(io::Error::new(
60                    io::ErrorKind::PermissionDenied,
61                    format!(
62                        "the supplied path already points to a file: {}",
63                        path.display()
64                    ),
65                ))
66            }
67        } else {
68            // Create the directory and any parent directories if they don't yet exist.
69            let res = create_dir_all(&path);
70            if res.is_err() {
71                Err(io::Error::new(
72                    io::ErrorKind::Other,
73                    format!("failed to create cache directory: {}", path.display()),
74                ))
75            } else {
76                Ok(Self { path, ext: None })
77            }
78        }
79    }
80
81    /// Set the extension for this cached file.
82    ///
83    /// This is needed for loading native files from Windows, as otherwise
84    /// loading the library will fail (it requires a `.dll` extension)
85    pub fn set_cache_extension(&mut self, ext: Option<impl ToString>) {
86        self.ext = ext.map(|ext| ext.to_string());
87    }
88}
89
90#[cfg(feature = "filesystem")]
91impl Cache for FileSystemCache {
92    type DeserializeError = DeserializeError;
93    type SerializeError = SerializeError;
94
95    unsafe fn load(
96        &self,
97        engine: &impl AsEngineRef,
98        key: Hash,
99    ) -> Result<Module, Self::DeserializeError> {
100        let filename = if let Some(ref ext) = self.ext {
101            format!("{}.{}", key, ext)
102        } else {
103            key.to_string()
104        };
105        let path = self.path.join(filename);
106        let ret = Module::deserialize_from_file(engine, path.clone());
107        if ret.is_err() {
108            // If an error occurs while deserializing then we can not trust it anymore
109            // so delete the cache file
110            let _ = std::fs::remove_file(path);
111        }
112        ret
113    }
114
115    fn store(&mut self, key: Hash, module: &Module) -> Result<(), Self::SerializeError> {
116        let filename = if let Some(ref ext) = self.ext {
117            format!("{}.{}", key, ext)
118        } else {
119            key.to_string()
120        };
121        let path = self.path.join(filename);
122        let mut file = File::create(path)?;
123
124        let buffer = module.serialize()?;
125        file.write_all(&buffer)?;
126
127        Ok(())
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_fs_cache() {
137        let dir = tempfile::tempdir().unwrap();
138
139        let mut cache = FileSystemCache::new(dir.path()).unwrap();
140
141        let engine = wasmer::Engine::default();
142
143        let bytes = include_bytes!("../../wasix/tests/envvar.wasm");
144
145        let module = Module::from_binary(&engine, bytes).unwrap();
146        let key = Hash::generate(bytes);
147
148        cache.store(key, &module).unwrap();
149        let _restored = unsafe { cache.load(&engine, key).unwrap() };
150    }
151}