Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,17 @@ impl FileLibrary for FileLibraryFake {
}
}

fn list_recent(&self, path: String, limit: i64) -> Result<Vec<String>, String> {
fn list_recent(&self, path: Option<String>, limit: Option<i64>) -> Result<Vec<String>, 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<String>) {
Expand Down Expand Up @@ -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);
Expand All @@ -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<bool>) -> Result<(), String> {
Expand Down Expand Up @@ -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);
}
}
6 changes: 3 additions & 3 deletions implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>`): The directory to scan. Defaults to "/".
/// - `limit` (`Option<int>`): The maximum number of files to return. Defaults to 10.
///
/// **Returns**
/// - `List<str>`: 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<Vec<String>, String>;
fn list_recent(&self, path: Option<String>, limit: Option<i64>) -> Result<Vec<String>, String>;

#[eldritch_method]
/// Creates a new directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ struct FileEntry {
}

#[cfg(feature = "stdlib")]
pub fn list_recent(path: String, limit: i64) -> Result<Vec<String>, String> {
pub fn list_recent(path: Option<String>, limit: Option<i64>) -> Result<Vec<String>, 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())?;
Expand All @@ -28,8 +36,12 @@ pub fn list_recent(path: String, limit: i64) -> Result<Vec<String>, 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)
}
Expand Down Expand Up @@ -66,7 +78,7 @@ fn visit_dirs(dir: &Path, cb: &mut Vec<FileEntry>) -> std::io::Result<()> {
}

#[cfg(not(feature = "stdlib"))]
pub fn list_recent(_path: String, _limit: i64) -> Result<Vec<String>, String> {
pub fn list_recent(_path: Option<String>, _limit: Option<i64>) -> Result<Vec<String>, String> {
Err("list_recent requires stdlib feature".into())
}

Expand Down Expand Up @@ -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"));
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl FileLibrary for StdFileLibrary {
list_impl::list(path)
}

fn list_recent(&self, path: String, limit: i64) -> Result<Vec<String>, String> {
fn list_recent(&self, path: Option<String>, limit: Option<i64>) -> Result<Vec<String>, String> {
list_recent_impl::list_recent(path, limit)
}

Expand Down
Loading