logix_vfs/
mem_fs.rs

1use std::{
2    collections::BTreeMap,
3    ffi::OsString,
4    fmt,
5    io::Cursor,
6    path::{Path, PathBuf},
7    sync::Arc,
8};
9
10use crate::{utils::PathUtil, Error, LogixVfs, LogixVfsDirEntry};
11
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13enum FileData {
14    Static(&'static [u8]),
15    Arc(Arc<[u8]>),
16}
17
18impl fmt::Debug for FileData {
19    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20        match self {
21            Self::Static(buf) => f.debug_struct("Static").field("size", &buf.len()).finish(),
22            Self::Arc(buf) => f.debug_struct("Arc").field("size", &buf.len()).finish(),
23        }
24    }
25}
26
27#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
28enum Entry {
29    #[default]
30    Empty,
31    File(FileData),
32    Dir(BTreeMap<OsString, Entry>),
33}
34
35#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
36pub struct MemFs {
37    root: Entry,
38}
39
40impl MemFs {
41    fn resolve_node_mut(&mut self, path: PathBuf, create_path: bool) -> Result<&mut Entry, Error> {
42        use std::path::Component;
43
44        let mut cur = &mut self.root;
45
46        for (i, component) in path.components().enumerate() {
47            match component {
48                Component::RootDir => (),
49                Component::Prefix(_) | Component::CurDir | Component::ParentDir => {
50                    debug_assert!(false, "Should be unreachable ({path:?})");
51                    return Err(Error::Other(format!(
52                        "Internal error: path {path:?} is not canonicalized",
53                    )));
54                }
55                Component::Normal(name) => 'retry_cur: loop {
56                    match cur {
57                        Entry::Empty => {
58                            if create_path {
59                                *cur = Entry::Dir([(name.to_owned(), Entry::Empty)].into());
60                                continue 'retry_cur;
61                            } else {
62                                return Err(Error::NotFound { path });
63                            }
64                        }
65                        Entry::File(_) => {
66                            let dir: PathBuf = path.components().take(i).collect();
67                            return Err(Error::Other(format!(
68                                "Cannot create directory {dir:?} as it is a file for {path:?}"
69                            )));
70                        }
71                        Entry::Dir(map) => cur = map.entry(name.to_owned()).or_default(),
72                    }
73                    break;
74                },
75            }
76        }
77
78        Ok(cur)
79    }
80
81    fn resolve_node(&self, path: PathBuf) -> Result<(PathBuf, &Entry), Error> {
82        use std::path::Component;
83
84        let mut cur = &self.root;
85
86        for (i, component) in path.components().enumerate() {
87            match component {
88                Component::RootDir => (),
89                Component::Prefix(_) | Component::CurDir | Component::ParentDir => {
90                    debug_assert!(false, "Should be unreachable ({path:?})");
91                    return Err(Error::Other(format!(
92                        "Internal error: path {path:?} is not canonicalized",
93                    )));
94                }
95                Component::Normal(name) => match cur {
96                    Entry::Empty => return Err(Error::NotFound { path }),
97                    Entry::File(_) => {
98                        let dir: PathBuf = path.components().take(i).collect();
99                        return Err(Error::NotADirectory { path: dir });
100                    }
101                    Entry::Dir(map) => {
102                        if let Some(entry) = map.get(name) {
103                            cur = entry;
104                        } else {
105                            return Err(Error::NotFound { path });
106                        }
107                    }
108                },
109            }
110        }
111
112        Ok((path, cur))
113    }
114
115    fn resolve_path(&self, path: impl AsRef<Path>) -> Result<PathBuf, Error> {
116        PathUtil {
117            root: "/".as_ref(),
118            cur_dir: "/".as_ref(),
119        }
120        .resolve_path(false, path.as_ref())
121    }
122
123    pub fn set_static_file(
124        &mut self,
125        path: impl AsRef<Path>,
126        data: &'static [u8],
127        create_dir: bool,
128    ) -> Result<(), Error> {
129        let path = path.as_ref();
130        let node = self.resolve_node_mut(self.resolve_path(path)?, create_dir)?;
131        match node {
132            Entry::Empty | Entry::File(_) => {
133                *node = Entry::File(FileData::Static(data));
134                Ok(())
135            }
136            Entry::Dir(_) => Err(Error::Other(format!(
137                "Can't overwrite directory with a file at {path:?}"
138            ))),
139        }
140    }
141
142    pub fn set_file(
143        &mut self,
144        path: impl AsRef<Path>,
145        data: impl Into<Arc<[u8]>>,
146        create_dir: bool,
147    ) -> Result<(), Error> {
148        let path = path.as_ref();
149        let node = self.resolve_node_mut(self.resolve_path(path)?, create_dir)?;
150        match node {
151            Entry::Empty | Entry::File(_) => {
152                *node = Entry::File(FileData::Arc(data.into()));
153                Ok(())
154            }
155            Entry::Dir(_) => Err(Error::Other(format!(
156                "Can't overwrite directory with a file at {path:?}"
157            ))),
158        }
159    }
160}
161
162#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
163pub struct MemFileData(FileData);
164
165impl AsRef<[u8]> for MemFileData {
166    fn as_ref(&self) -> &[u8] {
167        match &self.0 {
168            FileData::Static(buf) => buf,
169            FileData::Arc(buf) => buf,
170        }
171    }
172}
173
174pub type MemFile = Cursor<MemFileData>;
175
176#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
177enum DirEntryType {
178    File,
179    Dir,
180}
181
182#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
183pub struct DirEntry {
184    path: PathBuf,
185    ty: DirEntryType,
186}
187
188impl LogixVfsDirEntry for DirEntry {
189    fn path(&self) -> &Path {
190        &self.path
191    }
192
193    fn is_dir(&self) -> bool {
194        match self.ty {
195            DirEntryType::File => false,
196            DirEntryType::Dir => true,
197        }
198    }
199
200    fn is_file(&self) -> bool {
201        match self.ty {
202            DirEntryType::File => true,
203            DirEntryType::Dir => false,
204        }
205    }
206
207    fn is_symlink(&self) -> bool {
208        false
209    }
210}
211
212#[derive(Clone, Debug)]
213pub struct ReadDir {
214    it: std::vec::IntoIter<DirEntry>,
215}
216
217impl ReadDir {
218    fn new(base: &Path, map: &BTreeMap<OsString, Entry>) -> Self {
219        let list: Vec<_> = map
220            .iter()
221            .filter_map(|(k, v)| {
222                let ty = match v {
223                    Entry::Empty => return None,
224                    Entry::File(_) => DirEntryType::File,
225                    Entry::Dir(_) => DirEntryType::Dir,
226                };
227
228                Some(DirEntry {
229                    path: base.join(k),
230                    ty,
231                })
232            })
233            .collect();
234        ReadDir {
235            it: list.into_iter(),
236        }
237    }
238}
239
240impl Iterator for ReadDir {
241    type Item = Result<DirEntry, Error>;
242
243    fn next(&mut self) -> Option<Self::Item> {
244        self.it.next().map(Ok)
245    }
246}
247
248impl LogixVfs for MemFs {
249    type RoFile = Cursor<MemFileData>;
250    type DirEntry = DirEntry;
251    type ReadDir = ReadDir;
252
253    fn canonicalize_path(&self, path: &Path) -> Result<PathBuf, crate::Error> {
254        self.resolve_path(path)
255    }
256
257    fn open_file(&self, path: &Path) -> Result<Self::RoFile, crate::Error> {
258        match self.resolve_node(self.resolve_path(path)?)? {
259            (_, Entry::Empty) => Err(Error::NotFound {
260                path: path.to_path_buf(),
261            }),
262            (_, Entry::File(data)) => Ok(Cursor::new(MemFileData(data.clone()))),
263            (_, Entry::Dir(_)) => Err(Error::Other(format!("The path {path:?} is not a file"))),
264        }
265    }
266
267    fn read_dir(&self, path: &Path) -> Result<Self::ReadDir, crate::Error> {
268        match self.resolve_node(self.resolve_path(path)?)? {
269            (_, Entry::Empty) => Err(Error::NotFound {
270                path: path.to_path_buf(),
271            }),
272            (path, Entry::File(_)) => Err(Error::NotADirectory { path }),
273            (path, Entry::Dir(map)) => Ok(ReadDir::new(&path, map)),
274        }
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn basics() {
284        let mut fs = MemFs::default();
285        let hello_rs = b"fn hello() -> i32 {{\n42}}\n".as_slice();
286        let world_rs = b"fn world() -> i32 {{\n1337}}\n".as_slice();
287
288        assert_eq!(
289            fs.set_static_file("/src/hello.rs", hello_rs, false)
290                .unwrap_err(),
291            Error::NotFound {
292                path: "/src/hello.rs".into()
293            }
294        );
295
296        fs.set_static_file("/src/hello.rs", hello_rs, true).unwrap();
297        fs.set_static_file("/src/world.rs", world_rs, true).unwrap();
298
299        assert_eq!(
300            fs.set_static_file("/src/hello.rs/world.rs", hello_rs, false)
301                .unwrap_err(),
302            Error::Other(
303                "Cannot create directory \"/src/hello.rs\" as it is a file for \"/src/hello.rs/world.rs\"".into()
304            )
305        );
306
307        assert_eq!(
308            fs.open_file("/src/hello.rs".as_ref()).unwrap().get_ref().0,
309            FileData::Static(hello_rs)
310        );
311
312        assert_eq!(
313            fs.open_file("/src".as_ref()).unwrap_err(),
314            Error::Other("The path \"/src\" is not a file".to_owned())
315        );
316
317        assert_eq!(
318            fs.open_file("/src/hello.rs/world.rs".as_ref()).unwrap_err(),
319            Error::NotADirectory {
320                path: "/src/hello.rs".into()
321            }
322        );
323
324        {
325            let mut it = fs.read_dir("/".as_ref()).unwrap();
326            let entry = it.next().unwrap().unwrap();
327            assert!(it.next().is_none());
328
329            assert_eq!(entry.path(), Path::new("/src"),);
330            assert!(entry.is_dir());
331            assert!(!entry.is_file());
332        }
333
334        {
335            let mut it = fs.read_dir("/src".as_ref()).unwrap();
336            let file1 = it.next().unwrap().unwrap();
337            let file2 = it.next().unwrap().unwrap();
338            assert!(it.next().is_none());
339
340            assert_eq!(file1.path(), Path::new("/src/hello.rs"),);
341            assert_eq!(file2.path(), Path::new("/src/world.rs"),);
342
343            assert!(!file1.is_dir());
344            assert!(!file2.is_dir());
345
346            assert!(file1.is_file());
347            assert!(file2.is_file());
348        }
349    }
350}