vmspect 0.8.0

Blazing-fast static inspection, forensic analysis and information extraction library for virtual machine disk images (VMDK, RAW, QCOW2, VHD, VHDX, VDI).
docs.rs failed to build vmspect-0.8.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

vmspect

Crates.io License Rust

vmspect is a Rust library and CLI tool for ultra-fast static inspection, forensic analysis and information extraction of virtual machine disk images (VMDK, RAW, QCOW2, VHD, VHDX, VDI, etc.).

It can examine partition-table structures (MBR/GPT), identify the guest operating system (Windows/Linux), extract complete lists of installed software and detect integration tools (Guest Tools) in a non-invasive way (without booting the virtual machine or requiring mount privileges on the host) in less than 70 ms even for virtual disks larger than 80 GiB.


πŸš€ Key Features

  • Extreme performance and lightweight streaming (<70 ms for 80 GiB images):
    • Native Rust parser: Direct, ultra-low-latency reading for RAW and VMDK images (monolithicSparse, monolithicFlat, twoGbMaxExtentFlat/Sparse, etc.) without external dependencies or child processes.
    • Integrated qemu-nbd server: For complex formats (QCOW2, VHDX, VDI, compressed/streamOptimized VMDK), connects over a local TCP socket (127.0.0.1) or UNIX sockets using the standard NBD protocol, with direct block streaming and no temporary files on disk.
  • Resilience against dirty registries and NTFS fallback (Graceful Degradation):
    • Permissive Windows Registry reading: Tolerance for dirty or damaged registry hives (SequenceNumberMismatch caused by abrupt shutdowns or hot snapshots) using Hive::without_validation and isolating internal panics from third-party libraries via catch_unwind.
    • NTFS fallback inspection: If the Registry hives are totally inaccessible, vmspect gracefully degrades by inspecting the PE header of \Windows\System32\ntoskrnl.exe directly to extract the OS build and version, and scans \Program Files, tagging applications as source: Some("FallbackFS").
    • Non-fatal warnings list: Reports issues in the warnings field of the report without aborting the inspection pipeline.
  • Multi-hypervisor, hypervisor-agnostic Guest Tools detection:
    • Full and typed support in the GuestTools struct to identify and extract the version of:
      • VMware Tools / open-vm-tools
      • VirtualBox Guest Additions
      • QEMU Guest Agent
      • Hyper-V Integration Services
  • Supported guest operating systems:
    • Windows (NTFS): Extracts Registry hives (SOFTWARE and SYSTEM) by parsing uninstall keys (32 and 64-bit), operating system version, build number, Service Pack and Guest Tools.
    • Linux (ext2 / ext3 / ext4): Reads /etc/os-release, /etc/hostname and analyzes the /var/lib/dpkg/status package database along with virtualization agents.
  • Scheme and file-system detection:
    • Partition schemes: MBR, GPT and Volumes without a partition table.
    • Signature recognition: NTFS, FAT12/16/32, ext2/3/4, XFS, Btrfs, LVM2 PV, Linux Swap.
  • Agnostic and complete extraction:
    • Default, complete collection of all applications and system information without noise filters or proprietary categorizations.
    • Support for --no-apps (disables application collection) and --no-system (disables OS metadata collection) flags.
  • Designed for UI and CLI:
    • Exposes lock-free batch progress snapshots for external polling without report or program data.
    • Cancellation support via atomic tokens (Arc<AtomicBool> / CancellationToken) while preserving partial results.
    • Directory discovery is explicit and configurable with exclusions and a maximum depth. Initial directory results skip installed-program extraction; use --full-report or a full inspection when that data is required.
    • qemu-nbd sessions are limited process-wide (default: two); native readers do not consume a session.

DiagnΓ³stico de discos VMDK y qemu-nbd

Un descriptor VMDK no siempre contiene los datos del disco. Puede declarar varios extents (FLAT, VMFS, VMFSRAW, SPARSE o VMFSSPARSE) y tambiΓ©n puede apuntar a un disco padre mediante parentFileNameHint. Todos esos archivos forman parte de la entrada que debe estar disponible para la inspecciΓ³n.

Cuando falta un componente, vmspect devuelve VmSpectError::MissingDiskComponent en lugar de VmSpectError::QemuNotFound. El error conserva el nombre declarado, el descriptor principal, la ruta resuelta y el error original del sistema operativo. Por ejemplo:

Missing VMDK extent 'disk-s001.vmdk' referenced by 'fixtures/sample-vm/disk.vmdk'. Resolved path: 'fixtures/sample-vm/disk-s001.vmdk'. OS error: file not found.

qemu-nbd no puede reparar una cadena VMDK incompleta: solo proporciona acceso a una imagen que ya es coherente. Si falta un extent o un disco padre, hay que recuperar el archivo correcto desde el almacenamiento original o desde una copia consistente. No se debe renombrar otro extent para sustituir al faltante, porque eso puede mezclar segmentos distintos y producir una imagen silenciosamente corrupta.

Los errores de resoluciΓ³n del ejecutable (qemu_nbd, QEMU_NBD o PATH) se reportan como QemuNotFound. Los fallos del proceso ya iniciado β€”cΓ³digo de salida distinto de cero, stderr, handshake o timeout NBDβ€” se reportan como Nbd y conservan el contexto de ejecuciΓ³n.


πŸ“‚ Crate Structure

The project follows the standard Rust library package convention:

vmspect/
β”œβ”€β”€ Cargo.toml               # Crate configuration, metadata and dependencies
β”œβ”€β”€ readme.md                # Main documentation
β”œβ”€β”€ LICENSE                  # MIT / Apache-2.0 license
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ lib.rs               # Library entry point (public API and re-exports)
β”‚   β”œβ”€β”€ models/              # Domain types (InspectionReport, GuestInfo, Partition, etc.)
β”‚   β”‚   β”œβ”€β”€ image.rs
β”‚   β”‚   β”œβ”€β”€ options.rs       # Inspection options, progress and cancellation
β”‚   β”‚   β”œβ”€β”€ partition.rs
β”‚   β”‚   β”œβ”€β”€ software.rs      # Program and GuestInfo models
β”‚   β”‚   └── traits.rs        # Abstract traits (OsInspector, VmDriver, MemoryMapper)
β”‚   β”œβ”€β”€ parsers/             # Per-OS analyzers
β”‚   β”‚   β”œβ”€β”€ mod.rs           # OsInspector trait and polymorphic factory
β”‚   β”‚   β”œβ”€β”€ windows.rs       # NTFS extraction and Registry parsing (nt-hive)
β”‚   β”‚   β”œβ”€β”€ linux.rs         # ext4 superblock reading and DPKG database
β”‚   β”‚   └── desconocido.rs   # Handling of unrecognized operating systems
β”‚   └── vms/                 # Disk access and virtualization layer
β”‚       β”œβ”€β”€ mod.rs           # Disk access module
β”‚       β”œβ”€β”€ detector.rs       # MBR/GPT detection and FS signatures
β”‚       β”œβ”€β”€ nbd.rs           # Native NBD client and qemu-nbd connector
β”‚       β”œβ”€β”€ stream.rs        # DiskReader facade and VirtualDisk view (Read + Seek)
β”‚       └── vmdk.rs          # Native VMDK parser (sparse and descriptors)
β”œβ”€β”€ tests/                   # Integration tests
β”‚   └── integration_test.rs
└── examples/                # Ready-to-run usage examples
    β”œβ”€β”€ basic_inspection.rs
    └── batch_polling.rs

πŸ“¦ Installation

Add vmspect to your Cargo.toml:

[dependencies]

vmspect = "0.8.0"


πŸ’‘ Examples as a Library

1. Full Inspection with Guest Tools, Warnings and Progress

use std::path::Path;
use vmspect::{inspect_with_progress, Options, InspectionProgressEvent};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let path = Path::new("virtual_disk.vmdk");
    
    // Options configuration (e.g. full OS and application analysis)
    let options = Options::default();

    let report = inspect_with_progress(path, &options, |p: InspectionProgressEvent| {
        println!("[{:>3}%] {} - {}", p.percentage, p.stage, p.detail.unwrap_or_default());
    })?;

    println!("Disk format: {}", report.image.format);
    println!("Operating system: {:?}", report.operating_system);
    println!("OS details: {}", report.guest_info.formatted_os_string());
    
    // Hypervisor-agnostic Guest Tools detection (VMware, VirtualBox, QEMU, Hyper-V)
    if let Some(ref tools) = report.guest_info.guest_tools {
        if tools.present {
            println!("Guest tools: {} (version: {})", tools.kind, tools.version.as_deref().unwrap_or("N/A"));
        }
    }

    // Non-fatal warnings (graceful Registry/FS degradation)
    if !report.warnings.is_empty() {
        println!("Inspection warnings:");
        for w in &report.warnings {
            println!("  [!] {}", w);
        }
    }

    println!("Partitions detected: {}", report.partitions.len());
    println!("Installed software found: {}", report.installed_programs.len());

    for prog in report.installed_programs.iter().take(10) {
        let source = prog.source.as_deref().map(|s| format!(" [{}]", s)).unwrap_or_default();
        println!(
            " - {} (v{}) [Publisher: {}]{}",
            prog.name,
            prog.version.as_deref().unwrap_or("N/A"),
            prog.publisher.as_deref().unwrap_or("N/A"),
            source
        );
    }

    Ok(())
}

2. Advanced Extraction Options

use vmspect::Options;

// Disable application or system extraction depending on performance needs:
let light_options = Options {
    no_apps: true,           // Skip installed-software scan
    no_system: false,        // Keep OS detection and Guest Tools
    force_nbd: false,        // Use the ultra-fast native parser when available
    ..Options::default()
};

assert!(!light_options.should_analyze_apps());
assert!(light_options.should_analyze_system());

3. Concurrent Batch Processing with Polling

InspectionEngine::inspect_batch never calls UI, IPC, serialization, or user callback code. Obtain engine.progress() before starting the batch and poll its atomic snapshot from a separate thread, task, or timer. Reading progress does not block workers. A cancelled batch can finish with completed_tasks < total_tasks, so the worker handle also determines when to stop polling.

use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use vmspect::{InspectionEngine, Options};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let images = vec![
        PathBuf::from("srv1.vmdk"),
        PathBuf::from("srv2.raw"),
        PathBuf::from("srv3.qcow2"),
        PathBuf::from("srv4.vhdx"),
    ];

    let engine = Arc::new(InspectionEngine::new(Options::default()));
    let progress = engine.progress();

    let worker_engine = Arc::clone(&engine);
    let handle = std::thread::spawn(move || worker_engine.inspect_batch(images, 2));

    loop {
        let snapshot = progress.snapshot();
        let percentage = progress.completion_percentage();
        let completed = progress.completed_tasks();
        let total = progress.total_tasks();

        // Update the external UI with `snapshot`. Poll every 250-500 ms.
        println!("[{percentage:>5.1}%] {completed}/{total}");

        // `is_finished` also handles empty and partially cancelled batches.
        if handle.is_finished()
            || (snapshot.total_tasks > 0 && snapshot.completed_tasks >= snapshot.total_tasks)
        {
            break;
        }
        std::thread::sleep(Duration::from_millis(500));
    }

    let result = handle.join().expect("worker thread panicked")?;
    println!("Total reports recovered: {}", result.reports.len());
    println!("Image errors: {}", result.errors.len());
    Ok(())
}

GUI / IPC initial listing

For a GUI or IPC service, callers must always select the VM folders to discover; vmspect does not scan drive roots or any default location. Use list_vms_with_options to apply recursion, exclusions and depth limits. Perform the initial directory batch with Options { no_apps: true, ..Options::default() }, display each InspectionSummary, and run an individual full inspection only when the user opens a VM and needs installed_programs. Batch JSON is an object with reports and errors; directory JSON contains summaries unless --full-report is explicitly supplied.

4. Tauri / Async Runtime Integration

Run inspect_batch in tauri::async_runtime::spawn_blocking. Keep an Arc<InspectionEngine> in managed application state, expose a lightweight independent command that returns engine.progress().snapshot(), and have the frontend invoke that command from a 250-500 ms timer. Do not emit events from the inspection worker.

use std::path::PathBuf;
use std::sync::Arc;
use tauri::State;
use vmspect::{BatchResult, InspectionEngine, Options, ProgressSnapshot};

struct InspectionState {
    engine: Arc<InspectionEngine>,
}

#[tauri::command]
async fn inspect_vms(
    state: State<'_, InspectionState>,
    paths: Vec<PathBuf>,
) -> Result<BatchResult, String> {
    let engine = Arc::clone(&state.engine);
    tauri::async_runtime::spawn_blocking(move || engine.inspect_batch(paths, 2))
        .await
        .map_err(|error| error.to_string())?
        .map_err(|error| error.to_string())
}

#[tauri::command]
fn inspection_progress(state: State<'_, InspectionState>) -> ProgressSnapshot {
    state.engine.progress().snapshot()
}

The UI polls inspection_progress independently while inspect_vms runs. The inspection worker neither emits Tauri events nor serializes progress payloads.


πŸ–₯️ Command-Line Interface (CLI) Usage

vmspect ships a high-performance terminal binary:

# Standard inspection with human-formatted output:

vmspect /path/to/disk.vmdk


# Structured JSON output (ideal for scripts, CI/CD pipelines and forensic analysis):

vmspect /path/to/disk.qcow2 --json


# Concurrent recursive scan of a full VM directory:

vmspect /var/lib/libvirt/images/ --concurrent --recursive --workers 8


# Fast inspection skipping application extraction:

vmspect /path/to/disk.vhdx --no-apps


# Inspection skipping OS metadata:

vmspect /path/to/disk.raw --no-system


πŸ› οΈ Running Tests and Examples

Run Unit and Integration Tests:

cargo test

Run Example with a Real Disk:

cargo run --example basic_inspection -- /path/to/your/disk.vmdk


πŸ“„ License

This project is licensed under the MIT or Apache-2.0 license at your option.