liblitho 0.2.0

cli tool to flash/clone the images to storage devices
Documentation
//! Windows volume lock/dismount preparation.
//!
//! [`WindowsVolumeGuard`] is the exclusive-access session held for the lifetime
//! of a write handle. Constructed via [`VolumeOps::prepare_for_io`].

use crate::devices::{format_device_busy_error, DeviceError};
use crate::platform::traits::{DeviceSafety, VolumeOps};

use super::access::PhysicalDriveIoSession;
use super::devices as win_devices;
use super::WindowsPlatform;

/// RAII volume locks for a physical drive write session.
pub type WindowsVolumeGuard = PhysicalDriveIoSession;

impl VolumeOps for WindowsPlatform {
    type Guard = WindowsVolumeGuard;

    fn prepare_for_io(path: &str, auto_unmount: bool) -> Result<Self::Guard, DeviceError> {
        let mounts = WindowsPlatform::list_mounts(path)?;
        if !mounts.is_empty() && !auto_unmount {
            return Err(DeviceError::busy(format_device_busy_error(path, &mounts)));
        }

        let windows_device = win_devices::canonical_physical_drive_path(path)
            .map_err(DeviceError::invalid_path)?;

        // Holds volume locks until the writer is dropped.
        super::access::prepare_physical_drive_for_write(&windows_device)
            .map_err(DeviceError::unmount)
    }

    fn preflight_for_io(path: &str, auto_unmount: bool) -> Result<(), DeviceError> {
        let mounts = WindowsPlatform::list_mounts(path)?;
        if mounts.is_empty() {
            return Ok(());
        }
        if !auto_unmount {
            return Err(DeviceError::busy(format_device_busy_error(path, &mounts)));
        }
        // Unmount without holding locks across the gap until writer open.
        Self::unmount_volumes(path)?;
        Ok(())
    }

    fn unmount_volumes(device_path: &str) -> Result<Vec<String>, DeviceError> {
        let path = win_devices::canonical_physical_drive_path(device_path)
            .map_err(DeviceError::invalid_path)?;
        let disk_index = win_devices::parse_physical_drive_index(&path).ok_or_else(|| {
            DeviceError::invalid_path(format!("Invalid physical drive path: {device_path}"))
        })?;
        let targets = win_devices::volume_dismount_targets_for_disk_index(disk_index)
            .map_err(DeviceError::query)?;

        let mut dismounted = Vec::new();
        let mut failures = Vec::new();
        for (volume_path, label) in targets {
            match super::access::dismount_volume_at_path(&volume_path, &label) {
                Ok(()) => dismounted.push(label),
                Err(err) => failures.push(err),
            }
        }
        if !failures.is_empty() {
            return Err(DeviceError::unmount(format!(
                "Could not dismount all volumes on {device_path}: {}",
                failures.join("; ")
            )));
        }
        Ok(dismounted)
    }
}