fix(sre): implement PRR-01 stale mount self-healing and PRR-02 win32 console close handler

This commit is contained in:
2026-09-09 21:45:35 +02:00
parent 70d03df676
commit a8fdfc674e
2 changed files with 152 additions and 2 deletions
+35 -2
View File
@@ -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<()> { 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 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"]) .args(["use", drive_str, &url, "/persistent:no"])
.output() .output()
.context("Fehler beim Ausführen des Befehls 'net use'")?; .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() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout); 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!(" [{}] Drücke [Ctrl+C] oder nutze das Tray-Icon zum Beenden.", ui::yellow("Tipp"));
println!(); 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! { tokio::select! {
res = tokio::signal::ctrl_c() => { res = tokio::signal::ctrl_c() => {
let _ = res; let _ = res;
println!(); println!();
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]")); 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() => { _ = tray_shutdown_rx.recv() => {
println!(); println!();
println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]")); println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]"));
+117
View File
@@ -419,6 +419,114 @@ pub fn start_session_lock_monitor(
Ok(SessionLockGuard) Ok(SessionLockGuard)
} }
// ─────────────────────────────────────────────────────────────
// Console Close Event Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT)
// ─────────────────────────────────────────────────────────────
#[cfg(windows)]
static CONSOLE_CTRL_TX: std::sync::Mutex<Option<tokio::sync::mpsc::Sender<()>>> = std::sync::Mutex::new(None);
#[cfg(windows)]
static CONSOLE_CTRL_DRIVE: std::sync::Mutex<Option<char>> = 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<unsafe extern "system" fn(u32) -> 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<ConsoleCtrlGuard> {
#[cfg(windows)]
{
extern "system" {
fn SetConsoleCtrlHandler(
handler: Option<unsafe extern "system" fn(u32) -> 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -438,4 +546,13 @@ mod tests {
// Dropping monitor closes message loop and joins thread cleanly // Dropping monitor closes message loop and joins thread cleanly
drop(monitor); 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);
}
} }