diff --git a/src/core/fs/stdfs.rs b/src/core/fs/stdfs.rs index 7f7920c..7e51782 100644 --- a/src/core/fs/stdfs.rs +++ b/src/core/fs/stdfs.rs @@ -1,22 +1,50 @@ -use std::{time::SystemTime, path::Path}; - -use crate::{traits::vfs::{VirtualFileSystem, VMetadata, VDirEntry, VFileType, VirtualFile}, err::ForensicResult}; - +use std::{io::ErrorKind, path::Path, time::SystemTime}; + +use crate::{ + err::{ForensicError, ForensicResult}, + traits::vfs::{VDirEntry, VFileType, VMetadata, VirtualFile, VirtualFileSystem}, +}; + +/// this is an error handling routine. +/// +/// - if `ts_res` contains a valid unix timestamp `ts`, then `Ok(Some(ts))` is returned +/// - if `ts_res` contains a value which cannot be converted into a unix timestamp, then Err(_) is returned +/// - if `ts_res` contains an error, then: +/// - if `kind() == Unsupported` then Ok(None) is returned (because this is not an error) +/// - otherwise, the error is returned +fn timestamp_from(ts_res: std::io::Result) -> ForensicResult> { + match ts_res { + Ok(ts) => match ts.duration_since(SystemTime::UNIX_EPOCH) { + Ok(v) => Ok(Some(v.as_secs() as usize)), + Err(_why) => Err(ForensicError::IllegalTimestamp(format!( + "timestamp {ts:?} cannot be converted into a unix timestamp" + ))), + }, + Err(why) => { + if why.kind() == ErrorKind::Unsupported { + Ok(None) + } else { + Err(why.into()) + } + } + } +} /// A basic Virtual filesystem that uses the Rust standard library filesystem -/// -#[derive(Clone)] +/// +#[derive(Clone, Default)] pub struct StdVirtualFS {} impl StdVirtualFS { pub fn new() -> Self { - Self{} + Self::default() } } -pub struct StdVirtualFile{ - pub file : std::fs::File +pub struct StdVirtualFile { + pub file: std::fs::File, } + impl std::io::Read for StdVirtualFile { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.file.read(buf) @@ -31,27 +59,16 @@ impl VirtualFile for StdVirtualFile { fn metadata(&self) -> ForensicResult { let metadata = self.file.metadata()?; let file_type = if metadata.file_type().is_dir() { - VFileType::Directory - }else if metadata.file_type().is_symlink() { + VFileType::Directory + } else if metadata.file_type().is_symlink() { VFileType::Symlink - }else{ + } else { VFileType::File }; - let created = match metadata.created()?.duration_since(SystemTime::UNIX_EPOCH) { - Ok(v) => v, - Err(_e) => std::time::Duration::ZERO - }.as_secs() as usize; - - let accessed = match metadata.accessed()?.duration_since(SystemTime::UNIX_EPOCH) { - Ok(v) => v, - Err(_e) => std::time::Duration::ZERO - }.as_secs() as usize; - - let modified = match metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH) { - Ok(v) => v, - Err(_e) => std::time::Duration::ZERO - }.as_secs() as usize; - + let created = timestamp_from(metadata.created())?; + let accessed = timestamp_from(metadata.accessed())?; + let modified = timestamp_from(metadata.modified())?; + Ok(VMetadata { created, accessed, @@ -63,8 +80,7 @@ impl VirtualFile for StdVirtualFile { } impl VirtualFileSystem for StdVirtualFS { - - fn read_to_string(&mut self, path: &Path) -> ForensicResult{ + fn read_to_string(&mut self, path: &Path) -> ForensicResult { Ok(std::fs::read_to_string(path)?) } @@ -88,32 +104,22 @@ impl VirtualFileSystem for StdVirtualFS { fn metadata(&mut self, path: &Path) -> ForensicResult { let metadata = std::fs::metadata(path)?; let file_type = if metadata.file_type().is_dir() { - VFileType::Directory - }else if metadata.file_type().is_symlink() { + VFileType::Directory + } else if metadata.file_type().is_symlink() { VFileType::Symlink - }else{ + } else { VFileType::File }; - let created = match metadata.created()?.duration_since(SystemTime::UNIX_EPOCH) { - Ok(v) => v, - Err(_e) => std::time::Duration::ZERO - }.as_secs() as usize; - - let accessed = match metadata.accessed()?.duration_since(SystemTime::UNIX_EPOCH) { - Ok(v) => v, - Err(_e) => std::time::Duration::ZERO - }.as_secs() as usize; - - let modified = match metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH) { - Ok(v) => v, - Err(_e) => std::time::Duration::ZERO - }.as_secs() as usize; - + + let created = timestamp_from(metadata.created())?; + let accessed = timestamp_from(metadata.accessed())?; + let modified = timestamp_from(metadata.modified())?; + Ok(VMetadata { created, accessed, modified, - file_type, + file_type, size: metadata.len(), }) } @@ -125,9 +131,9 @@ impl VirtualFileSystem for StdVirtualFS { let file_type = entry.file_type()?; let file_entry = if file_type.is_dir() { VDirEntry::Directory(entry.file_name().to_string_lossy().into_owned()) - }else if file_type.is_symlink() { + } else if file_type.is_symlink() { VDirEntry::Symlink(entry.file_name().to_string_lossy().into_owned()) - }else{ + } else { VDirEntry::File(entry.file_name().to_string_lossy().into_owned()) }; ret.push(file_entry); @@ -139,40 +145,44 @@ impl VirtualFileSystem for StdVirtualFS { true } - fn open(&mut self, path : &Path) -> ForensicResult> { - Ok(Box::new(StdVirtualFile{file:std::fs::File::open(path)?})) + fn open(&mut self, path: &Path) -> ForensicResult> { + Ok(Box::new(StdVirtualFile { + file: std::fs::File::open(path)?, + })) } fn duplicate(&self) -> Box { - Box::new(StdVirtualFS{}) + Box::new(StdVirtualFS {}) } - fn from_file(&self, _file : Box) -> ForensicResult> { + fn from_file(&self, _file: Box) -> ForensicResult> { Err(crate::err::ForensicError::NoMoreData) } - fn from_fs(&self, _fs : Box) -> ForensicResult> { + fn from_fs( + &self, + _fs: Box, + ) -> ForensicResult> { Err(crate::err::ForensicError::NoMoreData) } - fn exists(&self, path : &Path) -> bool { + fn exists(&self, path: &Path) -> bool { path.exists() } } #[cfg(test)] mod tst { - use std::path::PathBuf; - use std::io::Write; use crate::traits::vfs::VirtualFileSystem; + use std::io::Write; + use std::path::Path; use crate::core::fs::StdVirtualFS; - const CONTENT: &'static str = "File_Content_Of_VFS"; - const FILE_NAME: &'static str = "test_vfs_file.txt"; + const CONTENT: &str = "File_Content_Of_VFS"; + const FILE_NAME: &str = "test_vfs_file.txt"; #[test] fn test_temp_file() { - let tmp = std::env::temp_dir(); let tmp_file = tmp.join(FILE_NAME); let mut file = std::fs::File::create(&tmp_file).unwrap(); @@ -180,25 +190,27 @@ mod tst { drop(file); let mut std_vfs = StdVirtualFS::new(); - test_file_content(&mut std_vfs,&tmp_file); - assert!(std_vfs.read_dir(tmp.as_path()).unwrap().into_iter().map(|v| v.to_string()).collect::>().contains(&"test_vfs_file.txt".to_string())); + test_file_content(&mut std_vfs, &tmp_file); + assert!(std_vfs + .read_dir(tmp.as_path()) + .unwrap() + .into_iter() + .map(|v| v.to_string()) + .collect::>() + .contains(&"test_vfs_file.txt".to_string())); } - fn test_file_content(std_vfs : &mut impl VirtualFileSystem, tmp_file : &PathBuf) { + fn test_file_content(std_vfs: &mut impl VirtualFileSystem, tmp_file: &Path) { let content = std_vfs.read_to_string(tmp_file).unwrap(); assert_eq!(CONTENT, content); - } #[test] fn should_allow_boxing() { struct Test { - _fs : Box + _fs: Box, } let boxed = Box::new(StdVirtualFS::new()); - Test { - _fs : boxed - }; - + Test { _fs: boxed }; } -} \ No newline at end of file +} diff --git a/src/err.rs b/src/err.rs index 7175f3f..7fab731 100644 --- a/src/err.rs +++ b/src/err.rs @@ -31,6 +31,7 @@ pub enum ForensicError { BadFormat(BadFormatError), Io(std::io::Error), CastError, + IllegalTimestamp(String) } impl ForensicError { @@ -62,6 +63,7 @@ impl Clone for ForensicError { Self::Missing(e) => Self::Missing(e.clone()), Self::BadFormat(e) => Self::BadFormat(e.clone()), Self::Io(e) => Self::Io(std::io::Error::new(e.kind(), e.to_string())), + Self::IllegalTimestamp(reason) => Self::IllegalTimestamp(reason.clone()), } } } @@ -75,6 +77,7 @@ impl PartialEq for ForensicError { (Self::PermissionError, Self::PermissionError) => true, (Self::NoMoreData, Self::NoMoreData) => true, (Self::CastError, Self::CastError) => true, + (Self::IllegalTimestamp(l0), Self::IllegalTimestamp(r0)) => l0 == r0, _ => false } } @@ -141,6 +144,7 @@ impl std::fmt::Display for ForensicError { ForensicError::BadFormat(e) => f.write_fmt(format_args!("The data have an unexpected format: {}", e)), ForensicError::Io(e) => f.write_fmt(format_args!("IO operations error: {}", e)), ForensicError::CastError => f.write_str("The Into/Form operation cannot be executed"), + ForensicError::IllegalTimestamp(reason) => f.write_fmt(format_args!("Illegal timestamp: '{reason}'")) } } } @@ -159,6 +163,7 @@ impl std::error::Error for ForensicError { ForensicError::BadFormat(e) => &e.0, ForensicError::Io(_) => "IO operations error", ForensicError::CastError => "The Into/Form operation cannot be executed", + ForensicError::IllegalTimestamp(_) => "Illegal timestamp" } } diff --git a/src/traits/vfs.rs b/src/traits/vfs.rs index bbad130..ebb86b9 100644 --- a/src/traits/vfs.rs +++ b/src/traits/vfs.rs @@ -1,4 +1,7 @@ -use std::{path::{Path, PathBuf}, fmt::Display}; +use std::{ + fmt::Display, + path::{Path, PathBuf}, +}; use crate::err::ForensicResult; @@ -25,21 +28,22 @@ impl From<&Path> for VPath { } } -pub trait VirtualFile : std::io::Seek + std::io::Read { +pub trait VirtualFile: std::io::Seek + std::io::Read { fn metadata(&self) -> ForensicResult; } pub trait VirtualFileSystem { /// Initializes a virtual filesystem from a file. Ex: a Zip FS from a file - fn from_file(&self, file : Box) -> ForensicResult>; + fn from_file(&self, file: Box) -> ForensicResult>; /// Initializes a virtual filesystem from a filesyste. Ex: a remapping of windows routes to Linux routes. - fn from_fs(&self, fs : Box) -> ForensicResult>; + fn from_fs(&self, fs: Box) + -> ForensicResult>; /// Read the entire contents of a file into a string. fn read_to_string(&mut self, path: &Path) -> ForensicResult; /// Read the entire contents of a file into a bytes vector. fn read_all(&mut self, path: &Path) -> ForensicResult>; /// Read part of the content of a file into a bytes vector. - fn read(& mut self, path: &Path, pos: u64, buf: & mut [u8]) -> ForensicResult; + fn read(&mut self, path: &Path, pos: u64, buf: &mut [u8]) -> ForensicResult; /// Get the metadata of a file/dir fn metadata(&mut self, path: &Path) -> ForensicResult; /// Lists the contents of a Directory @@ -47,48 +51,81 @@ pub trait VirtualFileSystem { /// Check if the VirtualFileSystem is an abstraction over the real filesystem and not a virtual (like a ZIP file). fn is_live(&self) -> bool; /// Open a file - fn open(&mut self, path : &Path) -> ForensicResult>; + fn open(&mut self, path: &Path) -> ForensicResult>; /// Allows duplicating the existing file system fn duplicate(&self) -> Box; /// Check if a file exists #[allow(unused_variables)] - fn exists(&self, path : &Path) -> bool { + fn exists(&self, path: &Path) -> bool { false } } - - pub struct VMetadata { /// Seconds elapsed since UNIX_EPOCH in UTC - pub created : usize, + /// + /// this is optional, because some filesystems might not support this timestamp + pub created: Option, + /// Seconds elapsed since UNIX_EPOCH in UTC - pub accessed : usize, + /// + /// this is optional, because some filesystems might not support this timestamp + pub accessed: Option, + /// Seconds elapsed since UNIX_EPOCH in UTC - pub modified : usize, - pub file_type : VFileType, - pub size : u64 + /// + /// this is optional, because some filesystems might not support this timestamp + pub modified: Option, + + pub file_type: VFileType, + pub size: u64, } #[derive(PartialEq)] pub enum VFileType { File, Directory, - Symlink + Symlink, } impl VMetadata { /// Seconds elapsed since UNIX_EPOCH in UTC - pub fn created(&self) -> usize{ - self.created + pub fn created(&self) -> usize { + self.created.unwrap_or_else(|| { + crate::warn!( + "this filesystem has no support for creation time, using UNIX_EPOCH instead" + ); + 0 + }) } /// Seconds elapsed since UNIX_EPOCH in UTC - pub fn accessed(&self) -> usize{ - self.accessed + pub fn accessed(&self) -> usize { + self.accessed.unwrap_or_else(|| { + crate::warn!("this filesystem has no support for access time, using UNIX_EPOCH instead"); + 0 + }) } /// Seconds elapsed since UNIX_EPOCH in UTC - pub fn modified(&self) -> usize{ - self.modified + pub fn modified(&self) -> usize { + self.modified.unwrap_or_else(|| { + crate::warn!( + "this filesystem has no support for modification time, using UNIX_EPOCH instead" + ); + 0 + }) + } + + /// Seconds elapsed since UNIX_EPOCH in UTC + pub fn created_opt(&self) -> Option<&usize> { + self.created.as_ref() + } + /// Seconds elapsed since UNIX_EPOCH in UTC + pub fn accessed_opt(&self) -> Option<&usize> { + self.accessed.as_ref() + } + /// Seconds elapsed since UNIX_EPOCH in UTC + pub fn modified_opt(&self) -> Option<&usize> { + self.modified.as_ref() } pub fn is_file(&self) -> bool { self.file_type == VFileType::File @@ -107,7 +144,7 @@ impl VMetadata { pub enum VDirEntry { Directory(String), File(String), - Symlink(String) + Symlink(String), } impl Display for VDirEntry { @@ -119,4 +156,4 @@ impl Display for VDirEntry { }; write!(f, "{}", content) } -} \ No newline at end of file +} diff --git a/src/utils/time.rs b/src/utils/time.rs index 565ace5..9c05607 100644 --- a/src/utils/time.rs +++ b/src/utils/time.rs @@ -11,6 +11,7 @@ use std::{ops::{Add, AddAssign, Sub}, time::{Duration, SystemTime, UNIX_EPOCH}}; #[derive(Clone, Default, Copy)] pub struct WinFiletime(pub u64); + /// Simplifies handling unix timestamp dates. Use only with UTC dates as it does not take time zones into account. Eliminates the need to use the chrono library. /// /// ```rust