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#[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#[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, };
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
164impl 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}