vfs/async_vfs/impls/
altroot.rs

1//! A file system with its root in a particular directory of another filesystem
2
3use crate::async_vfs::{AsyncFileSystem, AsyncVfsPath, SeekAndRead};
4use crate::{error::VfsErrorKind, VfsMetadata, VfsResult};
5use std::time::SystemTime;
6
7use async_std::io::Write;
8use async_trait::async_trait;
9use futures::stream::{Stream, StreamExt};
10
11/// Similar to a chroot but done purely by path manipulation
12///
13/// NOTE: This mechanism should only be used for convenience, NOT FOR SECURITY
14///
15/// Symlinks, hardlinks, remounts, side channels and other file system mechanisms can be exploited
16/// to circumvent this mechanism
17#[derive(Debug, Clone)]
18pub struct AsyncAltrootFS {
19    root: AsyncVfsPath,
20}
21
22impl AsyncAltrootFS {
23    /// Create a new root FileSystem at the given virtual path
24    pub fn new(root: AsyncVfsPath) -> Self {
25        AsyncAltrootFS { root }
26    }
27}
28
29impl AsyncAltrootFS {
30    #[allow(clippy::manual_strip)] // strip prefix manually for MSRV 1.32
31    fn path(&self, path: &str) -> VfsResult<AsyncVfsPath> {
32        if path.is_empty() {
33            return Ok(self.root.clone());
34        }
35        if path.starts_with('/') {
36            return self.root.join(&path[1..]);
37        }
38        self.root.join(path)
39    }
40}
41
42#[async_trait]
43impl AsyncFileSystem for AsyncAltrootFS {
44    async fn read_dir(
45        &self,
46        path: &str,
47    ) -> VfsResult<Box<dyn Stream<Item = String> + Send + Unpin>> {
48        self.path(path)?
49            .read_dir()
50            .await
51            .map(|result| result.map(|path| path.filename()))
52            .map(|entries| Box::new(entries) as Box<dyn Stream<Item = String> + Send + Unpin>)
53    }
54
55    async fn create_dir(&self, path: &str) -> VfsResult<()> {
56        self.path(path)?.create_dir().await
57    }
58
59    async fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send + Unpin>> {
60        self.path(path)?.open_file().await
61    }
62
63    async fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>> {
64        self.path(path)?.create_file().await
65    }
66
67    async fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>> {
68        self.path(path)?.append_file().await
69    }
70
71    async fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
72        self.path(path)?.metadata().await
73    }
74
75    async fn set_creation_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
76        self.path(path)?.set_creation_time(time).await
77    }
78
79    async fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
80        self.path(path)?.set_modification_time(time).await
81    }
82
83    async fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
84        self.path(path)?.set_access_time(time).await
85    }
86
87    async fn exists(&self, path: &str) -> VfsResult<bool> {
88        match self.path(path) {
89            Ok(p) => p.exists().await,
90            Err(_) => Ok(false),
91        }
92    }
93
94    async fn remove_file(&self, path: &str) -> VfsResult<()> {
95        self.path(path)?.remove_file().await
96    }
97
98    async fn remove_dir(&self, path: &str) -> VfsResult<()> {
99        self.path(path)?.remove_dir().await
100    }
101
102    async fn copy_file(&self, src: &str, dest: &str) -> VfsResult<()> {
103        if dest.is_empty() {
104            return Err(VfsErrorKind::NotSupported.into());
105        }
106        self.path(src)?.copy_file(&self.path(dest)?).await
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::async_vfs::AsyncMemoryFS;
114
115    test_async_vfs!(futures::executor::block_on(async {
116        let memory_root: AsyncVfsPath = AsyncMemoryFS::new().into();
117        let altroot_path = memory_root.join("altroot").unwrap();
118        altroot_path.create_dir().await.unwrap();
119        AsyncAltrootFS::new(altroot_path)
120    }));
121
122    #[tokio::test]
123    async fn parent() {
124        let memory_root: AsyncVfsPath = AsyncMemoryFS::new().into();
125        let altroot_path = memory_root.join("altroot").unwrap();
126        altroot_path.create_dir().await.unwrap();
127        let altroot: AsyncVfsPath = AsyncAltrootFS::new(altroot_path.clone()).into();
128        assert_eq!(altroot.parent(), altroot.root());
129        assert_eq!(altroot_path.parent(), memory_root);
130    }
131}
132
133#[cfg(test)]
134mod tests_physical {
135    use super::*;
136    use crate::async_vfs::AsyncPhysicalFS;
137
138    use async_std::io::ReadExt;
139
140    test_async_vfs!(futures::executor::block_on(async {
141        let temp_dir = std::env::temp_dir();
142        let dir = temp_dir.join(uuid::Uuid::new_v4().to_string());
143        std::fs::create_dir_all(&dir).unwrap();
144
145        let physical_root: AsyncVfsPath = AsyncPhysicalFS::new(dir).into();
146        let altroot_path = physical_root.join("altroot").unwrap();
147        altroot_path.create_dir().await.unwrap();
148        AsyncAltrootFS::new(altroot_path)
149    }));
150
151    test_async_vfs_readonly!({
152        let physical_root: AsyncVfsPath = AsyncPhysicalFS::new("test").into();
153        let altroot_path = physical_root.join("test_directory").unwrap();
154        AsyncAltrootFS::new(altroot_path)
155    });
156}