diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md index 914225d9a..016788679 100644 --- a/docs/_docs/user-guide/eldritch.md +++ b/docs/_docs/user-guide/eldritch.md @@ -706,6 +706,32 @@ The **file.follow** method will call `fn(line)` for any new `line` that is added file.follow('/home/bob/.bash_history', print) ``` +### file.get_times + +`file.get_times(path: str) -> Dict` + +The **file.get_times** method gets all timestamp metadata of a file/directory. + +On *Windows/Unix*, the **Created, Accessed, Modified** times are returned. On *Unix* targets specifically, the **Changed** time is also returned. + +These times are returned as an integer epoch (SECONDS), as in the number of seconds that elapsed from **Jan 1 1970** + +Example, with **1781444321** being the unix epoch for **Sunday, June 14, 2026 at 1:38:41 PM UTC**: + +```python +file.mkdir("./testdir") +print(file.get_times("./file")) +``` + +```json +{ + "atime": 1781444321, + "crtime": 1781444321, + "ctime": 1781444321, + "mtime": 1781444321 +} +``` + ### file.is_dir `file.is_dir(path: str) -> bool` diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs index 2dc411480..237c9e731 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs @@ -152,6 +152,24 @@ impl FileLibrary for FileLibraryFake { Ok(()) } + fn get_times(&self, path: String) -> Result, String> { + // ensure file existence + if !self.exists(path) { + return Err("File doesn't exist".to_string()); + } + + // create btree + let mut map = BTreeMap::new(); + map.insert("mtime".to_string(), 0); + map.insert("atime".to_string(), 0); + map.insert("crtime".to_string(), 0); + + #[cfg(unix)] + map.insert("ctime".to_string(), 0); + + Ok(map) + } + fn is_dir(&self, path: String) -> Result { let mut root = self.root.lock(); let parts = Self::normalize_path(&path); diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs index e5c69525c..94ea5dfae 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs @@ -115,6 +115,24 @@ pub trait FileLibrary { fn_val: Value, ) -> Result<(), String>; // fn is reserved + #[eldritch_method] + /// Gets the times of a file + /// Modified, Created, and Accessed work on most systems, and for UNIX systems changed time is also outputted. + /// + /// **Parameters** + /// - `path` (`str`): The file to get times from + /// + /// **Returns** + /// - `Dict`: The dictionary of files + /// - `mtime` (`int`) + /// - `atime` (`int`) + /// - `crtime` (`int`) + /// - `ctime` (unix only, `int`) + /// + /// **Errors** + /// - Returns an error upon failure of getting time of file + fn get_times(&self, path: String) -> Result, String>; + #[eldritch_method] /// Checks if the path exists and is a directory. /// diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/get_times_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/get_times_impl.rs new file mode 100644 index 000000000..b48ac3a72 --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/get_times_impl.rs @@ -0,0 +1,54 @@ +#[cfg(feature = "stdlib")] +extern crate alloc; + +#[cfg(feature = "stdlib")] +use alloc::collections::BTreeMap; + +#[cfg(feature = "stdlib")] +use alloc::string::String; + +#[cfg(feature = "stdlib")] +use std::{fs::{exists, metadata}, time::{SystemTime, UNIX_EPOCH}}; + +#[cfg(feature = "stdlib")] +fn time_to_epoch(time: SystemTime) -> Result { + // get the distance so the time can be represented as an epoch + // epoch is in seconds + let epoch_secs = time.duration_since(UNIX_EPOCH).map_err(|e| e.to_string())?.as_secs().cast_signed(); + Ok(epoch_secs) +} + +#[cfg(feature = "stdlib")] +fn fetch_times(path: String) -> Result, String> { + // check if exists + if !exists(&path).map_err(|e| e.to_string())? { + return Err(alloc::format!("Could not locate {}", path)); + } + + // open the file + let meta = metadata(path).map_err(|e| e.to_string())?; + + // create the output + let mut dict = BTreeMap::new(); + dict.insert("mtime".to_string(), time_to_epoch(meta.modified().map_err(|e| e.to_string())?)?); + dict.insert("atime".to_string(), time_to_epoch(meta.accessed().map_err(|e| e.to_string())?)?); + dict.insert("crtime".to_string(), time_to_epoch(meta.created().map_err(|e| e.to_string())?)?); + + // change time is only available on posix systems + #[cfg(unix)] + use std::os::unix::fs::MetadataExt; + #[cfg(unix)] + dict.insert("ctime".to_string(), meta.ctime()); + + Ok(dict) +} + +#[cfg(feature = "stdlib")] +pub fn get_times(path: String) -> Result, String> { + Ok(fetch_times(path)?) +} + +#[cfg(not(feature = "stdlib"))] +pub fn get_times(path: alloc::string::String) -> Result, alloc::string::String> { + return Err("stdlib required"); +} \ No newline at end of file diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs index de3991ac3..502b20c92 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs @@ -34,6 +34,7 @@ pub mod timestomp_impl; pub mod tmp_dir_impl; pub mod write_binary_impl; pub mod write_impl; +pub mod get_times_impl; #[derive(Debug, Default)] #[eldritch_library_impl(FileLibrary)] @@ -82,6 +83,10 @@ impl FileLibrary for StdFileLibrary { list_impl::list(path) } + fn get_times(&self, path: String) -> Result, String> { + get_times_impl::get_times(path) + } + fn list_named_pipes(&self, detailed: Option) -> Result { list_named_pipes_impl::list_named_pipes(detailed) }