vfs/impls/
memory.rs

1//! An ephemeral in-memory file system, intended mainly for unit tests
2
3use crate::error::VfsErrorKind;
4use crate::{FileSystem, VfsFileType};
5use crate::{SeekAndRead, VfsMetadata};
6use crate::{SeekAndWrite, VfsResult};
7use core::cmp;
8use std::collections::hash_map::Entry;
9use std::collections::HashMap;
10use std::fmt;
11use std::fmt::{Debug, Formatter};
12use std::io::{Cursor, Read, Seek, SeekFrom, Write};
13use std::mem::swap;
14use std::sync::{Arc, RwLock};
15use std::time::SystemTime;
16
17type MemoryFsHandle = Arc<RwLock<MemoryFsImpl>>;
18
19/// An ephemeral in-memory file system, intended mainly for unit tests
20pub struct MemoryFS {
21    handle: MemoryFsHandle,
22}
23
24impl Debug for MemoryFS {
25    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
26        f.write_str("In Memory File System")
27    }
28}
29
30impl MemoryFS {
31    /// Create a new in-memory filesystem
32    pub fn new() -> Self {
33        MemoryFS {
34            handle: Arc::new(RwLock::new(MemoryFsImpl::new())),
35        }
36    }
37
38    fn ensure_has_parent(&self, path: &str) -> VfsResult<()> {
39        let separator = path.rfind('/');
40        if let Some(index) = separator {
41            if self.exists(&path[..index])? {
42                return Ok(());
43            }
44        }
45        Err(VfsErrorKind::Other("Parent path does not exist".into()).into())
46    }
47}
48
49impl Default for MemoryFS {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55struct WritableFile {
56    content: Cursor<Vec<u8>>,
57    destination: String,
58    fs: MemoryFsHandle,
59}
60
61impl Seek for WritableFile {
62    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
63        self.content.seek(pos)
64    }
65}
66
67impl Write for WritableFile {
68    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
69        self.content.write(buf)
70    }
71
72    fn flush(&mut self) -> std::io::Result<()> {
73        self.content.flush()?;
74        let mut content = self.content.get_ref().clone();
75        swap(&mut content, self.content.get_mut());
76        let mut handle = self.fs.write().unwrap();
77        let previous_file = handle.files.get(&self.destination);
78
79        let new_file = MemoryFile {
80            file_type: VfsFileType::File,
81            content: Arc::new(content),
82            created: previous_file
83                .map(|file| file.created)
84                .unwrap_or(SystemTime::now()),
85            modified: Some(SystemTime::now()),
86            accessed: previous_file.map(|file| file.accessed).unwrap_or(None),
87        };
88
89        handle.files.insert(self.destination.clone(), new_file);
90        Ok(())
91    }
92}
93
94impl Drop for WritableFile {
95    fn drop(&mut self) {
96        self.flush()
97            .expect("Flush failed while dropping in-memory file");
98    }
99}
100
101struct ReadableFile {
102    #[allow(clippy::rc_buffer)] // to allow accessing the same object as writable
103    content: Arc<Vec<u8>>,
104    position: u64,
105}
106
107impl ReadableFile {
108    fn len(&self) -> u64 {
109        self.content.len() as u64 - self.position
110    }
111}
112
113impl Read for ReadableFile {
114    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
115        let amt = cmp::min(buf.len(), self.len() as usize);
116
117        if amt == 1 {
118            buf[0] = self.content[self.position as usize];
119        } else {
120            buf[..amt].copy_from_slice(
121                &self.content.as_slice()[self.position as usize..self.position as usize + amt],
122            );
123        }
124        self.position += amt as u64;
125        Ok(amt)
126    }
127}
128
129impl Seek for ReadableFile {
130    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
131        match pos {
132            SeekFrom::Start(offset) => self.position = offset,
133            SeekFrom::Current(offset) => self.position = (self.position as i64 + offset) as u64,
134            SeekFrom::End(offset) => self.position = (self.content.len() as i64 + offset) as u64,
135        }
136        Ok(self.position)
137    }
138}
139
140impl FileSystem for MemoryFS {
141    fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String> + Send>> {
142        let prefix = format!("{}/", path);
143        let handle = self.handle.read().unwrap();
144        let mut found_directory = false;
145        #[allow(clippy::needless_collect)] // need collect to satisfy lifetime requirements
146        let entries: Vec<_> = handle
147            .files
148            .iter()
149            .filter_map(|(candidate_path, _)| {
150                if candidate_path == path {
151                    found_directory = true;
152                }
153                if candidate_path.starts_with(&prefix) {
154                    let rest = &candidate_path[prefix.len()..];
155                    if !rest.contains('/') {
156                        return Some(rest.to_string());
157                    }
158                }
159                None
160            })
161            .collect();
162        if !found_directory {
163            return Err(VfsErrorKind::FileNotFound.into());
164        }
165        Ok(Box::new(entries.into_iter()))
166    }
167
168    fn create_dir(&self, path: &str) -> VfsResult<()> {
169        self.ensure_has_parent(path)?;
170        let map = &mut self.handle.write().unwrap().files;
171        let entry = map.entry(path.to_string());
172        match entry {
173            Entry::Occupied(file) => {
174                return match file.get().file_type {
175                    VfsFileType::File => Err(VfsErrorKind::FileExists.into()),
176                    VfsFileType::Directory => Err(VfsErrorKind::DirectoryExists.into()),
177                }
178            }
179            Entry::Vacant(_) => {
180                map.insert(
181                    path.to_string(),
182                    MemoryFile {
183                        file_type: VfsFileType::Directory,
184                        content: Default::default(),
185                        created: SystemTime::now(),
186                        modified: Some(SystemTime::now()),
187                        accessed: Some(SystemTime::now()),
188                    },
189                );
190            }
191        }
192        Ok(())
193    }
194
195    fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send>> {
196        self.set_access_time(path, SystemTime::now())?;
197
198        let handle = self.handle.read().unwrap();
199        let file = handle.files.get(path).ok_or(VfsErrorKind::FileNotFound)?;
200        ensure_file(file)?;
201        Ok(Box::new(ReadableFile {
202            content: file.content.clone(),
203            position: 0,
204        }))
205    }
206
207    fn create_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
208        self.ensure_has_parent(path)?;
209        let content = Arc::new(Vec::<u8>::new());
210        self.handle.write().unwrap().files.insert(
211            path.to_string(),
212            MemoryFile {
213                file_type: VfsFileType::File,
214                content,
215                created: SystemTime::now(),
216                modified: Some(SystemTime::now()),
217                accessed: Some(SystemTime::now()),
218            },
219        );
220        let writer = WritableFile {
221            content: Cursor::new(vec![]),
222            destination: path.to_string(),
223            fs: self.handle.clone(),
224        };
225        Ok(Box::new(writer))
226    }
227
228    fn append_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
229        let handle = self.handle.write().unwrap();
230        let file = handle.files.get(path).ok_or(VfsErrorKind::FileNotFound)?;
231        let mut content = Cursor::new(file.content.as_ref().clone());
232        content.seek(SeekFrom::End(0))?;
233        let writer = WritableFile {
234            content,
235            destination: path.to_string(),
236            fs: self.handle.clone(),
237        };
238        Ok(Box::new(writer))
239    }
240
241    fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
242        let guard = self.handle.read().unwrap();
243        let files = &guard.files;
244        let file = files.get(path).ok_or(VfsErrorKind::FileNotFound)?;
245        Ok(VfsMetadata {
246            file_type: file.file_type,
247            len: file.content.len() as u64,
248            modified: file.modified,
249            created: Some(file.created),
250            accessed: file.accessed,
251        })
252    }
253
254    fn set_creation_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
255        let mut guard = self.handle.write().unwrap();
256        let files = &mut guard.files;
257        let file = files.get_mut(path).ok_or(VfsErrorKind::FileNotFound)?;
258
259        file.created = time;
260
261        Ok(())
262    }
263
264    fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
265        let mut guard = self.handle.write().unwrap();
266        let files = &mut guard.files;
267        let file = files.get_mut(path).ok_or(VfsErrorKind::FileNotFound)?;
268
269        file.modified = Some(time);
270
271        Ok(())
272    }
273
274    fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
275        let mut guard = self.handle.write().unwrap();
276        let files = &mut guard.files;
277        let file = files.get_mut(path).ok_or(VfsErrorKind::FileNotFound)?;
278
279        file.accessed = Some(time);
280
281        Ok(())
282    }
283
284    fn exists(&self, path: &str) -> VfsResult<bool> {
285        Ok(self.handle.read().unwrap().files.contains_key(path))
286    }
287
288    fn remove_file(&self, path: &str) -> VfsResult<()> {
289        let mut handle = self.handle.write().unwrap();
290        handle
291            .files
292            .remove(path)
293            .ok_or(VfsErrorKind::FileNotFound)?;
294        Ok(())
295    }
296
297    fn remove_dir(&self, path: &str) -> VfsResult<()> {
298        if self.read_dir(path)?.next().is_some() {
299            return Err(VfsErrorKind::Other("Directory to remove is not empty".into()).into());
300        }
301        let mut handle = self.handle.write().unwrap();
302        handle
303            .files
304            .remove(path)
305            .ok_or(VfsErrorKind::FileNotFound)?;
306        Ok(())
307    }
308}
309
310struct MemoryFsImpl {
311    files: HashMap<String, MemoryFile>,
312}
313
314impl MemoryFsImpl {
315    pub fn new() -> Self {
316        let mut files = HashMap::new();
317        // Add root directory
318        files.insert(
319            "".to_string(),
320            MemoryFile {
321                file_type: VfsFileType::Directory,
322                content: Arc::new(vec![]),
323                created: SystemTime::now(),
324                modified: None,
325                accessed: None,
326            },
327        );
328        Self { files }
329    }
330}
331
332struct MemoryFile {
333    file_type: VfsFileType,
334    #[allow(clippy::rc_buffer)] // to allow accessing the same object as writable
335    content: Arc<Vec<u8>>,
336
337    created: SystemTime,
338    modified: Option<SystemTime>,
339    accessed: Option<SystemTime>,
340}
341
342fn ensure_file(file: &MemoryFile) -> VfsResult<()> {
343    if file.file_type != VfsFileType::File {
344        return Err(VfsErrorKind::Other("Not a file".into()).into());
345    }
346    Ok(())
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use crate::VfsPath;
353    test_vfs!(MemoryFS::new());
354
355    #[test]
356    fn write_and_read_file() -> VfsResult<()> {
357        let root = VfsPath::new(MemoryFS::new());
358        let path = root.join("foobar.txt").unwrap();
359        let _send = &path as &dyn Send;
360        {
361            let mut file = path.create_file().unwrap();
362            write!(file, "Hello world").unwrap();
363            write!(file, "!").unwrap();
364        }
365        {
366            let mut file = path.open_file().unwrap();
367            let mut string: String = String::new();
368            file.read_to_string(&mut string).unwrap();
369            assert_eq!(string, "Hello world!");
370        }
371        assert!(path.exists()?);
372        assert!(!root.join("foo").unwrap().exists()?);
373        let metadata = path.metadata().unwrap();
374        assert_eq!(metadata.len, 12);
375        assert_eq!(metadata.file_type, VfsFileType::File);
376        Ok(())
377    }
378
379    #[test]
380    fn write_and_seek_and_read_file() -> VfsResult<()> {
381        let root = VfsPath::new(MemoryFS::new());
382        let path = root.join("foobar.txt").unwrap();
383        let _send = &path as &dyn Send;
384        {
385            let mut file = path.create_file().unwrap();
386            write!(file, "Hello world").unwrap();
387            write!(file, "!").unwrap();
388            write!(file, " Before seek!!").unwrap();
389            file.seek(SeekFrom::Current(-2)).unwrap();
390            write!(file, " After the Seek!").unwrap();
391        }
392        {
393            let mut file = path.open_file().unwrap();
394            let mut string: String = String::new();
395            file.read_to_string(&mut string).unwrap();
396            assert_eq!(string, "Hello world! Before seek After the Seek!");
397        }
398        assert!(path.exists()?);
399        assert!(!root.join("foo").unwrap().exists()?);
400        let metadata = path.metadata().unwrap();
401        assert_eq!(metadata.len, 40);
402        assert_eq!(metadata.file_type, VfsFileType::File);
403        Ok(())
404    }
405
406    #[test]
407    fn append_file() {
408        let root = VfsPath::new(MemoryFS::new());
409        let _string = String::new();
410        let path = root.join("test_append.txt").unwrap();
411        path.create_file().unwrap().write_all(b"Testing 1").unwrap();
412        path.append_file().unwrap().write_all(b"Testing 2").unwrap();
413        {
414            let mut file = path.open_file().unwrap();
415            let mut string: String = String::new();
416            file.read_to_string(&mut string).unwrap();
417            assert_eq!(string, "Testing 1Testing 2");
418        }
419    }
420
421    #[test]
422    fn append_file_with_seek() {
423        let root = VfsPath::new(MemoryFS::new());
424        let _string = String::new();
425        let path = root.join("test_append.txt").unwrap();
426        path.create_file().unwrap().write_all(b"Testing 1").unwrap();
427        path.append_file().unwrap().write_all(b"Testing 2").unwrap();
428        {
429            let mut file = path.append_file().unwrap();
430            file.seek(SeekFrom::End(-1)).unwrap();
431            file.write_all(b"Testing 3").unwrap();
432        }
433        {
434            let mut file = path.open_file().unwrap();
435            let mut string: String = String::new();
436            file.read_to_string(&mut string).unwrap();
437            assert_eq!(string, "Testing 1Testing Testing 3");
438        }
439    }
440
441    #[test]
442    fn create_dir() {
443        let root = VfsPath::new(MemoryFS::new());
444        let _string = String::new();
445        let path = root.join("foo").unwrap();
446        path.create_dir().unwrap();
447        let metadata = path.metadata().unwrap();
448        assert_eq!(metadata.file_type, VfsFileType::Directory);
449    }
450
451    #[test]
452    fn remove_dir_error_message() {
453        let root = VfsPath::new(MemoryFS::new());
454        let path = root.join("foo").unwrap();
455        let result = path.remove_dir();
456        assert_eq!(
457            format!("{}", result.unwrap_err()),
458            "Could not remove directory for '/foo': The file or directory could not be found"
459        );
460    }
461
462    #[test]
463    fn read_dir_error_message() {
464        let root = VfsPath::new(MemoryFS::new());
465        let path = root.join("foo").unwrap();
466        let result = path.read_dir();
467        match result {
468            Ok(_) => panic!("Error expected"),
469            Err(err) => {
470                assert_eq!(
471                    format!("{}", err),
472                    "Could not read directory for '/foo': The file or directory could not be found"
473                );
474            }
475        }
476    }
477
478    #[test]
479    fn copy_file_across_filesystems() -> VfsResult<()> {
480        let root_a = VfsPath::new(MemoryFS::new());
481        let root_b = VfsPath::new(MemoryFS::new());
482        let src = root_a.join("a.txt")?;
483        let dest = root_b.join("b.txt")?;
484        src.create_file()?.write_all(b"Hello World")?;
485        src.copy_file(&dest)?;
486        assert_eq!(&dest.read_to_string()?, "Hello World");
487        Ok(())
488    }
489
490    // cf. https://github.com/manuel-woelker/rust-vfs/issues/70
491    #[test]
492    fn flush_then_read_with_new_handle() {
493        let root = VfsPath::new(MemoryFS::new());
494        let path = root.join("test.txt").unwrap();
495        let mut write_handle = path.create_file().unwrap();
496        write_handle.write_all(b"Testing 1").unwrap();
497
498        // Ensure flushed data can be read
499        write_handle.flush().unwrap();
500        let mut read_handle = path.open_file().unwrap();
501        let mut string: String = String::new();
502        read_handle.read_to_string(&mut string).unwrap();
503        assert_eq!(string, "Testing 1");
504
505        // Ensure second flush data can be read
506        write_handle.write_all(b"Testing 2").unwrap();
507        write_handle.flush().unwrap();
508        let mut read_handle = path.open_file().unwrap();
509        let mut string: String = String::new();
510        read_handle.read_to_string(&mut string).unwrap();
511        assert_eq!(string, "Testing 1Testing 2");
512
513        // Ensure everything can be read on drop
514        write_handle.write_all(b"Testing 3").unwrap();
515        drop(write_handle);
516        let mut read_handle = path.open_file().unwrap();
517        let mut string: String = String::new();
518        read_handle.read_to_string(&mut string).unwrap();
519        assert_eq!(string, "Testing 1Testing 2Testing 3");
520    }
521}