wasmer_vfs/
webc_fs.rs

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