wasmer_vfs/
static_fs.rs

1use anyhow::anyhow;
2
3use std::convert::TryInto;
4use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Seek, SeekFrom, Write};
5use std::path::Path;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use crate::mem_fs::FileSystem as MemFileSystem;
10use crate::{
11    FileDescriptor, FileOpener, FileSystem, FsError, Metadata, OpenOptions, OpenOptionsConfig,
12    ReadDir, VirtualFile,
13};
14use webc::{FsEntry, FsEntryType, OwnedFsEntryFile};
15
16/// Custom file system wrapper to map requested file paths
17#[derive(Debug)]
18pub struct StaticFileSystem {
19    pub package: String,
20    pub volumes: Arc<webc::IndexMap<String, webc::Volume<'static>>>,
21    pub memory: Arc<MemFileSystem>,
22}
23
24impl StaticFileSystem {
25    pub fn init(bytes: &'static [u8], package: &str) -> Option<Self> {
26        let volumes = Arc::new(webc::WebC::parse_volumes_from_fileblock(bytes).ok()?);
27        let fs = Self {
28            package: package.to_string(),
29            volumes: volumes.clone(),
30            memory: Arc::new(MemFileSystem::default()),
31        };
32        let volume_names = fs.volumes.keys().cloned().collect::<Vec<_>>();
33        for volume_name in volume_names {
34            let directories = volumes.get(&volume_name).unwrap().list_directories();
35            for directory in directories {
36                let _ = fs.create_dir(Path::new(&directory));
37            }
38        }
39        Some(fs)
40    }
41}
42
43/// Custom file opener, returns a WebCFile
44#[derive(Debug)]
45struct WebCFileOpener {
46    pub package: String,
47    pub volumes: Arc<webc::IndexMap<String, webc::Volume<'static>>>,
48    pub memory: Arc<MemFileSystem>,
49}
50
51impl FileOpener for WebCFileOpener {
52    fn open(
53        &mut self,
54        path: &Path,
55        _conf: &OpenOptionsConfig,
56    ) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError> {
57        match get_volume_name_opt(path) {
58            Some(volume) => {
59                let file = (*self.volumes)
60                    .get(&volume)
61                    .ok_or(FsError::EntityNotFound)?
62                    .get_file_entry(&format!("{}", path.display()))
63                    .map_err(|_e| FsError::EntityNotFound)?;
64
65                Ok(Box::new(WebCFile {
66                    package: self.package.clone(),
67                    volume,
68                    volumes: self.volumes.clone(),
69                    path: path.to_path_buf(),
70                    entry: file,
71                    cursor: 0,
72                }))
73            }
74            None => {
75                for (volume, v) in self.volumes.iter() {
76                    let entry = match v.get_file_entry(&format!("{}", path.display())) {
77                        Ok(s) => s,
78                        Err(_) => continue, // error
79                    };
80
81                    return Ok(Box::new(WebCFile {
82                        package: self.package.clone(),
83                        volume: volume.clone(),
84                        volumes: self.volumes.clone(),
85                        path: path.to_path_buf(),
86                        entry,
87                        cursor: 0,
88                    }));
89                }
90                self.memory.new_open_options().open(path)
91            }
92        }
93    }
94}
95
96#[derive(Debug)]
97pub struct WebCFile {
98    pub volumes: Arc<webc::IndexMap<String, webc::Volume<'static>>>,
99    pub package: String,
100    pub volume: String,
101    pub path: PathBuf,
102    pub entry: OwnedFsEntryFile,
103    pub cursor: u64,
104}
105
106impl VirtualFile for WebCFile {
107    fn last_accessed(&self) -> u64 {
108        0
109    }
110    fn last_modified(&self) -> u64 {
111        0
112    }
113    fn created_time(&self) -> u64 {
114        0
115    }
116    fn size(&self) -> u64 {
117        self.entry.get_len()
118    }
119    fn set_len(&mut self, _new_size: u64) -> Result<(), FsError> {
120        Ok(())
121    }
122    fn unlink(&mut self) -> Result<(), FsError> {
123        Ok(())
124    }
125    fn bytes_available(&self) -> Result<usize, FsError> {
126        Ok(self.size().try_into().unwrap_or(u32::MAX as usize))
127    }
128    fn sync_to_disk(&self) -> Result<(), FsError> {
129        Ok(())
130    }
131    fn get_fd(&self) -> Option<FileDescriptor> {
132        None
133    }
134}
135
136impl Read for WebCFile {
137    fn read(&mut self, buf: &mut [u8]) -> Result<usize, IoError> {
138        let bytes = self
139            .volumes
140            .get(&self.volume)
141            .ok_or_else(|| {
142                IoError::new(
143                    IoErrorKind::NotFound,
144                    anyhow!("Unknown volume {:?}", self.volume),
145                )
146            })?
147            .get_file_bytes(&self.entry)
148            .map_err(|e| IoError::new(IoErrorKind::NotFound, e))?;
149
150        let cursor: usize = self.cursor.try_into().unwrap_or(u32::MAX as usize);
151        let _start = cursor.min(bytes.len());
152        let bytes = &bytes[cursor..];
153
154        let mut len = 0;
155        for (source, target) in bytes.iter().zip(buf.iter_mut()) {
156            *target = *source;
157            len += 1;
158        }
159
160        Ok(len)
161    }
162}
163
164// WebC file is not writable, the FileOpener will return a MemoryFile for writing instead
165// This code should never be executed (since writes are redirected to memory instead).
166impl Write for WebCFile {
167    fn write(&mut self, buf: &[u8]) -> Result<usize, IoError> {
168        Ok(buf.len())
169    }
170    fn flush(&mut self) -> Result<(), IoError> {
171        Ok(())
172    }
173}
174
175impl Seek for WebCFile {
176    fn seek(&mut self, pos: SeekFrom) -> Result<u64, IoError> {
177        let self_size = self.size();
178        match pos {
179            SeekFrom::Start(s) => {
180                self.cursor = s.min(self_size);
181            }
182            SeekFrom::End(e) => {
183                let self_size_i64 = self_size.try_into().unwrap_or(i64::MAX);
184                self.cursor = ((self_size_i64).saturating_add(e))
185                    .min(self_size_i64)
186                    .try_into()
187                    .unwrap_or(i64::MAX as u64);
188            }
189            SeekFrom::Current(c) => {
190                self.cursor = (self
191                    .cursor
192                    .saturating_add(c.try_into().unwrap_or(i64::MAX as u64)))
193                .min(self_size);
194            }
195        }
196        Ok(self.cursor)
197    }
198}
199
200fn get_volume_name_opt<P: AsRef<Path>>(path: P) -> Option<String> {
201    use std::path::Component::Normal;
202    if let Some(Normal(n)) = path.as_ref().components().next() {
203        if let Some(s) = n.to_str() {
204            if s.ends_with(':') {
205                return Some(s.replace(':', ""));
206            }
207        }
208    }
209    None
210}
211
212fn transform_into_read_dir<'a>(path: &Path, fs_entries: &[FsEntry<'a>]) -> crate::ReadDir {
213    let entries = fs_entries
214        .iter()
215        .map(|e| crate::DirEntry {
216            path: path.join(&*e.text),
217            metadata: Ok(crate::Metadata {
218                ft: translate_file_type(e.fs_type),
219                accessed: 0,
220                created: 0,
221                modified: 0,
222                len: e.get_len(),
223            }),
224        })
225        .collect();
226
227    crate::ReadDir::new(entries)
228}
229
230impl FileSystem for StaticFileSystem {
231    fn read_dir(&self, path: &Path) -> Result<ReadDir, FsError> {
232        let path = normalizes_path(path);
233        for volume in self.volumes.values() {
234            let read_dir_result = volume
235                .read_dir(&path)
236                .map(|o| transform_into_read_dir(Path::new(&path), o.as_ref()))
237                .map_err(|_| FsError::EntityNotFound);
238
239            match read_dir_result {
240                Ok(o) => {
241                    return Ok(o);
242                }
243                Err(_) => {
244                    continue;
245                }
246            }
247        }
248
249        self.memory.read_dir(Path::new(&path))
250    }
251    fn create_dir(&self, path: &Path) -> Result<(), FsError> {
252        let path = normalizes_path(path);
253        let result = self.memory.create_dir(Path::new(&path));
254        result
255    }
256    fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
257        let path = normalizes_path(path);
258        let result = self.memory.remove_dir(Path::new(&path));
259        if self
260            .volumes
261            .values()
262            .find_map(|v| v.get_file_entry(&path).ok())
263            .is_some()
264        {
265            Ok(())
266        } else {
267            result
268        }
269    }
270    fn rename(&self, from: &Path, to: &Path) -> Result<(), FsError> {
271        let from = normalizes_path(from);
272        let to = normalizes_path(to);
273        let result = self.memory.rename(Path::new(&from), Path::new(&to));
274        if self
275            .volumes
276            .values()
277            .find_map(|v| v.get_file_entry(&from).ok())
278            .is_some()
279        {
280            Ok(())
281        } else {
282            result
283        }
284    }
285    fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
286        let path = normalizes_path(path);
287        if let Some(fs_entry) = self
288            .volumes
289            .values()
290            .find_map(|v| v.get_file_entry(&path).ok())
291        {
292            Ok(Metadata {
293                ft: translate_file_type(FsEntryType::File),
294                accessed: 0,
295                created: 0,
296                modified: 0,
297                len: fs_entry.get_len(),
298            })
299        } else if let Some(_fs) = self.volumes.values().find_map(|v| v.read_dir(&path).ok()) {
300            Ok(Metadata {
301                ft: translate_file_type(FsEntryType::Dir),
302                accessed: 0,
303                created: 0,
304                modified: 0,
305                len: 0,
306            })
307        } else {
308            self.memory.metadata(Path::new(&path))
309        }
310    }
311    fn remove_file(&self, path: &Path) -> Result<(), FsError> {
312        let path = normalizes_path(path);
313        let result = self.memory.remove_file(Path::new(&path));
314        if self
315            .volumes
316            .values()
317            .find_map(|v| v.get_file_entry(&path).ok())
318            .is_some()
319        {
320            Ok(())
321        } else {
322            result
323        }
324    }
325    fn new_open_options(&self) -> OpenOptions {
326        OpenOptions::new(Box::new(WebCFileOpener {
327            package: self.package.clone(),
328            volumes: self.volumes.clone(),
329            memory: self.memory.clone(),
330        }))
331    }
332    fn symlink_metadata(&self, path: &Path) -> Result<Metadata, FsError> {
333        let path = normalizes_path(path);
334        if let Some(fs_entry) = self
335            .volumes
336            .values()
337            .find_map(|v| v.get_file_entry(&path).ok())
338        {
339            Ok(Metadata {
340                ft: translate_file_type(FsEntryType::File),
341                accessed: 0,
342                created: 0,
343                modified: 0,
344                len: fs_entry.get_len(),
345            })
346        } else if self
347            .volumes
348            .values()
349            .find_map(|v| v.read_dir(&path).ok())
350            .is_some()
351        {
352            Ok(Metadata {
353                ft: translate_file_type(FsEntryType::Dir),
354                accessed: 0,
355                created: 0,
356                modified: 0,
357                len: 0,
358            })
359        } else {
360            self.memory.symlink_metadata(Path::new(&path))
361        }
362    }
363}
364
365fn normalizes_path(path: &Path) -> String {
366    let path = format!("{}", path.display());
367    if !path.starts_with('/') {
368        format!("/{path}")
369    } else {
370        path
371    }
372}
373
374fn translate_file_type(f: FsEntryType) -> crate::FileType {
375    crate::FileType {
376        dir: f == FsEntryType::Dir,
377        file: f == FsEntryType::File,
378        symlink: false,
379        char_device: false,
380        block_device: false,
381        socket: false,
382        fifo: false,
383    }
384}