diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs index 22336dfbc..882fce79c 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs @@ -201,9 +201,17 @@ impl FileLibrary for FileLibraryFake { } } - fn list_recent(&self, path: String, limit: i64) -> Result, String> { + fn list_recent(&self, path: Option, limit: Option) -> Result, String> { let mut root = self.root.lock(); - let parts = Self::normalize_path(&path); + let path_str = path.unwrap_or_else(|| { + if cfg!(windows) { + "C:\\".to_string() + } else { + "/".to_string() + } + }); + let limit_val = limit.unwrap_or(10); + let parts = Self::normalize_path(&path_str); let mut files = Vec::new(); fn traverse_recursive(entry: &FsEntry, current_path: String, files: &mut Vec) { @@ -238,10 +246,10 @@ impl FileLibrary for FileLibraryFake { // But traverse consumes parts. // It's easier to just traverse from the found entry and prepend `path`. - let base = if path.ends_with('/') && path.len() > 1 { - path.trim_end_matches('/').to_string() + let base = if path_str.ends_with('/') && path_str.len() > 1 { + path_str.trim_end_matches('/').to_string() } else { - path.clone() + path_str.clone() }; traverse_recursive(entry, base, &mut files); @@ -250,8 +258,8 @@ impl FileLibrary for FileLibraryFake { } // Since we don't have timestamps, we just return the first `limit` - let limit = if limit < 0 { 0 } else { limit as usize }; - Ok(files.into_iter().take(limit).collect()) + let limit_usize = if limit_val < 1 { 1 } else { limit_val as usize }; + Ok(files.into_iter().take(limit_usize).collect()) } fn mkdir(&self, path: String, _parent: Option) -> Result<(), String> { @@ -450,4 +458,21 @@ mod tests { file.remove("/tmp/notes_backup.txt".into()).unwrap(); assert!(!file.exists("/tmp/notes_backup.txt".into()).unwrap()); } + + #[test] + fn test_list_recent_default_args() { + let file = FileLibraryFake::default(); + + // Default path "/" should list files across the whole fake filesystem + let all_files = file.list_recent(None, None).unwrap(); + + // In default fake FS, we have: /home/user/notes.txt, /home/user/todo.txt, /etc/passwd + assert!(all_files.len() >= 3); + assert!(all_files.contains(&"/home/user/notes.txt".to_string())); + assert!(all_files.contains(&"/etc/passwd".to_string())); + + // Default limit (10) should capture all of them + let limited_files = file.list_recent(None, Some(1)).unwrap(); + assert_eq!(limited_files.len(), 1); + } } diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs index d380864d2..40c3688f0 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs @@ -162,15 +162,15 @@ pub trait FileLibrary { /// Lists files in a directory recursively, sorted by most recent modification time. /// /// **Parameters** - /// - `path` (`str`): The directory to scan. Defaults to "/". - /// - `limit` (`int`): The maximum number of files to return. Defaults to 10. + /// - `path` (`Option`): The directory to scan. Defaults to "/". + /// - `limit` (`Option`): The maximum number of files to return. Defaults to 10. /// /// **Returns** /// - `List`: A list of file paths sorted by modification time (descending). /// /// **Errors** /// - Returns an error string if scanning fails. - fn list_recent(&self, path: String, limit: i64) -> Result, String>; + fn list_recent(&self, path: Option, limit: Option) -> Result, String>; #[eldritch_method] /// Creates a new directory. diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/list_recent_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/list_recent_impl.rs index df69890d6..2cac9dbd7 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/list_recent_impl.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/list_recent_impl.rs @@ -14,12 +14,20 @@ struct FileEntry { } #[cfg(feature = "stdlib")] -pub fn list_recent(path: String, limit: i64) -> Result, String> { +pub fn list_recent(path: Option, limit: Option) -> Result, String> { let mut entries = Vec::new(); - let root = Path::new(&path); + let path_str = path.unwrap_or_else(|| { + if cfg!(windows) { + "C:\\".to_string() + } else { + "/".to_string() + } + }); + let limit_val = limit.unwrap_or(10); + let root = Path::new(&path_str); if !root.exists() { - return Err(alloc::format!("Path does not exist: {}", path)); + return Err(alloc::format!("Path does not exist: {}", path_str)); } visit_dirs(root, &mut entries).map_err(|e| e.to_string())?; @@ -28,8 +36,12 @@ pub fn list_recent(path: String, limit: i64) -> Result, String> { entries.sort_by(|a, b| b.modified.cmp(&a.modified)); // Take limit - let limit = if limit < 0 { 0 } else { limit as usize }; - let result = entries.into_iter().take(limit).map(|e| e.path).collect(); + let limit_usize = if limit_val < 1 { 1 } else { limit_val as usize }; + let result = entries + .into_iter() + .take(limit_usize) + .map(|e| e.path) + .collect(); Ok(result) } @@ -66,7 +78,7 @@ fn visit_dirs(dir: &Path, cb: &mut Vec) -> std::io::Result<()> { } #[cfg(not(feature = "stdlib"))] -pub fn list_recent(_path: String, _limit: i64) -> Result, String> { +pub fn list_recent(_path: Option, _limit: Option) -> Result, String> { Err("list_recent requires stdlib feature".into()) } @@ -107,18 +119,18 @@ mod tests { let base_path_str = base_path.to_string_lossy().to_string(); // Test limit 1 (should be file3) - let res = list_recent(base_path_str.clone(), 1).unwrap(); + let res = list_recent(Some(base_path_str.clone()), Some(1)).unwrap(); assert_eq!(res.len(), 1); assert!(res[0].contains("file3.txt")); // Test limit 2 (should be file3, file2) - let res = list_recent(base_path_str.clone(), 2).unwrap(); + let res = list_recent(Some(base_path_str.clone()), Some(2)).unwrap(); assert_eq!(res.len(), 2); assert!(res[0].contains("file3.txt")); assert!(res[1].contains("file2.txt")); // Test limit 3 (should be file3, file2, file1) - let res = list_recent(base_path_str.clone(), 10).unwrap(); + let res = list_recent(Some(base_path_str.clone()), Some(10)).unwrap(); assert_eq!(res.len(), 3); assert!(res[0].contains("file3.txt")); assert!(res[1].contains("file2.txt")); @@ -130,7 +142,29 @@ mod tests { let tmp_dir = TempDir::new().unwrap(); let base_path_str = tmp_dir.path().to_string_lossy().to_string(); - let res = list_recent(base_path_str, 10).unwrap(); + let res = list_recent(Some(base_path_str), Some(10)).unwrap(); assert!(res.is_empty()); } + + #[test] + fn test_list_recent_default_args() { + let tmp_dir = TempDir::new().unwrap(); + let base_path = tmp_dir.path(); + + let file1 = base_path.join("file1.txt"); + fs::write(&file1, "content1").unwrap(); + + // Change working directory to test default path "/" which depends on current dir/fs context + // Actually, since we can't easily mock "/", let's just test that passing None for limit + // uses the default of 10. + // For testing `None` for path, it tries to access `/`. We just ensure it doesn't crash + // and returns a Result (might be Err depending on permissions, but usually Ok with / files) + + let res_limit = list_recent(Some(base_path.to_string_lossy().to_string()), None).unwrap(); + assert_eq!(res_limit.len(), 1); + + // Test None path (defaults to "/") + let res_path = list_recent(None, Some(1)); + assert!(res_path.is_ok() || res_path.is_err()); + } } 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 40417ac15..44a8a633b 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs @@ -77,7 +77,7 @@ impl FileLibrary for StdFileLibrary { list_impl::list(path) } - fn list_recent(&self, path: String, limit: i64) -> Result, String> { + fn list_recent(&self, path: Option, limit: Option) -> Result, String> { list_recent_impl::list_recent(path, limit) }