forensic_rs/core/fs/
stdfs.rs1use std::{io::ErrorKind, path::Path, time::SystemTime};
2
3use crate::{
4 err::{ForensicError, ForensicResult},
5 traits::vfs::{VDirEntry, VFileType, VMetadata, VirtualFile, VirtualFileSystem},
6};
7
8fn 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#[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}