forensic_rs/core/fs/
stdfs.rs

1use std::{io::ErrorKind, path::Path, time::SystemTime};
2
3use crate::{
4    err::{ForensicError, ForensicResult},
5    traits::vfs::{VDirEntry, VFileType, VMetadata, VirtualFile, VirtualFileSystem},
6};
7
8/// this is an error handling routine.
9///
10/// - if `ts_res` contains a valid unix timestamp `ts`, then `Ok(Some(ts))` is returned
11/// - if `ts_res` contains a value which cannot be converted into a unix timestamp, then Err(_) is returned
12/// - if `ts_res` contains an error, then:
13///    - if `kind() == Unsupported` then Ok(None) is returned (because this is not an error)
14///    - otherwise, the error is returned
15fn timestamp_from(ts_res: std::io::Result<SystemTime>) -> ForensicResult<Option<usize>> {
16    match ts_res {
17        Ok(ts) => match ts.duration_since(SystemTime::UNIX_EPOCH) {
18            Ok(v) => Ok(Some(v.as_secs() as usize)),
19            Err(_why) => Err(ForensicError::IllegalTimestamp(format!(
20                "timestamp {ts:?} cannot be converted into a unix timestamp"
21            ))),
22        },
23        Err(why) => {
24            if why.kind() == ErrorKind::Unsupported {
25                Ok(None)
26            } else {
27                Err(why.into())
28            }
29        }
30    }
31}
32
33/// A basic Virtual filesystem that uses the Rust standard library filesystem
34///
35#[derive(Clone, Default)]
36pub struct StdVirtualFS {}
37
38impl StdVirtualFS {
39    pub fn new() -> Self {
40        Self::default()
41    }
42}
43
44pub struct StdVirtualFile {
45    pub file: std::fs::File,
46}
47
48impl std::io::Read for StdVirtualFile {
49    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
50        self.file.read(buf)
51    }
52}
53impl std::io::Seek for StdVirtualFile {
54    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
55        self.file.seek(pos)
56    }
57}
58impl VirtualFile for StdVirtualFile {
59    fn metadata(&self) -> ForensicResult<VMetadata> {
60        let metadata = self.file.metadata()?;
61        let file_type = if metadata.file_type().is_dir() {
62            VFileType::Directory
63        } else if metadata.file_type().is_symlink() {
64            VFileType::Symlink
65        } else {
66            VFileType::File
67        };
68        let created = timestamp_from(metadata.created())?;
69        let accessed = timestamp_from(metadata.accessed())?;
70        let modified = timestamp_from(metadata.modified())?;
71
72        Ok(VMetadata {
73            created,
74            accessed,
75            modified,
76            file_type,
77            size: metadata.len(),
78        })
79    }
80}
81
82impl VirtualFileSystem for StdVirtualFS {
83    fn read_to_string(&mut self, path: &Path) -> ForensicResult<String> {
84        Ok(std::fs::read_to_string(path)?)
85    }
86
87    fn read_all(&mut self, path: &Path) -> ForensicResult<Vec<u8>> {
88        Ok(std::fs::read(path)?)
89    }
90
91    #[cfg(target_os = "macos")]
92    fn read(&mut self, path: &Path, pos: u64, buf: &mut [u8]) -> ForensicResult<usize> {
93        use std::os::unix::prelude::FileExt;
94        let file = std::fs::File::open(path)?;
95        Ok(file.read_at(buf, pos)?)
96    }
97    #[cfg(target_os = "linux")]
98    fn read(&mut self, path: &Path, pos: u64, buf: &mut [u8]) -> ForensicResult<usize> {
99        use std::os::unix::prelude::FileExt;
100        let file = std::fs::File::open(path)?;
101        Ok(file.read_at(buf, pos)?)
102    }
103
104    #[cfg(target_os = "windows")]
105    fn read(&mut self, path: &Path, pos: u64, buf: &mut [u8]) -> ForensicResult<usize> {
106        use std::os::windows::prelude::FileExt;
107        let file = std::fs::File::open(path)?;
108        Ok(file.seek_read(buf, pos)?)
109    }
110
111    fn metadata(&mut self, path: &Path) -> ForensicResult<VMetadata> {
112        let metadata = std::fs::metadata(path)?;
113        let file_type = if metadata.file_type().is_dir() {
114            VFileType::Directory
115        } else if metadata.file_type().is_symlink() {
116            VFileType::Symlink
117        } else {
118            VFileType::File
119        };
120
121        let created = timestamp_from(metadata.created())?;
122        let accessed = timestamp_from(metadata.accessed())?;
123        let modified = timestamp_from(metadata.modified())?;
124
125        Ok(VMetadata {
126            created,
127            accessed,
128            modified,
129            file_type,
130            size: metadata.len(),
131        })
132    }
133
134    fn read_dir(&mut self, path: &Path) -> ForensicResult<Vec<VDirEntry>> {
135        let mut ret = Vec::with_capacity(128);
136        for dir_entry in std::fs::read_dir(path)? {
137            let entry = dir_entry?;
138            let file_type = entry.file_type()?;
139            let file_entry = if file_type.is_dir() {
140                VDirEntry::Directory(entry.file_name().to_string_lossy().into_owned())
141            } else if file_type.is_symlink() {
142                VDirEntry::Symlink(entry.file_name().to_string_lossy().into_owned())
143            } else {
144                VDirEntry::File(entry.file_name().to_string_lossy().into_owned())
145            };
146            ret.push(file_entry);
147        }
148        Ok(ret)
149    }
150
151    fn is_live(&self) -> bool {
152        true
153    }
154
155    fn open(&mut self, path: &Path) -> ForensicResult<Box<dyn VirtualFile>> {
156        Ok(Box::new(StdVirtualFile {
157            file: std::fs::File::open(path)?,
158        }))
159    }
160
161    fn duplicate(&self) -> Box<dyn VirtualFileSystem> {
162        Box::new(StdVirtualFS {})
163    }
164
165    fn from_file(&self, _file: Box<dyn VirtualFile>) -> ForensicResult<Box<dyn VirtualFileSystem>> {
166        Err(crate::err::ForensicError::NoMoreData)
167    }
168
169    fn from_fs(
170        &self,
171        _fs: Box<dyn VirtualFileSystem>,
172    ) -> ForensicResult<Box<dyn VirtualFileSystem>> {
173        Err(crate::err::ForensicError::NoMoreData)
174    }
175    fn exists(&self, path: &Path) -> bool {
176        path.exists()
177    }
178}
179
180#[cfg(test)]
181mod tst {
182    use crate::traits::vfs::VirtualFileSystem;
183    use std::io::Write;
184    use std::path::Path;
185
186    use crate::core::fs::StdVirtualFS;
187
188    const CONTENT: &str = "File_Content_Of_VFS";
189    const FILE_NAME: &str = "test_vfs_file.txt";
190
191    #[test]
192    fn test_temp_file() {
193        let tmp = std::env::temp_dir();
194        let tmp_file = tmp.join(FILE_NAME);
195        let mut file = std::fs::File::create(&tmp_file).unwrap();
196        file.write_all(CONTENT.as_bytes()).unwrap();
197        drop(file);
198
199        let mut std_vfs = StdVirtualFS::new();
200        test_file_content(&mut std_vfs, &tmp_file);
201        assert!(std_vfs
202            .read_dir(tmp.as_path())
203            .unwrap()
204            .into_iter()
205            .map(|v| v.to_string())
206            .collect::<Vec<String>>()
207            .contains(&"test_vfs_file.txt".to_string()));
208    }
209
210    fn test_file_content(std_vfs: &mut impl VirtualFileSystem, tmp_file: &Path) {
211        let content = std_vfs.read_to_string(tmp_file).unwrap();
212        assert_eq!(CONTENT, content);
213    }
214
215    #[test]
216    fn should_allow_boxing() {
217        struct Test {
218            _fs: Box<dyn VirtualFileSystem>,
219        }
220        let boxed = Box::new(StdVirtualFS::new());
221        Test { _fs: boxed };
222    }
223}