virtual_fs/
static_fs.rs

1use anyhow::anyhow;
2use futures::future::BoxFuture;
3use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
4
5use std::convert::TryInto;
6use std::io::{self, Error as IoError, ErrorKind as IoErrorKind, SeekFrom};
7use std::path::Path;
8use std::path::PathBuf;
9use std::pin::Pin;
10use std::sync::Arc;
11use std::task::{Context, Poll};
12
13use crate::mem_fs::FileSystem as MemFileSystem;
14use crate::{
15    FileOpener, FileSystem, FsError, Metadata, OpenOptions, OpenOptionsConfig, ReadDir, VirtualFile,
16};
17use indexmap::IndexMap;
18use webc::v1::{FsEntry, FsEntryType, OwnedFsEntryFile};
19
20/// Custom file system wrapper to map requested file paths
21#[derive(Debug)]
22pub struct StaticFileSystem {
23    pub package: String,
24    pub volumes: Arc<IndexMap<String, webc::v1::Volume<'static>>>,
25    pub memory: Arc<MemFileSystem>,
26}
27
28impl StaticFileSystem {
29    pub fn init(bytes: &'static [u8], package: &str) -> Option<Self> {
30        let volumes = Arc::new(webc::v1::WebC::parse_volumes_from_fileblock(bytes).ok()?);
31        let fs = Self {
32            package: package.to_string(),
33            volumes: volumes.clone(),
34            memory: Arc::new(MemFileSystem::default()),
35        };
36        let volume_names = fs.volumes.keys().cloned().collect::<Vec<_>>();
37        for volume_name in volume_names {
38            let directories = volumes.get(&volume_name).unwrap().list_directories();
39            for directory in directories {
40                let _ = fs.create_dir(Path::new(&directory));
41            }
42        }
43        Some(fs)
44    }
45}
46
47/// Custom file opener, returns a WebCFile
48impl FileOpener for StaticFileSystem {
49    fn open(
50        &self,
51        path: &Path,
52        _conf: &OpenOptionsConfig,
53    ) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError> {
54        match get_volume_name_opt(path) {
55            Some(volume) => {
56                let file = (*self.volumes)
57                    .get(&volume)
58                    .ok_or(FsError::EntryNotFound)?
59                    .get_file_entry(path.to_string_lossy().as_ref())
60                    .map_err(|_e| FsError::EntryNotFound)?;
61
62                Ok(Box::new(WebCFile {
63                    package: self.package.clone(),
64                    volume,
65                    volumes: self.volumes.clone(),
66                    path: path.to_path_buf(),
67                    entry: file,
68                    cursor: 0,
69                }))
70            }
71            None => {
72                for (volume, v) in self.volumes.iter() {
73                    let entry = match v.get_file_entry(path.to_string_lossy().as_ref()) {
74                        Ok(s) => s,
75                        Err(_) => continue, // error
76                    };
77
78                    return Ok(Box::new(WebCFile {
79                        package: self.package.clone(),
80                        volume: volume.clone(),
81                        volumes: self.volumes.clone(),
82                        path: path.to_path_buf(),
83                        entry,
84                        cursor: 0,
85                    }));
86                }
87                self.memory.new_open_options().open(path)
88            }
89        }
90    }
91}
92
93#[derive(Debug)]
94pub struct WebCFile {
95    pub volumes: Arc<IndexMap<String, webc::v1::Volume<'static>>>,
96    pub package: String,
97    pub volume: String,
98    pub path: PathBuf,
99    pub entry: OwnedFsEntryFile,
100    pub cursor: u64,
101}
102
103#[async_trait::async_trait]
104impl VirtualFile for WebCFile {
105    fn last_accessed(&self) -> u64 {
106        0
107    }
108    fn last_modified(&self) -> u64 {
109        0
110    }
111    fn created_time(&self) -> u64 {
112        0
113    }
114    fn size(&self) -> u64 {
115        self.entry.get_len()
116    }
117    fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
118        Ok(())
119    }
120    fn unlink(&mut self) -> Result<(), FsError> {
121        Ok(())
122    }
123    fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
124        let remaining = self.entry.get_len() - self.cursor;
125        Poll::Ready(Ok(remaining as usize))
126    }
127    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
128        Poll::Ready(Ok(0))
129    }
130}
131
132impl AsyncRead for WebCFile {
133    fn poll_read(
134        self: Pin<&mut Self>,
135        _cx: &mut Context<'_>,
136        buf: &mut tokio::io::ReadBuf<'_>,
137    ) -> Poll<io::Result<()>> {
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        if bytes.len() > buf.remaining() {
155            let remaining = buf.remaining();
156            buf.put_slice(&bytes[..remaining]);
157        } else {
158            buf.put_slice(bytes);
159        }
160        Poll::Ready(Ok(()))
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 AsyncWrite for WebCFile {
167    fn poll_write(
168        self: Pin<&mut Self>,
169        _cx: &mut Context<'_>,
170        buf: &[u8],
171    ) -> Poll<io::Result<usize>> {
172        Poll::Ready(Ok(buf.len()))
173    }
174    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
175        Poll::Ready(Ok(()))
176    }
177    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
178        Poll::Ready(Ok(()))
179    }
180}
181
182impl AsyncSeek for WebCFile {
183    fn start_seek(mut self: Pin<&mut Self>, pos: io::SeekFrom) -> io::Result<()> {
184        let self_size = self.size();
185        match pos {
186            SeekFrom::Start(s) => {
187                self.cursor = s.min(self_size);
188            }
189            SeekFrom::End(e) => {
190                let self_size_i64 = self_size.try_into().unwrap_or(i64::MAX);
191                self.cursor = ((self_size_i64).saturating_add(e))
192                    .min(self_size_i64)
193                    .try_into()
194                    .unwrap_or(i64::MAX as u64);
195            }
196            SeekFrom::Current(c) => {
197                self.cursor = (self
198                    .cursor
199                    .saturating_add(c.try_into().unwrap_or(i64::MAX as u64)))
200                .min(self_size);
201            }
202        }
203        Ok(())
204    }
205    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
206        Poll::Ready(Ok(self.cursor))
207    }
208}
209
210fn get_volume_name_opt<P: AsRef<Path>>(path: P) -> Option<String> {
211    use std::path::Component::Normal;
212    if let Some(Normal(n)) = path.as_ref().components().next() {
213        if let Some(s) = n.to_str() {
214            if s.ends_with(':') {
215                return Some(s.replace(':', ""));
216            }
217        }
218    }
219    None
220}
221
222fn transform_into_read_dir(path: &Path, fs_entries: &[FsEntry<'_>]) -> crate::ReadDir {
223    let entries = fs_entries
224        .iter()
225        .map(|e| crate::DirEntry {
226            path: path.join(&*e.text),
227            metadata: Ok(crate::Metadata {
228                ft: translate_file_type(e.fs_type),
229                accessed: 0,
230                created: 0,
231                modified: 0,
232                len: e.get_len(),
233            }),
234        })
235        .collect();
236
237    crate::ReadDir::new(entries)
238}
239
240impl FileSystem for StaticFileSystem {
241    fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
242        let path = normalizes_path(path);
243        if self
244            .volumes
245            .values()
246            .find_map(|v| v.get_file_entry(&path).ok())
247            .is_some()
248        {
249            Err(FsError::InvalidInput)
250        } else {
251            self.memory.readlink(Path::new(&path))
252        }
253    }
254
255    fn read_dir(&self, path: &Path) -> Result<ReadDir, FsError> {
256        let path = normalizes_path(path);
257        for volume in self.volumes.values() {
258            let read_dir_result = volume
259                .read_dir(&path)
260                .map(|o| transform_into_read_dir(Path::new(&path), o.as_ref()))
261                .map_err(|_| FsError::EntryNotFound);
262
263            match read_dir_result {
264                Ok(o) => {
265                    return Ok(o);
266                }
267                Err(_) => {
268                    continue;
269                }
270            }
271        }
272
273        self.memory.read_dir(Path::new(&path))
274    }
275    fn create_dir(&self, path: &Path) -> Result<(), FsError> {
276        let path = normalizes_path(path);
277        self.memory.create_dir(Path::new(&path))
278    }
279    fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
280        let path = normalizes_path(path);
281        let result = self.memory.remove_dir(Path::new(&path));
282        if self
283            .volumes
284            .values()
285            .find_map(|v| v.get_file_entry(&path).ok())
286            .is_some()
287        {
288            Ok(())
289        } else {
290            result
291        }
292    }
293    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
294        Box::pin(async {
295            let from = normalizes_path(from);
296            let to = normalizes_path(to);
297            let result = self.memory.rename(Path::new(&from), Path::new(&to)).await;
298            if self
299                .volumes
300                .values()
301                .find_map(|v| v.get_file_entry(&from).ok())
302                .is_some()
303            {
304                Ok(())
305            } else {
306                result
307            }
308        })
309    }
310    fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
311        let path = normalizes_path(path);
312        if let Some(fs_entry) = self
313            .volumes
314            .values()
315            .find_map(|v| v.get_file_entry(&path).ok())
316        {
317            Ok(Metadata {
318                ft: translate_file_type(FsEntryType::File),
319                accessed: 0,
320                created: 0,
321                modified: 0,
322                len: fs_entry.get_len(),
323            })
324        } else if let Some(_fs) = self.volumes.values().find_map(|v| v.read_dir(&path).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
340            .volumes
341            .values()
342            .find_map(|v| v.get_file_entry(&path).ok())
343            .is_some()
344        {
345            Ok(())
346        } else {
347            result
348        }
349    }
350    fn new_open_options(&self) -> OpenOptions<'_> {
351        OpenOptions::new(self)
352    }
353    fn symlink_metadata(&self, path: &Path) -> Result<Metadata, FsError> {
354        let path = normalizes_path(path);
355        if let Some(fs_entry) = self
356            .volumes
357            .values()
358            .find_map(|v| v.get_file_entry(&path).ok())
359        {
360            Ok(Metadata {
361                ft: translate_file_type(FsEntryType::File),
362                accessed: 0,
363                created: 0,
364                modified: 0,
365                len: fs_entry.get_len(),
366            })
367        } else if self
368            .volumes
369            .values()
370            .find_map(|v| v.read_dir(&path).ok())
371            .is_some()
372        {
373            Ok(Metadata {
374                ft: translate_file_type(FsEntryType::Dir),
375                accessed: 0,
376                created: 0,
377                modified: 0,
378                len: 0,
379            })
380        } else {
381            self.memory.symlink_metadata(Path::new(&path))
382        }
383    }
384
385    fn mount(
386        &self,
387        _name: String,
388        _path: &Path,
389        _fs: Box<dyn FileSystem + Send + Sync>,
390    ) -> Result<(), FsError> {
391        Err(FsError::Unsupported)
392    }
393}
394
395fn normalizes_path(path: &Path) -> String {
396    let path = format!("{}", path.display());
397    if !path.starts_with('/') {
398        format!("/{path}")
399    } else {
400        path
401    }
402}
403
404fn translate_file_type(f: FsEntryType) -> crate::FileType {
405    crate::FileType {
406        dir: f == FsEntryType::Dir,
407        file: f == FsEntryType::File,
408        symlink: false,
409        char_device: false,
410        block_device: false,
411        socket: false,
412        fifo: false,
413    }
414}