From a8fdfc674e44a05880b8116a5b82fa7feb445108 Mon Sep 17 00:00:00 2001 From: harald Date: Wed, 9 Sep 2026 21:45:35 +0200 Subject: [PATCH] fix(sre): implement PRR-01 stale mount self-healing and PRR-02 win32 console close handler --- src/mount.rs | 37 +++++++++++++++- src/windows.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/src/mount.rs b/src/mount.rs index 912a5d1..c9979c8 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -59,11 +59,36 @@ pub fn unmount_drive(drive_letter: char) -> Result<()> { fn run_net_use_mount(drive_str: &str, port: u16, session_token: &str) -> Result<()> { let url = format!("http://127.0.0.1:{}/{}/", port, session_token); - let output = Command::new("net") + let mut output = Command::new("net") .args(["use", drive_str, &url, "/persistent:no"]) .output() .context("Fehler beim Ausführen des Befehls 'net use'")?; + // PRR-01: Selbstreparatur bei verwaister Zuordnung nach unsauberem Vorläufer (Systemfehler 85) + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let is_already_in_use = stderr.contains("85") + || stderr.contains("bereits verwendet") + || stderr.contains("already in use") + || stdout.contains("85") + || stdout.contains("bereits verwendet") + || stdout.contains("already in use"); + + if is_already_in_use { + debug!("Verwaiste Zuordnung für {} entdeckt — führe automatische Bereinigung durch...", drive_str); + let _ = Command::new("net") + .args(["use", drive_str, "/delete", "/y"]) + .output(); + + // Zweiter Versuch nach automatischer Bereinigung + output = Command::new("net") + .args(["use", drive_str, &url, "/persistent:no"]) + .output() + .context("Fehler beim erneuten Ausführen des Befehls 'net use'")?; + } + } + if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); @@ -318,13 +343,21 @@ pub async fn mount_container( println!(" [{}] Drücke [Ctrl+C] oder nutze das Tray-Icon zum Beenden.", ui::yellow("Tipp")); println!(); - // Warten auf Beendigungssignal (Ctrl+C, Tray-Klick, Inaktivität, Win+L) + // Windows Console Close Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT) + let (console_close_tx, mut console_close_rx) = tokio::sync::mpsc::channel::<()>(1); + let _console_guard = crate::windows::start_console_ctrl_monitor(console_close_tx, drive_letter).ok(); + + // Warten auf Beendigungssignal (Ctrl+C, Tray-Klick, Inaktivität, Win+L, Konsolenfenster-Schließen) tokio::select! { res = tokio::signal::ctrl_c() => { let _ = res; println!(); println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]")); } + _ = console_close_rx.recv() => { + println!(); + println!(" {} Konsolenfenster wird geschlossen — sichere Trennung ausgeführt!", ui::yellow("[!]")); + } _ = tray_shutdown_rx.recv() => { println!(); println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]")); diff --git a/src/windows.rs b/src/windows.rs index 8672e3a..b1e7d57 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -419,6 +419,114 @@ pub fn start_session_lock_monitor( Ok(SessionLockGuard) } +// ───────────────────────────────────────────────────────────── +// Console Close Event Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT) +// ───────────────────────────────────────────────────────────── + +#[cfg(windows)] +static CONSOLE_CTRL_TX: std::sync::Mutex>> = std::sync::Mutex::new(None); +#[cfg(windows)] +static CONSOLE_CTRL_DRIVE: std::sync::Mutex> = std::sync::Mutex::new(None); + +#[cfg(windows)] +unsafe extern "system" fn console_ctrl_routine(ctrl_type: u32) -> i32 { + const CTRL_C_EVENT: u32 = 0; + const CTRL_BREAK_EVENT: u32 = 1; + const CTRL_CLOSE_EVENT: u32 = 2; + const CTRL_LOGOFF_EVENT: u32 = 5; + const CTRL_SHUTDOWN_EVENT: u32 = 6; + + match ctrl_type { + CTRL_CLOSE_EVENT | CTRL_LOGOFF_EVENT | CTRL_SHUTDOWN_EVENT => { + // 1. Sofortiges Notfall-Unmount direkt aus dem Win32-Callback ausführen + if let Ok(guard) = CONSOLE_CTRL_DRIVE.lock() { + if let Some(dl) = *guard { + let drive_str = format!("{}:", dl.to_ascii_uppercase()); + let _ = std::process::Command::new("net") + .args(["use", &drive_str, "/delete", "/y"]) + .output(); + } + } + // 2. Asynchronen Shutdown-Kanal benachrichtigen (für SQLite WAL Checkpoint) + if let Ok(guard) = CONSOLE_CTRL_TX.lock() { + if let Some(ref tx) = *guard { + let _ = tx.blocking_send(()); + } + } + 1 // TRUE: Event abgearbeitet + } + CTRL_C_EVENT | CTRL_BREAK_EVENT => { + // Ctrl+C wird primär von tokio::signal::ctrl_c() behandelt + 0 + } + _ => 0, + } +} + +/// RAII Guard zur sauberen Deregistrierung des Win32 Console-Control-Handlers. +pub struct ConsoleCtrlGuard; + +impl Drop for ConsoleCtrlGuard { + fn drop(&mut self) { + #[cfg(windows)] + { + extern "system" { + fn SetConsoleCtrlHandler( + handler: Option i32>, + add: i32, + ) -> i32; + } + unsafe { + SetConsoleCtrlHandler(Some(console_ctrl_routine), 0); + } + if let Ok(mut guard) = CONSOLE_CTRL_TX.lock() { + *guard = None; + } + if let Ok(mut guard) = CONSOLE_CTRL_DRIVE.lock() { + *guard = None; + } + } + } +} + +/// Registriert einen Win32 Console Control Handler für CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT +/// und CTRL_SHUTDOWN_EVENT, um verwaiste Netzlaufwerke beim Schließen des Konsolenfensters zu verhindern. +pub fn start_console_ctrl_monitor( + shutdown_tx: tokio::sync::mpsc::Sender<()>, + drive_letter: char, +) -> Result { + #[cfg(windows)] + { + extern "system" { + fn SetConsoleCtrlHandler( + handler: Option i32>, + add: i32, + ) -> i32; + } + + if let Ok(mut guard) = CONSOLE_CTRL_TX.lock() { + *guard = Some(shutdown_tx); + } + if let Ok(mut guard) = CONSOLE_CTRL_DRIVE.lock() { + *guard = Some(drive_letter); + } + + let res = unsafe { SetConsoleCtrlHandler(Some(console_ctrl_routine), 1) }; + if res == 0 { + bail!("Konnte Win32 SetConsoleCtrlHandler nicht registrieren"); + } + + Ok(ConsoleCtrlGuard) + } + + #[cfg(not(windows))] + { + let _ = shutdown_tx; + let _ = drive_letter; + Ok(ConsoleCtrlGuard) + } +} + #[cfg(test)] mod tests { use super::*; @@ -438,4 +546,13 @@ mod tests { // Dropping monitor closes message loop and joins thread cleanly drop(monitor); } + + #[test] + fn test_console_ctrl_monitor_lifecycle() { + let (tx, _rx) = tokio::sync::mpsc::channel(1); + let monitor = start_console_ctrl_monitor(tx, 'Z'); + assert!(monitor.is_ok()); + drop(monitor); + } } +