Fix: Hinzufügen von Locking-Mechanismen und verbessertes Mount-/Unmount-Handling

- Globaler RW-Lock eingeführt, um parallele Statusabfragen und Mount-/Unmount-Operationen zu synchronisieren.
- Funktionsspezifische Mutexe für Mount und Unmount hinzugefügt, um konsistente Lock-Reihenfolgen zu gewährleisten.
- Verbesserte Fehlerbehandlung beim Mounten, inklusive Umgang mit „already mounted“ und „busy“ Fehlern.
- Refactoring zur Nutzung von `/proc/mounts` statt `mount`-Befehl auf Nicht-Windows-Systemen.
- Normalisierung von Mount-Pfaden implementiert.
This commit is contained in:
2025-08-23 17:30:07 +02:00
parent bd8d9709c5
commit c57ffe4580
2 changed files with 153 additions and 36 deletions

View File

@@ -1,5 +1,12 @@
use crate::log::{log, LogLevel};
use crate::filesystem::mounted::{/* is_mounted, */ FS_MOUNT_GUARD};
use crate::log::{LogLevel, log};
use std::process::Command;
use std::sync::{Mutex, OnceLock, RwLock};
use std::thread::sleep;
use std::time::Duration;
static GUARD_MOUNT: OnceLock<Mutex<()>> = OnceLock::new();
static GUARD_UNMOUNT: OnceLock<Mutex<()>> = OnceLock::new();
/// Führt die Einbindung eines Dateisystems durch.
///
@@ -8,6 +15,16 @@ use std::process::Command;
/// * `network_path` - Der Netzwerkpfad zum einzubindenden Dateisystem
/// * `mount_type` - Der Mount-Typ, z.B. "nfs" oder "davfs2"
pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
// 1) Globalen Write-Lock halten (blockiert parallele Statusabfragen)
let _fs_write = FS_MOUNT_GUARD
.get_or_init(|| RwLock::new(()))
.write()
.expect("mount rwlock poisoned");
// 2) Danach funktionsspezifischen Mutex sperren (konsistente Lock-Reihenfolge!)
let m = GUARD_MOUNT.get_or_init(|| Mutex::new(()));
let _lock = m.lock().expect("Mutex poisoned");
log(
"mount",
&*format!(
@@ -53,12 +70,7 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
#[cfg(not(target_os = "windows"))]
{
let mount_point: &str = if mount_point.ends_with('/') {
mount_point
} else {
&*format!("{}/", mount_point)
};
// Verzeichnis vorbereiten
let output = Command::new("mkdir")
.arg("-p")
.arg(mount_point)
@@ -103,9 +115,28 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
.output()
.expect("Failed to execute mount command");
sleep(Duration::from_secs(1));
if output.status.success() {
log("mount", "Filesystem mounted successfully.", LogLevel::Info);
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
// Häufige „schon gemountet“/Busy-Indikatoren tolerant behandeln
let already_or_busy = stderr.contains("already mounted")
|| stderr.contains("is busy")
|| stderr.contains("Device or resource busy")
|| stderr.contains("EBUSY");
if already_or_busy {
log(
"mount",
"Filesystem appears to be already mounted or mountpoint is busy. Treating as no-op.",
LogLevel::Info,
);
log("mount", &*stderr, LogLevel::Debug);
return;
}
log(
"mount",
&*format!(
@@ -114,11 +145,7 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
),
LogLevel::Error,
);
log(
"mount",
&*String::from_utf8_lossy(&output.stderr),
LogLevel::Debug,
);
log("mount", &*stderr, LogLevel::Debug);
}
}
}
@@ -128,6 +155,16 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
/// # Parameter
/// * `mount_point` - Der lokale Verzeichnispfad, von dem das Dateisystem ausgehängt werden soll
pub fn unmount(mount_point: &str) {
// 1) Globalen Write-Lock halten (blockiert parallele Statusabfragen)
let _fs_write = FS_MOUNT_GUARD
.get_or_init(|| RwLock::new(()))
.write()
.expect("mount rwlock poisoned");
// 2) Danach funktionsspezifischen Mutex sperren (konsistente Lock-Reihenfolge!)
let m = GUARD_UNMOUNT.get_or_init(|| Mutex::new(()));
let _lock = m.lock().expect("Mutex poisoned");
#[cfg(target_os = "windows")]
{
let output = Command::new("Remove-PSDrive")

View File

@@ -1,4 +1,14 @@
use std::process::Command;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::sync::{OnceLock, RwLock};
// Globaler RW-Lock für Mount-Operationen und Statusabfragen
pub static FS_MOUNT_GUARD: OnceLock<RwLock<()>> = OnceLock::new();
fn guard() -> &'static RwLock<()> {
FS_MOUNT_GUARD.get_or_init(|| RwLock::new(()))
}
/// Überprüft, ob ein Dateisystem am angegebenen Mount-Point mit dem spezifizierten Mount-Typ eingebunden ist.
///
@@ -10,13 +20,20 @@ use std::process::Command;
/// * `true` wenn das Dateisystem mit dem angegebenen Typ eingebunden ist
/// * `false` wenn das Dateisystem nicht oder mit einem anderen Typ eingebunden ist
pub fn is_mounted_as(mount_point: &str, mount_type: &str) -> bool {
// Während Statusabfragen nur Read-Lock halten
let _read_guard = guard().read().expect("mount rwlock poisoned");
#[cfg(target_os = "windows")]
{
use std::process::Command;
let drive_letter = &mount_point[0..1];
let output = Command::new("powershell")
.args([
"-Command",
&format!("(Get-PSDrive -Name {} -PSProvider 'FileSystem').Description", drive_letter)
&format!(
"(Get-PSDrive -Name {} -PSProvider 'FileSystem').Description",
drive_letter
),
])
.output()
.expect("Failed to execute get-psdrive command");
@@ -31,19 +48,18 @@ pub fn is_mounted_as(mount_point: &str, mount_type: &str) -> bool {
#[cfg(not(target_os = "windows"))]
{
let output = Command::new("mount")
.output()
.expect("Failed to execute mount command");
let norm_mp = normalize_mount_point(mount_point);
if !output.status.success() {
return false;
for entry in read_proc_mounts() {
let (mp, fstype, _opts, _src) = entry;
if mp == norm_mp {
if type_matches(mount_type, &fstype, &_opts) {
return true;
}
}
}
let mount_output = String::from_utf8_lossy(&output.stdout);
mount_output
.lines()
.any(|line| line.contains(mount_point) && line.contains(mount_type))
false
}
}
@@ -56,13 +72,20 @@ pub fn is_mounted_as(mount_point: &str, mount_type: &str) -> bool {
/// * `true` wenn ein Dateisystem am angegebenen Pfad eingebunden ist
/// * `false` wenn kein Dateisystem eingebunden ist
pub fn is_mounted(mount_point: &str) -> bool {
// Während Statusabfragen nur Read-Lock halten
let _read_guard = guard().read().expect("mount rwlock poisoned");
#[cfg(target_os = "windows")]
{
use std::process::Command;
let drive_letter = &mount_point[0..1];
let output = Command::new("powershell")
.args([
"-Command",
&format!("(Get-PSDrive -Name {} -PSProvider 'FileSystem')", drive_letter)
&format!(
"(Get-PSDrive -Name {} -PSProvider 'FileSystem')",
drive_letter
),
])
.output()
.expect("Failed to execute get-psdrive command");
@@ -72,16 +95,73 @@ pub fn is_mounted(mount_point: &str) -> bool {
#[cfg(not(target_os = "windows"))]
{
let output = Command::new("mount")
.output()
.expect("Failed to execute mount command");
if !output.status.success() {
return false;
}
let mount_output = String::from_utf8_lossy(&output.stdout);
mount_output.lines().any(|line| line.contains(mount_point))
let norm_mp = normalize_mount_point(mount_point);
read_proc_mounts()
.into_iter()
.any(|(mp, _, _, _)| mp == norm_mp)
}
}
#[cfg(not(target_os = "windows"))]
fn read_proc_mounts() -> Vec<(String, String, String, String)> {
// Liefert Tupel: (mount_point, fstype, options, source)
let file = match File::open("/proc/mounts") {
Ok(f) => f,
Err(_) => return Vec::new(),
};
let reader = BufReader::new(file);
let mut result = Vec::new();
for line in reader.lines().flatten() {
// Format /proc/mounts:
// fs_spec fs_file fs_vfstype fs_mntops fs_freq fs_passno
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 6 {
continue;
}
let fs_spec = parts[0].to_string();
let fs_file = parts[1].to_string();
let fs_vfstype = parts[2].to_string();
let fs_mntops = parts[3].to_string();
result.push((fs_file, fs_vfstype, fs_mntops, fs_spec));
}
result
}
#[cfg(not(target_os = "windows"))]
fn normalize_mount_point(p: &str) -> String {
// Entfernt redundante Slashes am Ende (außer bei "/") und canonicalized soweit möglich.
if p == "/" {
return "/".to_string();
}
let trimmed = p.trim_end_matches('/');
// Versuche, realpath zu bilden, falle sonst auf trimmed zurück
let path = Path::new(trimmed);
path.canonicalize()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| trimmed.to_string())
}
#[cfg(not(target_os = "windows"))]
fn type_matches(expected: &str, actual_fstype: &str, options: &str) -> bool {
// Normalisiere erwartete Typfamilien
match expected {
// NFS kann als nfs oder nfs4 erscheinen
"nfs" | "nfs4" => actual_fstype == "nfs" || actual_fstype == "nfs4",
// davfs/webdav erscheint häufig als fuse.davfs (oder fuse mit helper=davfs)
"webdav" | "davfs" | "davfs2" => {
actual_fstype == "fuse.davfs"
|| actual_fstype == "davfs"
|| (actual_fstype == "fuse" && options.contains("helper=davfs"))
}
// CIFS/Samba Alias
"cifs" | "smb" | "smb3" => actual_fstype == "cifs" || actual_fstype == "smb3",
// Fallback: exakter Vergleich
other => other == actual_fstype,
}
}