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).
//! # vmspect
//!
//! `vmspect` is a Rust library designed for the static inspection, analysis and
//! information extraction of virtual machine disk images (VMDK, RAW, QCOW2, VHD, etc.).
//!
//! It can examine partition-table structures (MBR/GPT), identify the hosted operating
//! system (Windows/Linux) and extract complete lists of installed software in a
//! non-invasive way (without booting the VM or mounting the disk on the host).
//!
//! ## Key Features
//!
//! - **Hybrid access:** Native Rust parser for common formats (VMDK/RAW) with a lightweight
//!   dynamic-streaming layer via `qemu-nbd` (local TCP) for complex formats (`QCOW2`,
//!   `VHDX`, `VDI`, ...).
//! - **Multi-OS support:** Full software extraction from the Windows Registry (`NTFS`) and
//!   DPKG indexes on Linux (`EXT4`).
//! - **Agnostic extraction:** Complete and unfiltered collection of software and system
//!   metadata.
//! - **Lock-free progress reporting:** Atomic metrics that integrate cleanly with GUI
//!   front-ends (Tauri / Egui / CLI) via [`InspectionProgress`].
//! - **Graceful shutdown and result preservation:** Cooperative cancellation via
//!   [`CancellationToken`] that preserves all completed reports up to the interruption.
//! - **Open architecture:** Traits ([`VmDriver`], [`MemoryMapper`], [`OsInspector`]) and
//!   an extensible engine ([`InspectionEngine`], [`ConcurrentProcessor`]).
//!
//! ## Quick Usage Example
//!
//! ```rust,no_run
//! use std::path::Path;
//! use vmspect::prelude::*;
//!
//! fn main() -> Result<()> {
//!     let path = Path::new("virtual_disk.vmdk");
//!     let options = Options::default();
//!
//!     let report = inspect_with_progress(path, &options, |progress: InspectionProgressEvent| {
//!         println!("[{:>3}%] {} - {}", progress.percentage, progress.stage,
//!             progress.detail.unwrap_or_default());
//!     })?;
//!
//!     println!("Detected OS: {:?}", report.operating_system);
//!     println!("Found programs: {}", report.installed_programs.len());
//!
//!     Ok(())
//! }
//! ```
//!
//! ## Concurrent Batch Processing with Polling
//!
//! Obtain the shared [`InspectionProgress`] before starting the batch. Its reads use atomics, so
//! an external UI, service, or timer can poll it without blocking inspection workers. The worker
//! handle is also checked below because a cancelled batch may finish with
//! `completed_tasks < total_tasks`.
//!
//! ```rust,no_run
//! use std::path::PathBuf;
//! use std::sync::Arc;
//! use std::time::Duration;
//!
//! use vmspect::prelude::*;
//!
//! fn main() -> Result<()> {
//!     let images = vec![
//!         PathBuf::from("vm1.vmdk"),
//!         PathBuf::from("vm2.raw"),
//!         PathBuf::from("vm3.qcow2"),
//!     ];
//!
//!     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();
//!
//!         println!("[{percentage:>5.1}%] {completed}/{total}");
//!         // Update the external UI from this polling task, every 250-500 ms.
//!         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!("Preserved reports: {}", result.reports.len());
//!     println!("Image errors: {}", result.errors.len());
//!     Ok(())
//! }
//! ```

#![deny(missing_docs)]

pub mod engine;
pub mod error;
pub mod models;
pub(crate) mod operation;
pub(crate) mod parsers;
pub mod prelude;
pub mod vms;

// Flat public-API re-exports for ergonomic consumption from the crate root.
pub use crate::vms::discovery::{
    count_vms, has_vms, is_secondary_extent, is_vm_image, list_vms, list_vms_with_options,
    requires_nbd, requires_qemu, verify_image_integrity, DiscoveryOptions, DiscoveryReport,
};
pub use crate::vms::stream::VirtualDisk;
pub use engine::{ConcurrentProcessor, InspectionEngine};
pub use error::{Result, VmSpectError};
pub use models::{
    format_bytes, AnalysisResult, BatchResult, CancellationToken, FileSystem, GuestInfo,
    GuestTools, Hypervisor, ImageInfo, ImageInspectionError, InspectionOptions, InspectionProgress,
    InspectionProgressEvent, InspectionReport, InspectionSummary, MemoryMapper, OperatingSystem,
    Options, OsInspector, Partition, PartitionScheme, Program, ProgressSnapshot, Stats, VmDriver,
};

use std::path::Path;

/// Performs a full static inspection of a disk image using a plain-text callback.
///
/// This function is primarily designed for CLI applications or console scripts where
/// status output is printed line by line via text messages (`&str`).
///
/// # Parameters
///
/// - `image_path`: Reference to the [`Path`] of the virtual disk file (`.vmdk`, `.raw`, ...).
/// - `options`: Inspection configuration ([`Options`]), which controls apps/system analysis and paths.
/// - `progress`: Mutable callback receiving `&str` references with the description of the current step.
///
/// # Errors
///
/// Returns a [`VmSpectError`] if:
/// - The file at `image_path` does not exist ([`VmSpectError::ImageNotFound`]).
/// - A VMDK descriptor references a missing extent or parent disk
///   ([`VmSpectError::MissingDiskComponent`]). The error contains both the declared and
///   resolved component paths, plus the original operating-system error.
/// - The inspection was cancelled by the user ([`VmSpectError::Cancelled`]).
/// - An I/O read error occurs on the image ([`VmSpectError::Io`]).
/// - The image requires the `qemu-nbd` server and the executable is unavailable
///   ([`VmSpectError::QemuNotFound`]).
/// - Starting or communicating with `qemu-nbd` fails ([`VmSpectError::Nbd`]), including the
///   executable path, exit code and `stderr` when the subprocess provides them.
/// - The partition table or underlying file system cannot be recognized
///   ([`VmSpectError::FileSystem`]).
///
/// # Example
///
/// ```rust,no_run
/// use std::path::Path;
/// use vmspect::{inspect, Options};
///
/// let path = Path::new("disk.vmdk");
/// let options = Options::default();
///
/// let result = inspect(path, &options, &mut |message| {
///     println!("LOG: {}", message);
/// });
/// ```
pub fn inspect(
    image_path: &Path,
    options: &Options,
    progress: &mut dyn FnMut(&str),
) -> Result<InspectionReport> {
    let engine = InspectionEngine::new(options.clone());
    engine.inspect_with_progress(image_path, |p| {
        let msg = match &p.detail {
            Some(d) => format!("[{:>3}%] {} - {}", p.percentage, p.stage, d),
            None => format!("[{:>3}%] {}", p.percentage, p.stage),
        };
        progress(&msg);
    })
}

/// Performs a static inspection reporting structured progress events (`0` to `100%`).
///
/// This is the recommended option for integrations with GUI environments (such as **Tauri**,
/// **Electron** or **Egui**), as it emits a serializable [`InspectionProgressEvent`] with
/// bounded percentages and descriptions of the current stage.
///
/// # Parameters
///
/// - `image_path`: Reference to the [`Path`] of the virtual disk image.
/// - `options`: Engine configuration ([`Options`]).
/// - `progress_callback`: Closure implementing `FnMut(InspectionProgressEvent)`, invoked
///   sequentially during the analysis.
///
/// # Emitted Percentage Flow
///
/// - **`5% - 15%`**: Image format identification and read-backend setup.
/// - **`25% - 45%`**: Partitioning scheme (MBR/GPT) and file-system signature detection.
/// - **`55%`**: Deep OS analysis (NTFS Registry / DPKG package extraction).
/// - **`90%`**: Report generation and consolidation.
/// - **`100%`**: Final report delivery and performance metrics computation.
pub fn inspect_with_progress<F>(
    image_path: &Path,
    options: &Options,
    progress_callback: F,
) -> Result<InspectionReport>
where
    F: FnMut(InspectionProgressEvent),
{
    let engine = InspectionEngine::new(options.clone());
    engine.inspect_with_progress(image_path, progress_callback)
}