Skip to main content

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, normalized absolute_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 metadata JSON 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.