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:
@@ -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::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.
|
/// Führt die Einbindung eines Dateisystems durch.
|
||||||
///
|
///
|
||||||
@@ -8,6 +15,16 @@ use std::process::Command;
|
|||||||
/// * `network_path` - Der Netzwerkpfad zum einzubindenden Dateisystem
|
/// * `network_path` - Der Netzwerkpfad zum einzubindenden Dateisystem
|
||||||
/// * `mount_type` - Der Mount-Typ, z.B. "nfs" oder "davfs2"
|
/// * `mount_type` - Der Mount-Typ, z.B. "nfs" oder "davfs2"
|
||||||
pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
|
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(
|
log(
|
||||||
"mount",
|
"mount",
|
||||||
&*format!(
|
&*format!(
|
||||||
@@ -53,12 +70,7 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
let mount_point: &str = if mount_point.ends_with('/') {
|
// Verzeichnis vorbereiten
|
||||||
mount_point
|
|
||||||
} else {
|
|
||||||
&*format!("{}/", mount_point)
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = Command::new("mkdir")
|
let output = Command::new("mkdir")
|
||||||
.arg("-p")
|
.arg("-p")
|
||||||
.arg(mount_point)
|
.arg(mount_point)
|
||||||
@@ -103,9 +115,28 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
|
|||||||
.output()
|
.output()
|
||||||
.expect("Failed to execute mount command");
|
.expect("Failed to execute mount command");
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(1));
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
log("mount", "Filesystem mounted successfully.", LogLevel::Info);
|
log("mount", "Filesystem mounted successfully.", LogLevel::Info);
|
||||||
} else {
|
} 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(
|
log(
|
||||||
"mount",
|
"mount",
|
||||||
&*format!(
|
&*format!(
|
||||||
@@ -114,11 +145,7 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
|
|||||||
),
|
),
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
);
|
);
|
||||||
log(
|
log("mount", &*stderr, LogLevel::Debug);
|
||||||
"mount",
|
|
||||||
&*String::from_utf8_lossy(&output.stderr),
|
|
||||||
LogLevel::Debug,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +155,16 @@ pub fn mount(mount_point: &str, network_path: &str, mount_type: &str) {
|
|||||||
/// # Parameter
|
/// # Parameter
|
||||||
/// * `mount_point` - Der lokale Verzeichnispfad, von dem das Dateisystem ausgehängt werden soll
|
/// * `mount_point` - Der lokale Verzeichnispfad, von dem das Dateisystem ausgehängt werden soll
|
||||||
pub fn unmount(mount_point: &str) {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
let output = Command::new("Remove-PSDrive")
|
let output = Command::new("Remove-PSDrive")
|
||||||
|
|||||||
@@ -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.
|
/// Ü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
|
/// * `true` wenn das Dateisystem mit dem angegebenen Typ eingebunden ist
|
||||||
/// * `false` wenn das Dateisystem nicht oder mit einem anderen 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 {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
use std::process::Command;
|
||||||
let drive_letter = &mount_point[0..1];
|
let drive_letter = &mount_point[0..1];
|
||||||
let output = Command::new("powershell")
|
let output = Command::new("powershell")
|
||||||
.args([
|
.args([
|
||||||
"-Command",
|
"-Command",
|
||||||
&format!("(Get-PSDrive -Name {} -PSProvider 'FileSystem').Description", drive_letter)
|
&format!(
|
||||||
|
"(Get-PSDrive -Name {} -PSProvider 'FileSystem').Description",
|
||||||
|
drive_letter
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to execute get-psdrive command");
|
.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"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
let output = Command::new("mount")
|
let norm_mp = normalize_mount_point(mount_point);
|
||||||
.output()
|
|
||||||
.expect("Failed to execute mount command");
|
|
||||||
|
|
||||||
if !output.status.success() {
|
for entry in read_proc_mounts() {
|
||||||
return false;
|
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);
|
false
|
||||||
|
|
||||||
mount_output
|
|
||||||
.lines()
|
|
||||||
.any(|line| line.contains(mount_point) && line.contains(mount_type))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
/// * `true` wenn ein Dateisystem am angegebenen Pfad eingebunden ist
|
||||||
/// * `false` wenn kein Dateisystem eingebunden ist
|
/// * `false` wenn kein Dateisystem eingebunden ist
|
||||||
pub fn is_mounted(mount_point: &str) -> bool {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
use std::process::Command;
|
||||||
let drive_letter = &mount_point[0..1];
|
let drive_letter = &mount_point[0..1];
|
||||||
let output = Command::new("powershell")
|
let output = Command::new("powershell")
|
||||||
.args([
|
.args([
|
||||||
"-Command",
|
"-Command",
|
||||||
&format!("(Get-PSDrive -Name {} -PSProvider 'FileSystem')", drive_letter)
|
&format!(
|
||||||
|
"(Get-PSDrive -Name {} -PSProvider 'FileSystem')",
|
||||||
|
drive_letter
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to execute get-psdrive command");
|
.expect("Failed to execute get-psdrive command");
|
||||||
@@ -72,16 +95,73 @@ pub fn is_mounted(mount_point: &str) -> bool {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
let output = Command::new("mount")
|
let norm_mp = normalize_mount_point(mount_point);
|
||||||
.output()
|
read_proc_mounts()
|
||||||
.expect("Failed to execute mount command");
|
.into_iter()
|
||||||
|
.any(|(mp, _, _, _)| mp == norm_mp)
|
||||||
if !output.status.success() {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
let mount_output = String::from_utf8_lossy(&output.stdout);
|
fn read_proc_mounts() -> Vec<(String, String, String, String)> {
|
||||||
|
// Liefert Tupel: (mount_point, fstype, options, source)
|
||||||
mount_output.lines().any(|line| line.contains(mount_point))
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user