1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! A file system with its root in a particular directory of another filesystem

use crate::{FileSystem, SeekAndRead, VfsError, VfsMetadata, VfsPath, VfsResult};
use std::io::Write;

/// Similar to a chroot but done purely by path manipulation
///
/// NOTE: This mechanism should only be used for convenience, NOT FOR SECURITY
///
/// Symlinks, hardlinks, remounts, side channels and other file system mechanisms can be exploited
/// to circumvent this mechanism
#[derive(Debug, Clone)]
pub struct AltrootFS {
    root: VfsPath,
}

impl AltrootFS {
    /// Create a new root FileSystem at the given virtual path
    pub fn new(root: VfsPath) -> Self {
        AltrootFS { root }
    }
}

impl AltrootFS {
    fn path(&self, path: &str) -> VfsResult<VfsPath> {
        if path.is_empty() {
            return Ok(self.root.clone());
        }
        if path.starts_with('/') {
            return self.root.join(&path[1..]);
        }
        self.root.join(&path)
    }
}

impl FileSystem for AltrootFS {
    fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String>>> {
        self.path(path)?
            .read_dir()
            .map(|result| result.map(|path| path.filename()))
            .map(|entries| Box::new(entries) as Box<dyn Iterator<Item = String>>)
    }

    fn create_dir(&self, path: &str) -> VfsResult<()> {
        self.path(path)?.create_dir()
    }

    fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead>> {
        self.path(path)?.open_file()
    }

    fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
        self.path(path)?.create_file()
    }

    fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
        self.path(path)?.append_file()
    }

    fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
        self.path(path)?.metadata()
    }

    fn exists(&self, path: &str) -> bool {
        self.path(path).map(|path| path.exists()).unwrap_or(false)
    }

    fn remove_file(&self, path: &str) -> VfsResult<()> {
        self.path(path)?.remove_file()
    }

    fn remove_dir(&self, path: &str) -> VfsResult<()> {
        self.path(path)?.remove_dir()
    }

    fn copy_file(&self, src: &str, dest: &str) -> VfsResult<()> {
        if dest.is_empty() {
            return Err(VfsError::NotSupported);
        }
        self.path(src)?.copy_file(&self.path(dest)?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::MemoryFS;
    test_vfs!({
        let memory_root: VfsPath = MemoryFS::new().into();
        let altroot_path = memory_root.join("altroot").unwrap();
        altroot_path.create_dir().unwrap();
        AltrootFS::new(altroot_path)
    });

    #[test]
    fn parent() {
        let memory_root: VfsPath = MemoryFS::new().into();
        let altroot_path = memory_root.join("altroot").unwrap();
        altroot_path.create_dir().unwrap();
        let altroot: VfsPath = AltrootFS::new(altroot_path.clone()).into();
        assert_eq!(altroot.parent(), None);
        assert_eq!(altroot_path.parent(), Some(memory_root));
    }
}

#[cfg(test)]
mod tests_physical {
    use super::*;
    use crate::PhysicalFS;
    test_vfs!({
        let temp_dir = std::env::temp_dir();
        let dir = temp_dir.join(uuid::Uuid::new_v4().to_string());
        std::fs::create_dir_all(&dir).unwrap();

        let physical_root: VfsPath = PhysicalFS::new(dir).into();
        let altroot_path = physical_root.join("altroot").unwrap();
        altroot_path.create_dir().unwrap();
        AltrootFS::new(altroot_path)
    });
}