Exhume Filesystem as a library
Embed exhume_filesystem to detect, walk, and read supported filesystems through
one normalized, read-only Rust interface.
Install​
cargo add exhume_filesystem
cargo add exhume_body
Detect and walk an image filesystem​
use exhume_body::Body;
use exhume_filesystem::detected_fs::detect_filesystem;
use exhume_filesystem::{Filesystem, WalkEvent, WalkOptions};
fn list_partition() -> Result<(), Box<dyn std::error::Error>> {
let body = Body::new("/evidence/disk.raw".to_string(), "raw");
let offset_bytes = 0x10_0000;
let size_sectors = 0x40_0000;
let size_bytes = size_sectors * body.get_sector_size() as u64;
let mut fs = detect_filesystem(&body, offset_bytes, size_bytes, None)?;
println!("detected {}", fs.filesystem_type());
let report = fs.walk_fs(&WalkOptions::default(), &mut |event| {
if let WalkEvent::File(file) = event {
println!("{}\t{}\t{}", file.identifier, file.size, file.absolute_path);
}
Ok(())
})?;
println!("walk status: {:?}, coverage issues: {}", report.status, report.issues);
Ok(())
}
Unlike the CLI, detect_filesystem receives the partition length in bytes.
The example therefore multiplies the sector count by the body's sector size.
walk_fs continues past recoverable metadata errors and emits typed
WalkEvent::Issue diagnostics. It also logs each warning through log; the
embedding application configures its logger. Filtering warnings never removes
diagnostics from the callback or the completion report. Source initialization
and callback failures return WalkError with the partial coverage. Cancellation
and traversal limits have distinct completion statuses in the report.
Read a file without extracting it first​
FsFileReadSeek adapts a filesystem record to std::io::Read + Seek. It reads
bounded slices through the backing filesystem and keeps a small read-ahead
cache, making it suitable for parsers that expect a seekable stream.
use exhume_filesystem::detected_fs::detect_filesystem_from_path;
use exhume_filesystem::filesystem::FsFileReadSeek;
use exhume_filesystem::Filesystem;
use std::io::{Read, Seek, SeekFrom};
fn read_header() -> Result<(), Box<dyn std::error::Error>> {
let mut fs = detect_filesystem_from_path("/evidence/mobile-extraction")?;
let root_id = fs.get_root_file_id();
let file = fs.get_file_by_path("/private/var/mobile/example.db", root_id)?;
let mut reader = FsFileReadSeek::new(&mut fs, file);
let mut sqlite_header = [0_u8; 16];
reader.read_exact(&mut sqlite_header)?;
reader.seek(SeekFrom::Start(0))?;
assert_eq!(&sqlite_header, b"SQLite format 3\0");
Ok(())
}
The normalized record​
Walking a filesystem produces filesystem::File values with stable fields for
downstream indexing:
- native
identifier, normalizedabsolute_path,name, type, and size; - created, modified, and accessed timestamps normalized to Unix seconds;
- permissions, owner, and group where the filesystem records them;
- optional signature fields populated later by
exhume_indexer; - a
metadataJSON value that retains filesystem-specific detail.
Use the normalized fields for cross-filesystem queries and preserve metadata
when provenance or filesystem-specific interpretation matters.
License​
GPL-2.0-or-later.