gvfs/
vfs.rs

1//! A virtual file system layer that lets us define multiple
2//! "file systems" with various backing stores, then merge them
3//! together.
4//!
5//! Basically a re-implementation of the C library `PhysFS`.  The
6//! `vfs` crate does something similar but has a couple design
7//! decisions that make it kind of incompatible with this use case:
8//! the relevant trait for it has generic methods so we can't use it
9//! as a trait object, and its path abstraction is not the most
10//! convenient.
11
12use std::cell::RefCell;
13use std::fmt::{self, Debug};
14use std::fs;
15use std::io::{self, Read, Seek, Write};
16use std::path::{self, Path, PathBuf};
17
18use zip;
19
20use crate::{Error, Result};
21
22/// What it says on the tin
23fn convenient_path_to_str(path: &path::Path) -> Result<&str> {
24    path.to_str().ok_or_else(|| {
25        let errmessage = format!("Invalid path format for resource: {:?}", path);
26        Error::VfsError(errmessage)
27    })
28}
29
30/// Our basic trait for files.  All different types of filesystem
31/// must provide a thing that implements this trait.
32pub trait VFile: Read + Write + Seek + Debug {}
33
34impl<T> VFile for T where T: Read + Write + Seek + Debug {}
35
36/// Options for opening files
37///
38/// We need our own version of this structure because the one in
39/// `std` annoyingly doesn't let you read the read/write/create/etc
40/// state out of it.
41#[must_use]
42#[derive(Debug, Default, Copy, Clone, PartialEq)]
43pub struct OpenOptions {
44    read: bool,
45    write: bool,
46    create: bool,
47    append: bool,
48    truncate: bool,
49}
50
51impl OpenOptions {
52    /// Create a new instance with defaults.
53    pub fn new() -> OpenOptions {
54        Default::default()
55    }
56
57    /// Open for reading
58    pub fn read(mut self, read: bool) -> OpenOptions {
59        self.read = read;
60        self
61    }
62
63    /// Open for writing
64    pub fn write(mut self, write: bool) -> OpenOptions {
65        self.write = write;
66        self
67    }
68
69    /// Create the file if it does not exist yet
70    pub fn create(mut self, create: bool) -> OpenOptions {
71        self.create = create;
72        self
73    }
74
75    /// Append at the end of the file
76    pub fn append(mut self, append: bool) -> OpenOptions {
77        self.append = append;
78        self
79    }
80
81    /// Truncate the file to 0 bytes after opening
82    pub fn truncate(mut self, truncate: bool) -> OpenOptions {
83        self.truncate = truncate;
84        self
85    }
86
87    fn to_fs_openoptions(self) -> fs::OpenOptions {
88        let mut opt = fs::OpenOptions::new();
89        let _ = opt
90            .read(self.read)
91            .write(self.write)
92            .create(self.create)
93            .append(self.append)
94            .truncate(self.truncate)
95            .create(self.create);
96        opt
97    }
98}
99
100/// A trait for a virtual file system, such as a zip file or a point
101/// in the real file system.
102pub trait VFS: Debug {
103    /// Open the file at this path with the given options
104    fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>>;
105    /// Open the file at this path for reading
106    fn open(&self, path: &Path) -> Result<Box<dyn VFile>> {
107        self.open_options(path, OpenOptions::new().read(true))
108    }
109    /// Open the file at this path for writing, truncating it if it exists already
110    fn create(&self, path: &Path) -> Result<Box<dyn VFile>> {
111        self.open_options(
112            path,
113            OpenOptions::new().write(true).create(true).truncate(true),
114        )
115    }
116    /// Open the file at this path for appending, creating it if necessary
117    fn append(&self, path: &Path) -> Result<Box<dyn VFile>> {
118        self.open_options(
119            path,
120            OpenOptions::new().write(true).create(true).append(true),
121        )
122    }
123    /// Create a directory at the location by this path
124    fn mkdir(&self, path: &Path) -> Result;
125
126    /// Remove a file or an empty directory.
127    fn rm(&self, path: &Path) -> Result;
128
129    /// Remove a file or directory and all its contents
130    fn rmrf(&self, path: &Path) -> Result;
131
132    /// Check if the file exists
133    fn exists(&self, path: &Path) -> bool;
134
135    /// Get the file's metadata
136    fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>>;
137
138    /// Retrieve all file and directory entries in the given directory.
139    fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>>;
140
141    /// Retrieve the actual location of the VFS root, if available.
142    fn to_path_buf(&self) -> Option<PathBuf>;
143}
144
145/// The metadata we can read from a file.
146pub trait VMetadata {
147    /// Returns whether or not it is a directory.
148    /// Note that zip files don't actually have directories, awkwardly,
149    /// just files with very long names.
150    fn is_dir(&self) -> bool;
151    /// Returns whether or not it is a file.
152    fn is_file(&self) -> bool;
153    /// Returns the length of the thing.  If it is a directory,
154    /// the result of this is undefined/platform dependent.
155    fn len(&self) -> u64;
156}
157
158/// A VFS that points to a directory and uses it as the root of its
159/// file hierarchy.
160///
161/// It IS allowed to have symlinks in it!  They're surprisingly
162/// difficult to get rid of.
163#[derive(Clone)]
164pub struct PhysicalFS {
165    root: PathBuf,
166    readonly: bool,
167}
168
169/// Metadata for a physical file.
170#[derive(Debug, Clone)]
171pub struct PhysicalMetadata(fs::Metadata);
172
173impl VMetadata for PhysicalMetadata {
174    fn is_dir(&self) -> bool {
175        self.0.is_dir()
176    }
177    fn is_file(&self) -> bool {
178        self.0.is_file()
179    }
180    fn len(&self) -> u64 {
181        self.0.len()
182    }
183}
184
185/// This takes an absolute path and returns either a sanitized relative
186/// version of it, or None if there's something bad in it.
187///
188/// What we want is an absolute path with no `..`'s in it, so, something
189/// like "/foo" or "/foo/bar.txt".  This means a path with components
190/// starting with a `RootDir`, and zero or more `Normal` components.
191///
192/// We gotta return a new path because there's apparently no real good way
193/// to turn an absolute path into a relative path with the same
194/// components (other than the first), and pushing an absolute `Path`
195/// onto a `PathBuf` just completely nukes its existing contents.
196fn sanitize_path(path: &path::Path) -> Option<PathBuf> {
197    let mut c = path.components();
198    match c.next() {
199        Some(path::Component::RootDir) => (),
200        _ => return None,
201    }
202
203    fn is_normal_component(comp: path::Component) -> Option<&str> {
204        match comp {
205            path::Component::Normal(s) => s.to_str(),
206            _ => None,
207        }
208    }
209
210    // This could be done more cleverly but meh
211    let mut accm = PathBuf::new();
212    for component in c {
213        if let Some(s) = is_normal_component(component) {
214            accm.push(s)
215        } else {
216            return None;
217        }
218    }
219    Some(accm)
220}
221
222impl PhysicalFS {
223    /// Create new PhysicalFS
224    pub fn new(root: &Path, readonly: bool) -> Self {
225        PhysicalFS {
226            root: root.into(),
227            readonly,
228        }
229    }
230
231    /// Takes a given absolute `&Path` and returns
232    /// a new PathBuf containing the canonical
233    /// absolute path you get when appending it
234    /// to this filesystem's root.
235    ///
236    /// So if this FS's root is `/home/icefox/foo` then
237    /// calling `fs.to_absolute("/bar")` should return
238    /// `/home/icefox/foo/bar`
239    fn to_absolute(&self, p: &Path) -> Result<PathBuf> {
240        if let Some(safe_path) = sanitize_path(p) {
241            let mut root_path = self.root.clone();
242            root_path.push(safe_path);
243            Ok(root_path)
244        } else {
245            let msg = format!(
246                "Path {:?} is not valid: must be an absolute path with no \
247                 references to parent directories",
248                p
249            );
250            Err(Error::VfsError(msg))
251        }
252    }
253
254    /// Creates the PhysicalFS's root directory if necessary.
255    /// Idempotent.
256    ///
257    /// This way we can avoid creating the directory
258    /// until it's actually used, though it IS a tiny bit of a
259    /// performance malus.
260    fn create_root(&self) -> Result {
261        if !self.root.exists() {
262            fs::create_dir_all(&self.root).map_err(Error::from)
263        } else {
264            Ok(())
265        }
266    }
267}
268
269impl Debug for PhysicalFS {
270    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271        write!(f, "<PhysicalFS root: {}>", self.root.display())
272    }
273}
274
275impl VFS for PhysicalFS {
276    /// Open the file at this path with the given options
277    fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>> {
278        if self.readonly
279            && (open_options.write
280                || open_options.create
281                || open_options.append
282                || open_options.truncate)
283        {
284            let msg = format!(
285                "Cannot alter file {:?} in root {:?}, filesystem read-only",
286                path, self
287            );
288            return Err(Error::VfsError(msg));
289        }
290        self.create_root()?;
291        let p = self.to_absolute(path)?;
292        open_options
293            .to_fs_openoptions()
294            .open(p)
295            .map(|x| Box::new(x) as Box<dyn VFile>)
296            .map_err(Error::from)
297    }
298
299    /// Create a directory at the location by this path
300    fn mkdir(&self, path: &Path) -> Result {
301        if self.readonly {
302            return Err(Error::VfsError(
303                "Tried to make directory {} but FS is \
304                 read-only"
305                    .to_string(),
306            ));
307        }
308        self.create_root()?;
309        let p = self.to_absolute(path)?;
310        //println!("Creating {:?}", p);
311        fs::DirBuilder::new()
312            .recursive(true)
313            .create(p)
314            .map_err(Error::from)
315    }
316
317    /// Remove a file
318    fn rm(&self, path: &Path) -> Result {
319        if self.readonly {
320            return Err(Error::VfsError(
321                "Tried to remove file {} but FS is read-only".to_string(),
322            ));
323        }
324
325        self.create_root()?;
326        let p = self.to_absolute(path)?;
327        if p.is_dir() {
328            fs::remove_dir(p).map_err(Error::from)
329        } else {
330            fs::remove_file(p).map_err(Error::from)
331        }
332    }
333
334    /// Remove a file or directory and all its contents
335    fn rmrf(&self, path: &Path) -> Result {
336        if self.readonly {
337            return Err(Error::VfsError(
338                "Tried to remove file/dir {} but FS is \
339                 read-only"
340                    .to_string(),
341            ));
342        }
343
344        self.create_root()?;
345        let p = self.to_absolute(path)?;
346        if p.is_dir() {
347            fs::remove_dir_all(p).map_err(Error::from)
348        } else {
349            fs::remove_file(p).map_err(Error::from)
350        }
351    }
352
353    /// Check if the file exists
354    fn exists(&self, path: &Path) -> bool {
355        match self.to_absolute(path) {
356            Ok(p) => p.exists(),
357            _ => false,
358        }
359    }
360
361    /// Get the file's metadata
362    fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>> {
363        self.create_root()?;
364        let p = self.to_absolute(path)?;
365        p.metadata()
366            .map(|m| Box::new(PhysicalMetadata(m)) as Box<dyn VMetadata>)
367            .map_err(Error::from)
368    }
369
370    /// Retrieve the path entries in this path
371    fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
372        self.create_root()?;
373        let p = self.to_absolute(path)?;
374        // This is inconvenient because path() returns the full absolute
375        // path of the bloody file, which is NOT what we want!
376        // But if we use file_name() to just get the name then it is ALSO not what we want!
377        // what we WANT is the full absolute file path, *relative to the resources dir*.
378        // So that we can do read_dir("/foobar/"), and for each file, open it and query
379        // it and such by name.
380        // So we build the paths ourself.
381        let direntry_to_path = |entry: &fs::DirEntry| -> Result<PathBuf> {
382            let fname = entry
383                .file_name()
384                .into_string()
385                .expect("Non-unicode char in file path?  Should never happen, I hope!");
386            let mut pathbuf = PathBuf::from(path);
387            pathbuf.push(fname);
388            Ok(pathbuf)
389        };
390        let itr = fs::read_dir(p)?
391            .map(|entry| direntry_to_path(&entry?))
392            .collect::<Vec<_>>()
393            .into_iter();
394        Ok(Box::new(itr))
395    }
396
397    /// Retrieve the actual location of the VFS root, if available.
398    fn to_path_buf(&self) -> Option<PathBuf> {
399        Some(self.root.clone())
400    }
401}
402
403/// A structure that joins several VFS's together in order.
404///
405/// So if a file isn't found in one FS it will search through them
406/// looking for it and return the
407#[derive(Debug)]
408pub struct OverlayFS {
409    roots: Vec<Box<dyn VFS>>,
410}
411
412impl OverlayFS {
413    /// New OverlayFS containing zero filesystems.
414    pub fn new() -> Self {
415        Self { roots: Vec::new() }
416    }
417
418    /// Adds a new VFS to the end of the list.
419    pub fn push(&mut self, fs: Box<dyn VFS>) {
420        self.roots.push(fs);
421    }
422
423    /// Get a reference to the inner file systems,
424    /// in search order.
425    pub fn roots(&self) -> &[Box<dyn VFS>] {
426        &self.roots
427    }
428}
429
430impl VFS for OverlayFS {
431    /// Open the file at this path with the given options
432    fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>> {
433        let mut tried: Vec<(PathBuf, Error)> = vec![];
434
435        for vfs in &self.roots {
436            match vfs.open_options(path, open_options) {
437                Err(e) => {
438                    if let Some(vfs_path) = vfs.to_path_buf() {
439                        tried.push((vfs_path, e));
440                    } else {
441                        tried.push((PathBuf::from("<invalid path>"), e));
442                    }
443                }
444                f => return f,
445            }
446        }
447        let errmessage = String::from(convenient_path_to_str(path)?);
448        Err(Error::ResourceNotFound(errmessage, tried))
449    }
450
451    /// Create a directory at the location by this path
452    fn mkdir(&self, path: &Path) -> Result {
453        for vfs in &self.roots {
454            match vfs.mkdir(path) {
455                Err(_) => (),
456                f => return f,
457            }
458        }
459        Err(Error::VfsError(format!(
460            "Could not find anywhere writeable to make dir {:?}",
461            path
462        )))
463    }
464
465    /// Remove a file
466    fn rm(&self, path: &Path) -> Result {
467        for vfs in &self.roots {
468            match vfs.rm(path) {
469                Err(_) => (),
470                f => return f,
471            }
472        }
473        Err(Error::VfsError(format!("Could not remove file {:?}", path)))
474    }
475
476    /// Remove a file or directory and all its contents
477    fn rmrf(&self, path: &Path) -> Result {
478        for vfs in &self.roots {
479            match vfs.rmrf(path) {
480                Err(_) => (),
481                f => return f,
482            }
483        }
484        Err(Error::VfsError(format!(
485            "Could not remove file/dir {:?}",
486            path
487        )))
488    }
489
490    /// Check if the file exists
491    fn exists(&self, path: &Path) -> bool {
492        for vfs in &self.roots {
493            if vfs.exists(path) {
494                return true;
495            }
496        }
497
498        false
499    }
500
501    /// Get the file's metadata
502    fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>> {
503        for vfs in &self.roots {
504            match vfs.metadata(path) {
505                Err(_) => (),
506                f => return f,
507            }
508        }
509        Err(Error::VfsError(format!(
510            "Could not get metadata for file/dir {:?}",
511            path
512        )))
513    }
514
515    /// Retrieve the path entries in this path
516    fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
517        // This is tricky 'cause we have to actually merge iterators together...
518        // Doing it the simple and stupid way works though.
519        let mut v = Vec::new();
520        for fs in &self.roots {
521            if let Ok(rddir) = fs.read_dir(path) {
522                v.extend(rddir)
523            }
524        }
525        Ok(Box::new(v.into_iter()))
526    }
527
528    /// Retrieve the actual location of the VFS root, if available.
529    fn to_path_buf(&self) -> Option<PathBuf> {
530        None
531    }
532}
533
534trait ZipArchiveAccess {
535    fn by_name<'a>(&'a mut self, name: &str) -> zip::result::ZipResult<zip::read::ZipFile<'a>>;
536    fn by_index<'a>(
537        &'a mut self,
538        file_number: usize,
539    ) -> zip::result::ZipResult<zip::read::ZipFile<'a>>;
540    fn len(&self) -> usize;
541}
542
543impl<T: Read + Seek> ZipArchiveAccess for zip::ZipArchive<T> {
544    fn by_name(&mut self, name: &str) -> zip::result::ZipResult<zip::read::ZipFile> {
545        self.by_name(name)
546    }
547
548    fn by_index(&mut self, file_number: usize) -> zip::result::ZipResult<zip::read::ZipFile> {
549        self.by_index(file_number)
550    }
551
552    fn len(&self) -> usize {
553        self.len()
554    }
555}
556
557impl Debug for dyn ZipArchiveAccess {
558    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
559        // Hide the contents; for an io::Cursor, this would print what is
560        // likely to be megabytes of data.
561        write!(f, "<ZipArchiveAccess>")
562    }
563}
564
565/// A filesystem backed by a zip file.
566#[derive(Debug)]
567pub struct ZipFS {
568    // It's... a bit jankity.
569    // Zip files aren't really designed to be virtual filesystems,
570    // and the structure of the `zip` crate doesn't help.  See the various
571    // issues that have been filed on it by icefoxen.
572    //
573    // ALSO THE SEMANTICS OF ZIPARCHIVE AND HAVING ZIPFILES BORROW IT IS
574    // HORRIFICALLY BROKEN BY DESIGN SO WE'RE JUST GONNA REFCELL IT AND COPY
575    // ALL CONTENTS OUT OF IT AAAAA.
576    source: Option<PathBuf>,
577    archive: RefCell<Box<dyn ZipArchiveAccess>>,
578    // We keep an index of what files are in the zip file
579    // because trying to read it lazily is a pain in the butt.
580    index: Vec<String>,
581}
582
583impl ZipFS {
584    /// Make new VFS from a zip file
585    pub fn new(filename: &Path) -> Result<Self> {
586        let f = fs::File::open(filename)?;
587        let archive = Box::new(zip::ZipArchive::new(f)?);
588        ZipFS::from_boxed_archive(archive, Some(filename.into()))
589    }
590
591    /// Creates a `ZipFS` from any `Read+Seek` object, most useful with an
592    /// in-memory `std::io::Cursor`.
593    pub fn from_read<R>(reader: R) -> Result<Self>
594    where
595        R: Read + Seek + 'static,
596    {
597        let archive = Box::new(zip::ZipArchive::new(reader)?);
598        ZipFS::from_boxed_archive(archive, None)
599    }
600
601    fn from_boxed_archive(
602        mut archive: Box<dyn ZipArchiveAccess>,
603        source: Option<PathBuf>,
604    ) -> Result<Self> {
605        let idx = (0..archive.len())
606            .map(|i| {
607                archive
608                    .by_index(i)
609                    .expect("Should never happen!")
610                    .name()
611                    .to_string()
612            })
613            .collect();
614        Ok(Self {
615            source,
616            archive: RefCell::new(archive),
617            index: idx,
618        })
619    }
620}
621
622/// A wrapper to contain a zipfile so we can implement
623/// (janky) Seek on it and such.
624///
625/// We're going to do it the *really* janky way and just read
626/// the whole `ZipFile` into a buffer, which is kind of awful but means
627/// we don't have to deal with lifetimes, self-borrowing structs,
628/// rental, re-implementing Seek on compressed data, making multiple zip
629/// zip file objects share a single file handle, or any of that
630/// other nonsense.
631#[derive(Clone)]
632pub struct ZipFileWrapper {
633    buffer: io::Cursor<Vec<u8>>,
634}
635
636impl ZipFileWrapper {
637    fn new(z: &mut zip::read::ZipFile) -> Result<Self> {
638        let mut b = Vec::new();
639        let _ = z.read_to_end(&mut b)?;
640        Ok(Self {
641            buffer: io::Cursor::new(b),
642        })
643    }
644}
645
646impl io::Read for ZipFileWrapper {
647    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
648        self.buffer.read(buf)
649    }
650}
651
652impl io::Write for ZipFileWrapper {
653    fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
654        panic!("Cannot write to a zip file!")
655    }
656
657    fn flush(&mut self) -> io::Result<()> {
658        Ok(())
659    }
660}
661
662impl io::Seek for ZipFileWrapper {
663    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
664        self.buffer.seek(pos)
665    }
666}
667
668impl Debug for ZipFileWrapper {
669    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
670        write!(f, "<Zipfile>")
671    }
672}
673
674#[derive(Debug, Copy, Clone, PartialEq)]
675struct ZipMetadata {
676    len: u64,
677    is_dir: bool,
678    is_file: bool,
679}
680
681impl ZipMetadata {
682    /// Returns a ZipMetadata, or None if the file does not exist or such.
683    /// This is not QUITE correct; since zip archives don't actually have
684    /// directories (just long filenames), we can't get a directory's metadata
685    /// this way without basically just faking it.
686    ///
687    /// This does make listing a directory rather screwy.
688    fn new(name: &str, archive: &mut dyn ZipArchiveAccess) -> Option<Self> {
689        match archive.by_name(name) {
690            Err(_) => None,
691            Ok(zipfile) => {
692                let len = zipfile.size();
693                Some(ZipMetadata {
694                    len,
695                    is_file: true,
696                    is_dir: false, // mu
697                })
698            }
699        }
700    }
701}
702
703impl VMetadata for ZipMetadata {
704    fn is_dir(&self) -> bool {
705        self.is_dir
706    }
707    fn is_file(&self) -> bool {
708        self.is_file
709    }
710    fn len(&self) -> u64 {
711        self.len
712    }
713}
714
715impl VFS for ZipFS {
716    fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>> {
717        // Zip is readonly
718        let path = convenient_path_to_str(path)?;
719        if open_options.write || open_options.create || open_options.append || open_options.truncate
720        {
721            let msg = format!(
722                "Cannot alter file {:?} in zipfile {:?}, filesystem read-only",
723                path, self
724            );
725            return Err(Error::VfsError(msg));
726        }
727        let mut stupid_archive_borrow = self.archive
728            .try_borrow_mut()
729            .expect("Couldn't borrow ZipArchive in ZipFS::open_options(); should never happen! Report a bug at https://github.com/ggez/ggez/");
730        let mut f = stupid_archive_borrow.by_name(path)?;
731        let zipfile = ZipFileWrapper::new(&mut f)?;
732        Ok(Box::new(zipfile) as Box<dyn VFile>)
733    }
734
735    fn mkdir(&self, path: &Path) -> Result {
736        let msg = format!(
737            "Cannot mkdir {:?} in zipfile {:?}, filesystem read-only",
738            path, self
739        );
740        Err(Error::VfsError(msg))
741    }
742
743    fn rm(&self, path: &Path) -> Result {
744        let msg = format!(
745            "Cannot rm {:?} in zipfile {:?}, filesystem read-only",
746            path, self
747        );
748        Err(Error::VfsError(msg))
749    }
750
751    fn rmrf(&self, path: &Path) -> Result {
752        let msg = format!(
753            "Cannot rmrf {:?} in zipfile {:?}, filesystem read-only",
754            path, self
755        );
756        Err(Error::VfsError(msg))
757    }
758
759    fn exists(&self, path: &Path) -> bool {
760        let mut stupid_archive_borrow = self.archive
761            .try_borrow_mut()
762            .expect("Couldn't borrow ZipArchive in ZipFS::exists(); should never happen!  Report a bug at https://github.com/ggez/ggez/");
763        if let Ok(path) = convenient_path_to_str(path) {
764            stupid_archive_borrow.by_name(path).is_ok()
765        } else {
766            false
767        }
768    }
769
770    fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>> {
771        let path = convenient_path_to_str(path)?;
772        let mut stupid_archive_borrow = self.archive
773            .try_borrow_mut()
774            .expect("Couldn't borrow ZipArchive in ZipFS::metadata(); should never happen! Report a bug at https://github.com/ggez/ggez/");
775        match ZipMetadata::new(path, &mut **stupid_archive_borrow) {
776            None => Err(Error::VfsError(format!(
777                "Metadata not found in zip file for {}",
778                path
779            ))),
780            Some(md) => Ok(Box::new(md) as Box<dyn VMetadata>),
781        }
782    }
783
784    /// Zip files don't have real directories, so we (incorrectly) hack it by
785    /// just looking for a path prefix for now.
786    fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
787        let path = convenient_path_to_str(path)?;
788        let itr = self
789            .index
790            .iter()
791            .filter(|s| s.starts_with(path))
792            .map(|s| Ok(PathBuf::from(s)))
793            .collect::<Vec<_>>();
794        Ok(Box::new(itr.into_iter()))
795    }
796
797    fn to_path_buf(&self) -> Option<PathBuf> {
798        self.source.clone()
799    }
800}
801
802#[cfg(test)]
803mod tests {
804    use super::*;
805    use std::io::{self, BufRead};
806
807    #[test]
808    fn headless_test_path_filtering() {
809        // Valid paths
810        let p = path::Path::new("/foo");
811        assert!(sanitize_path(p).is_some());
812
813        let p = path::Path::new("/foo/");
814        assert!(sanitize_path(p).is_some());
815
816        let p = path::Path::new("/foo/bar.txt");
817        assert!(sanitize_path(p).is_some());
818
819        let p = path::Path::new("/");
820        assert!(sanitize_path(p).is_some());
821
822        // Invalid paths
823        let p = path::Path::new("../foo");
824        assert!(sanitize_path(p).is_none());
825
826        let p = path::Path::new("foo");
827        assert!(sanitize_path(p).is_none());
828
829        let p = path::Path::new("/foo/../../");
830        assert!(sanitize_path(p).is_none());
831
832        let p = path::Path::new("/foo/../bop");
833        assert!(sanitize_path(p).is_none());
834
835        let p = path::Path::new("/../bar");
836        assert!(sanitize_path(p).is_none());
837
838        let p = path::Path::new("");
839        assert!(sanitize_path(p).is_none());
840    }
841
842    #[test]
843    fn headless_test_read() {
844        let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR"));
845        let fs = PhysicalFS::new(cargo_path, true);
846        let f = fs.open(Path::new("/Cargo.toml")).unwrap();
847        let mut bf = io::BufReader::new(f);
848        let mut s = String::new();
849        let _ = bf.read_line(&mut s).unwrap();
850        // Trim whitespace from string 'cause it will
851        // potentially be different on Windows and Unix.
852        let trimmed_string = s.trim();
853        assert_eq!(trimmed_string, "[package]");
854    }
855
856    #[test]
857    fn headless_test_read_overlay() {
858        let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR"));
859        let fs1 = PhysicalFS::new(cargo_path, true);
860        let mut f2path = PathBuf::from(cargo_path);
861        f2path.push("src");
862        let fs2 = PhysicalFS::new(&f2path, true);
863        let mut ofs = OverlayFS::new();
864        ofs.push(Box::new(fs1));
865        ofs.push(Box::new(fs2));
866
867        assert!(ofs.exists(Path::new("/Cargo.toml")));
868        assert!(ofs.exists(Path::new("/lib.rs")));
869        assert!(!ofs.exists(Path::new("/foobaz.rs")));
870    }
871
872    #[test]
873    fn headless_test_physical_all() {
874        let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR"));
875        let fs = PhysicalFS::new(cargo_path, false);
876        let testdir = Path::new("/testdir");
877        let f1 = Path::new("/testdir/file1.txt");
878
879        // Delete testdir if it is still lying around
880        if fs.exists(testdir) {
881            fs.rmrf(testdir).unwrap();
882        }
883        assert!(!fs.exists(testdir));
884
885        // Create and delete test dir
886        fs.mkdir(testdir).unwrap();
887        assert!(fs.exists(testdir));
888        fs.rm(testdir).unwrap();
889        assert!(!fs.exists(testdir));
890
891        let test_string = "Foo!";
892        fs.mkdir(testdir).unwrap();
893        {
894            let mut f = fs.append(f1).unwrap();
895            let _ = f.write(test_string.as_bytes()).unwrap();
896        }
897        {
898            let mut buf = Vec::new();
899            let mut f = fs.open(f1).unwrap();
900            let _ = f.read_to_end(&mut buf).unwrap();
901            assert_eq!(&buf[..], test_string.as_bytes());
902        }
903
904        {
905            // Test metadata()
906            let m = fs.metadata(f1).unwrap();
907            assert!(m.is_file());
908            assert!(!m.is_dir());
909            assert_eq!(m.len(), 4);
910
911            let m = fs.metadata(testdir).unwrap();
912            assert!(!m.is_file());
913            assert!(m.is_dir());
914            // Not exactly sure what the "length" of a directory is, buuuuuut...
915            // It appears to vary based on the platform in fact.
916            // On my desktop, it's 18.
917            // On Travis's VM, it's 4096.
918            // On Appveyor's VM, it's 0.
919            // So, it's meaningless.
920            //assert_eq!(m.len(), 18);
921        }
922
923        {
924            // Test read_dir()
925            let r = fs.read_dir(testdir).unwrap();
926            assert_eq!(r.count(), 1);
927            let r = fs.read_dir(testdir).unwrap();
928            for f in r {
929                let fname = f.unwrap();
930                assert!(fs.exists(&fname));
931            }
932        }
933
934        {
935            assert!(fs.exists(f1));
936            fs.rm(f1).unwrap();
937            assert!(!fs.exists(f1));
938        }
939
940        fs.rmrf(testdir).unwrap();
941        assert!(!fs.exists(testdir));
942    }
943
944    fn make_zip_fs() -> Box<dyn VFS> {
945        let mut finished_zip_bytes: io::Cursor<_> = {
946            let zip_bytes = io::Cursor::new(vec![]);
947            let mut zip_archive = zip::ZipWriter::new(zip_bytes);
948
949            zip_archive
950                .start_file("fake_file_name.txt", zip::write::FileOptions::default())
951                .unwrap();
952            let _bytes = zip_archive.write(b"Zip contents!").unwrap();
953            zip_archive.add_directory("fake_dir", zip::write::FileOptions::default())
954                .unwrap();
955            zip_archive
956                .start_file("fake_dir/file.txt", zip::write::FileOptions::default())
957                .unwrap();
958            let _bytes = zip_archive.write(b"Zip contents!").unwrap();
959
960            zip_archive.finish().unwrap()
961        };
962
963        let _bytes = finished_zip_bytes.seek(io::SeekFrom::Start(0)).unwrap();
964        let zfs = ZipFS::from_read(finished_zip_bytes).unwrap();
965        Box::new(zfs)
966    }
967
968    #[test]
969    fn test_zip_files() {
970        let zfs = make_zip_fs();
971
972        assert!(zfs.exists(Path::new("fake_file_name.txt".into())));
973
974        let mut contents = String::new();
975        let _bytes = zfs
976            .open(Path::new("fake_file_name.txt"))
977            .unwrap()
978            .read_to_string(&mut contents)
979            .unwrap();
980        assert_eq!(contents, "Zip contents!");
981    }
982
983    #[test]
984    fn headless_test_zip_all() {
985        let fs = make_zip_fs();
986        let testdir = Path::new("/testdir");
987        let testfile = Path::new("/file1.txt");
988        // TODO: Fix absolute vs. relative paths for zip files...
989        let existing_file = Path::new("fake_file_name.txt");
990        let existing_dir = Path::new("fake_dir");
991
992        assert!(!fs.exists(testfile));
993        assert!(!fs.exists(testdir));
994        assert!(fs.exists(existing_file));
995        // TODO: This fails, why?
996        //assert!(fs.exists(existing_dir));
997
998
999        // Create and delete test dir -- which always fails
1000        assert!(fs.mkdir(testdir).is_err());
1001        assert!(!fs.exists(testdir));
1002        assert!(fs.rm(testdir).is_err());
1003
1004        // Reading an existing file succeeds.
1005        let _ = fs.open(existing_file).unwrap();
1006        // Writing to a new fails
1007        assert!(fs.create(testfile).is_err());
1008        // Appending a file fails
1009        assert!(fs.append(testfile).is_err());
1010
1011        {
1012            // Test metadata()
1013            let m = fs.metadata(existing_file).unwrap();
1014            assert!(m.is_file());
1015            assert!(!m.is_dir());
1016            assert_eq!(m.len(), 13);
1017
1018            // TODO: Fix
1019            /*
1020            let m = fs.metadata(existing_dir).unwrap();
1021            assert!(!m.is_file());
1022            assert!(m.is_dir());
1023*/
1024
1025            assert!(fs.metadata(testfile).is_err());
1026        }
1027
1028        {
1029            // TODO: Test read_dir()
1030            /*
1031            let r = fs.read_dir(existing_dir).unwrap();
1032            assert_eq!(r.count(), 1);
1033            let r = fs.read_dir(testdir).unwrap();
1034            for f in r {
1035                let fname = f.unwrap();
1036                assert!(fs.exists(&fname));
1037            }
1038             */
1039        }
1040
1041        assert!(fs.rmrf(testdir).is_err());
1042        assert!(fs.rmrf(existing_dir).is_err());
1043
1044    }
1045
1046    // BUGGO: TODO: Make sure all functions are tested for OverlayFS and ZipFS!!
1047}