10 Commits
Author SHA1 Message Date
DragonSlayer_14 f871353795 Ref: Aktualisiert Projektmetadaten in Cargo.toml (Version, Edition, Beschreibung, Debug-Einstellungen) 2025-08-23 17:37:30 +02:00
DragonSlayer_14 acc7a887bc Feat: Ergänzt Abhängigkeiten nmap und $auto in Cargo.toml 2025-08-23 17:31:49 +02:00
DragonSlayer_14 c4a8b74f8d Fix: Entfernt doppelte Leerzeilen in .gitignore und fügt .idea hinzu 2025-08-23 17:31:38 +02:00
DragonSlayer_14 c6cfc4cb3b Docs: Ergänzt Build-Anleitung für Debian in README.md 2025-08-23 17:30:36 +02:00
DragonSlayer_14 c2d92bb807 Fix: Bereinigt Mount-/Unmount-Logik und verschiebt WebDAV-Anmeldeinformationen in richtigen Ablauf 2025-08-23 17:30:28 +02:00
DragonSlayer_14 c57ffe4580 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.
2025-08-23 17:30:07 +02:00
DragonSlayer_14 bd8d9709c5 Ref: Ergänzt Projekt-Metadaten in Cargo.toml mit Readme, Lizenz, Repository und Debian-spezifischen Einstellungen 2025-08-21 17:10:39 +02:00
DragonSlayer_14 918b5ff1d1 Ref: Aktualisiert und erweitert Projektbeschreibung in README.md für klarere Funktionsübersicht 2025-08-21 17:10:22 +02:00
DragonSlayer_14 cf9132fbb1 Ref: Verbessert Logging bei Geräteadress- und MAC-Ermittlung, fügt zusätzliche Validierung und Fehlermeldungen hinzu 2025-08-21 16:24:44 +02:00
DragonSlayer_14 14876c7e1e Ref: Optimiert IP-Adressprüfung und verbessert MAC-Ermittlung durch zusätzliche Validierung und Mehrfachversuche 2025-08-21 16:24:32 +02:00
7 changed files with 450 additions and 110 deletions
+1 -2
View File
@@ -505,8 +505,7 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
.idea
# Added by cargo # Added by cargo
+15 -1
View File
@@ -1,11 +1,25 @@
[package] [package]
name = "SmartMount" name = "SmartMount"
version = "0.1.0" version = "0.2.0"
edition = "2024" edition = "2024"
authors = ['DragonSlayer_14'] authors = ['DragonSlayer_14']
readme = "README.md"
license-file = "LICENSE"
repository = "https://gitea.creative-dragonslayer.de/creative-dragonslayer/SmartMount"
description = "SmartMount ist ein innovatives Tool zur intelligenten Verwaltung von Netzwerk-Dateisystemen. Es ermöglicht das automatische Einbinden von Netzwerk-Freigaben über das lokale Netzwerk und wechselt nahtlos zu einer Cloud-basierten Lösung, falls keine lokale Verbindung verfügbar ist. Durch diese hybride Architektur wird ein zuverlässiger Zugriff auf wichtige Daten sichergestellt - egal ob zu Hause oder unterwegs."
[dependencies] [dependencies]
time = { version="0.3.41", features = ["formatting", "macros", "local-offset"] } time = { version="0.3.41", features = ["formatting", "macros", "local-offset"] }
serde = { version="1.0.219", features = ["derive"] } serde = { version="1.0.219", features = ["derive"] }
confy = "1.0.0" confy = "1.0.0"
libc = "1.0.0-alpha.1" libc = "1.0.0-alpha.1"
[profile.release]
debug = "none"
[package.metadata.deb]
section = "utils"
priority = "optional"
provides = ["smartmount"]
depends = ["nmap", "$auto"]
+12 -1
View File
@@ -1,3 +1,14 @@
# SmartMount # SmartMount
Diese Projekt hängt ein Dateisystem über das lokale Netzwerk ein oder als Fallback eine Cloud, sollte lokal nichts verfügbar sein. SmartMount ist ein innovatives Tool zur intelligenten Verwaltung von Netzwerk-Dateisystemen. Es ermöglicht das
automatische Einbinden von Netzwerk-Freigaben über das lokale Netzwerk und wechselt nahtlos zu einer Cloud-basierten
Lösung, falls keine lokale Verbindung verfügbar ist. Durch diese hybride Architektur wird ein zuverlässiger Zugriff auf
wichtige Daten sichergestellt - egal ob zu Hause oder unterwegs.
### Build:
Für Debian muss `cargo-deb` installiert sein, dann kann man das Paket mit diesem Paket builden:
```shell
cargo deb --separate-debug-symbols --compress-debug-symbols
```
+49 -12
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::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")
+100 -20
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. /// Ü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() { #[cfg(not(target_os = "windows"))]
return false; 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"))
} }
let mount_output = String::from_utf8_lossy(&output.stdout); // CIFS/Samba Alias
"cifs" | "smb" | "smb3" => actual_fstype == "cifs" || actual_fstype == "smb3",
mount_output.lines().any(|line| line.contains(mount_point)) // Fallback: exakter Vergleich
other => other == actual_fstype,
} }
} }
+188 -41
View File
@@ -1,69 +1,113 @@
use crate::config::{get_config, modify_config, Storage}; use crate::config::{Storage, get_config, modify_config};
use crate::filesystem::credentials::save_credentials_webdav; use crate::filesystem::credentials::save_credentials_webdav;
use crate::filesystem::mount::{mount, unmount}; use crate::filesystem::mount::{mount, unmount};
use crate::filesystem::mounted::{is_mounted, is_mounted_as}; use crate::filesystem::mounted::{is_mounted, is_mounted_as};
use crate::log::log;
use crate::log::LogLevel; use crate::log::LogLevel;
use crate::log::log;
use crate::network::network_interface::{get_active_network_interface, get_interface_ip_address}; use crate::network::network_interface::{get_active_network_interface, get_interface_ip_address};
use crate::network::utils::{get_ip_from_mac, get_mac_from_ip, get_network_address, is_reachable, wake_on_land}; use crate::network::utils::{
get_ip_from_mac, get_mac_from_ip, get_network_address, is_reachable, wake_on_land,
};
use crate::sudo::{is_run_as_root, run_as_root}; use crate::sudo::{is_run_as_root, run_as_root};
use std::process::exit; use std::process::exit;
use std::thread; use std::thread;
use std::thread::{sleep, JoinHandle}; use std::thread::{JoinHandle, sleep};
use std::time::Duration; use std::time::Duration;
mod config;
mod filesystem;
mod log; mod log;
mod network; mod network;
mod config;
mod program; mod program;
mod filesystem;
mod sudo; mod sudo;
fn main() { fn main() {
log("main", "========== PROGRAM START ==========", LogLevel::Info); log(
"main",
"========== PROGRAM START ==========",
LogLevel::Info,
);
if !is_run_as_root() { if !is_run_as_root() {
log("main", "Program is not run as root. Trying to run as root...", LogLevel::Warn); log(
"main",
"Program is not run as root. Trying to run as root...",
LogLevel::Warn,
);
run_as_root(); run_as_root();
} }
let network_interface : String; let network_interface: String;
let mut count : i32 = 0; let mut count: i32 = 0;
loop { loop {
let interface_str : String = get_active_network_interface().unwrap().trim().to_string(); let interface_str: String = get_active_network_interface().unwrap().trim().to_string();
if !interface_str.is_empty() { if !interface_str.is_empty() {
network_interface = interface_str; network_interface = interface_str;
break; break;
} else if count >= 10 { } else if count >= 10 {
log("main", "Couldn't find active network card, exiting.", LogLevel::Error); log(
"main",
"Couldn't find active network card, exiting.",
LogLevel::Error,
);
exit(1); exit(1);
} }
log("main", "No active network card found, waiting 1 second.", LogLevel::Warn); log(
count = count+1; "main",
"No active network card found, waiting 1 second.",
LogLevel::Warn,
);
count = count + 1;
sleep(Duration::from_secs(1)); sleep(Duration::from_secs(1));
} }
log("main", &*format!("Active network interface found: {}", network_interface), LogLevel::Info); log(
"main",
&*format!("Active network interface found: {}", network_interface),
LogLevel::Info,
);
let interface_address = get_interface_ip_address(network_interface.as_str()).unwrap().trim().to_string(); let interface_address = get_interface_ip_address(network_interface.as_str())
log("main", &*format!("Interface address: {}", interface_address), LogLevel::Info); .unwrap()
.trim()
.to_string();
log(
"main",
&*format!("Interface address: {}", interface_address),
LogLevel::Info,
);
let network_address = get_network_address(interface_address.as_str()).unwrap().trim().to_string(); let network_address = get_network_address(interface_address.as_str())
log("main", &*format!("Network address: {}", network_address), LogLevel::Info); .unwrap()
.trim()
.to_string();
log(
"main",
&*format!("Network address: {}", network_address),
LogLevel::Info,
);
count = 0; count = 0;
loop { loop {
if is_reachable(network_address.as_str()) { if is_reachable(network_address.as_str()) {
break; break;
} else if count >= 10 { } else if count >= 10 {
log("main", "Couldn't reach network address, exiting.", LogLevel::Error); log(
"main",
"Couldn't reach network address, exiting.",
LogLevel::Error,
);
exit(1); exit(1);
} }
log("main", "Network address not reachable, waiting 1 second.", LogLevel::Warn); log(
count = count+1; "main",
"Network address not reachable, waiting 1 second.",
LogLevel::Warn,
);
count = count + 1;
sleep(Duration::from_secs(1)); sleep(Duration::from_secs(1));
} }
log("main", "Network address is reachable.", LogLevel::Info); log("main", "Network address is reachable.", LogLevel::Info);
@@ -78,7 +122,11 @@ fn main() {
} }
fn mount_local(network_address: String) { fn mount_local(network_address: String) {
log("main", "Trying to mount filesystem locally...", LogLevel::Info); log(
"main",
"Trying to mount filesystem locally...",
LogLevel::Info,
);
let mount_point: &str = get_config().general.mount_point.as_str(); let mount_point: &str = get_config().general.mount_point.as_str();
@@ -90,7 +138,64 @@ fn mount_local(network_address: String) {
if get_config().storage.is_some() { if get_config().storage.is_some() {
device_address = Some(get_config().storage.clone().unwrap().device_ip); device_address = Some(get_config().storage.clone().unwrap().device_ip);
if !is_reachable(device_address.clone().unwrap().as_str()) && get_mac_from_ip(device_address.clone().unwrap().as_str()).unwrap() != mac_address { if is_reachable(&device_address.clone().unwrap()) {
log(
"main",
format!(
"Searching mac for device address {}.",
device_address.clone().unwrap()
)
.as_str(),
LogLevel::Info,
);
let mac_of_ip =
get_mac_from_ip(device_address.clone().unwrap().as_str()).unwrap_or("".to_string());
log(
"main",
format!(
"Found mac {} for device address {}.",
mac_of_ip,
device_address.clone().unwrap()
)
.as_str(),
LogLevel::Info,
);
if mac_of_ip == mac_address {
log(
"main",
format!(
"Found device mac {} on saved ip {}.",
mac_address,
device_address.clone().unwrap()
)
.as_str(),
LogLevel::Info,
);
} else {
log(
"main",
format!(
"Device mac {} is not the same as {} of ip {}.",
mac_address,
mac_of_ip,
device_address.clone().unwrap()
)
.as_str(),
LogLevel::Warn,
);
device_address = None;
}
} else {
log(
"main",
format!(
"Device address {} is not reachable.",
device_address.clone().unwrap()
)
.as_str(),
LogLevel::Warn,
);
device_address = None; device_address = None;
} }
} }
@@ -109,7 +214,11 @@ fn mount_local(network_address: String) {
break; break;
} }
log("main", "Couldn't find MAC adress in local network, sending awake call...", LogLevel::Info); log(
"main",
"Couldn't find MAC adress in local network, sending awake call...",
LogLevel::Info,
);
wake_on_land(mac_address); wake_on_land(mac_address);
log("main", "Waiting 30 seconds...", LogLevel::Info); log("main", "Waiting 30 seconds...", LogLevel::Info);
@@ -119,28 +228,52 @@ fn mount_local(network_address: String) {
} }
if device_address.is_none() { if device_address.is_none() {
log("main", "Couldn't find device address for MAC address.", LogLevel::Warn); log(
"main",
"Couldn't find device address for MAC address.",
LogLevel::Warn,
);
if is_mounted_as(mount_point, mount_type) { if is_mounted_as(mount_point, mount_type) {
log("main", "Filesystem is mounted locally. Unmounting...", LogLevel::Info); log(
"main",
"Filesystem is mounted locally. Unmounting...",
LogLevel::Info,
);
unmount(mount_point); unmount(mount_point);
} }
mount_remote(); mount_remote();
} else { } else {
let dev_ip: String = device_address.unwrap(); let dev_ip: String = device_address.unwrap();
log("main", "Found MAC address in local network.", LogLevel::Info); log(
"main",
"Found MAC address in local network.",
LogLevel::Info,
);
if !is_mounted_as(mount_point, mount_type) { if !is_mounted_as(mount_point, mount_type) {
if is_mounted_as(mount_point, get_config().remote.mount_type.as_str()) { if is_mounted(mount_point) {
log("main", "Filesystem is mounted remotely. Unmounting...", LogLevel::Info); log(
"main",
"Filesystem is mounted. Unmounting...",
LogLevel::Info,
);
unmount(mount_point); unmount(mount_point);
} }
log("main", "Mounting local filesystem...", LogLevel::Info); log("main", "Mounting local filesystem...", LogLevel::Info);
mount(mount_point, &*format!("{}:{}", dev_ip, get_config().local.mount_path), mount_type); mount(
mount_point,
&*format!("{}:{}", dev_ip, get_config().local.mount_path),
mount_type,
);
} else { } else {
log("main", "Filesystem is already mounted locally. Doing nothing.", LogLevel::Info); log(
"main",
"Filesystem is already mounted locally. Doing nothing.",
LogLevel::Info,
);
} }
} }
} }
@@ -159,26 +292,40 @@ fn mount_remote() {
break; break;
} }
log("main", "Couldn't reach remote server, waiting 1 second.", LogLevel::Warn); log(
"main",
"Couldn't reach remote server, waiting 1 second.",
LogLevel::Warn,
);
count = count + 1; count = count + 1;
sleep(Duration::from_secs(1)); sleep(Duration::from_secs(1));
} }
if could_reach { if could_reach {
log("main", "Remote server reachable.", LogLevel::Info); log("main", "Remote server reachable.", LogLevel::Info);
if is_mounted_as(mount_point, get_config().local.mount_type.as_str()) ||
is_mounted_as(mount_point, mount_type) { if mount_type == "davfs" || mount_type == "webdav" {
log("main", "Filesystem is already mounted. Doing nothing.", LogLevel::Info); save_credentials_webdav();
}
if is_mounted_as(mount_point, get_config().local.mount_type.as_str())
|| is_mounted_as(mount_point, mount_type)
{
log(
"main",
"Filesystem is already mounted. Doing nothing.",
LogLevel::Info,
);
} else { } else {
if is_mounted(mount_point) { if is_mounted(mount_point) {
log("main", "Filesystem is already mounted. Unmounting...", LogLevel::Info); log(
"main",
"Filesystem is already mounted. Unmounting...",
LogLevel::Info,
);
unmount(mount_point); unmount(mount_point);
} }
if mount_type == "davfs" || mount_type == "webdav" {
save_credentials_webdav();
}
log("main", "Mounting remote filesystem...", LogLevel::Info); log("main", "Mounting remote filesystem...", LogLevel::Info);
mount(mount_point, &*get_config().remote.mount_path, mount_type); mount(mount_point, &*get_config().remote.mount_path, mount_type);
} }
+85 -33
View File
@@ -11,9 +11,12 @@ use std::process::Command;
/// # Rückgabe /// # Rückgabe
/// - `bool`: True wenn die Adresse erreichbar ist, False wenn nicht /// - `bool`: True wenn die Adresse erreichbar ist, False wenn nicht
pub fn is_reachable(address: &str) -> bool { pub fn is_reachable(address: &str) -> bool {
let addr = &*if let Some(parts) = address.split('/').nth(0) { let addr_string = if let Some((ip_str, mask_str)) = address.split_once('/') {
if let Ok(ip) = parts.parse::<Ipv4Addr>() { if ip_str.parse::<Ipv4Addr>().is_ok()
let next_ip = Ipv4Addr::from(u32::from(ip) + 1); && mask_str.parse::<u8>().ok().filter(|m| *m <= 32).is_some()
{
let ip = ip_str.parse::<Ipv4Addr>().unwrap();
let next_ip = Ipv4Addr::from(u32::from(ip).saturating_add(1));
next_ip.to_string() next_ip.to_string()
} else { } else {
address.to_string() address.to_string()
@@ -21,6 +24,7 @@ pub fn is_reachable(address: &str) -> bool {
} else { } else {
address.to_string() address.to_string()
}; };
let addr = addr_string.as_str();
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -30,6 +34,9 @@ pub fn is_reachable(address: &str) -> bool {
.output(); .output();
if let Ok(out) = output { if let Ok(out) = output {
let stdout = String::from_utf8_lossy(&out.stdout);
log("network_utils", &*stdout, LogLevel::Debug);
out.status.success() out.status.success()
} else { } else {
log::log("network_utils", &*format!("Couldn't reach address {}!", addr), LogLevel::Error); log::log("network_utils", &*format!("Couldn't reach address {}!", addr), LogLevel::Error);
@@ -224,54 +231,98 @@ pub fn get_ip_from_mac(mac: &str, network: &str) -> Option<String> {
/// # Rückgabe /// # Rückgabe
/// - Option<String>: Die MAC-Adresse des Geräts oder None wenn nicht gefunden /// - Option<String>: Die MAC-Adresse des Geräts oder None wenn nicht gefunden
pub fn get_mac_from_ip(ip: &str) -> Option<String> { pub fn get_mac_from_ip(ip: &str) -> Option<String> {
// Hilfsfunktionen zur MAC-Normalisierung
let normalize_mac = |m: &str| -> String {
m.chars()
.filter(|c| c.is_ascii_hexdigit())
.flat_map(|c| c.to_lowercase())
.collect::<String>()
};
let canonicalize_mac = |hex_no_sep: &str| -> String {
hex_no_sep
.as_bytes()
.chunks(2)
.map(std::str::from_utf8)
.filter_map(Result::ok)
.collect::<Vec<&str>>()
.join(":")
};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
let output = Command::new("ip") // 1) Ziel kurz anpingen, damit ein ARP-Eintrag entsteht
.args(["neigh", "show", ip]) let _ = Command::new("ping")
.args(["-c", "1", "-W", "1", ip])
.output(); .output();
match output { // 2) Bis zu drei Versuche, ARP/Neigh einzulesen (kleine Wartezeit)
Ok(out) => { for _ in 0..3 {
if !out.status.success() { let output = Command::new("ip")
return None; .args(["neigh", "show", ip])
} .output();
let stdout = String::from_utf8_lossy(&out.stdout);
// Format: 192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE if let Ok(out) = output {
for line in stdout.lines() { if out.status.success() {
let parts: Vec<&str> = line.split_whitespace().collect(); let stdout = String::from_utf8_lossy(&out.stdout);
if parts.len() >= 4 && parts[0] == ip && parts[2] == "lladdr" { // Beispiel: 192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE
return Some(parts[3].to_string()); for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.get(0).copied() == Some(ip) {
if let Some(idx) = parts.iter().position(|p| *p == "lladdr") {
if let Some(mac_tok) = parts.get(idx + 1) {
let seen_norm = normalize_mac(mac_tok);
if seen_norm.len() == 12 {
return Some(canonicalize_mac(&seen_norm));
}
}
}
}
} }
} }
None
} }
Err(_) => None
std::thread::sleep(std::time::Duration::from_millis(100));
} }
None
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let output = Command::new("arp") // 1) Ziel kurz anpingen, damit ein ARP-Eintrag entsteht
.args(["-a", ip]) let _ = Command::new("ping")
.args(["-n", "1", "-w", "500", ip])
.output(); .output();
match output { // 2) Bis zu drei Versuche, ARP einzulesen (kleine Wartezeit)
Ok(out) => { for _ in 0..3 {
if !out.status.success() { // Hinweis: `arp -a` auf Windows zeigt die gesamte Tabelle; mit IP filtert es i. d. R. auf Interface,
return None; // daher filtern wir inhaltlich auf die Zeile mit der Ziel-IP.
} let output = Command::new("arp")
let stdout = String::from_utf8_lossy(&out.stdout); .args(["-a"])
for line in stdout.lines() { .output();
let parts: Vec<&str> = line.split_whitespace().collect();
// Format: 192.168.1.1 00-11-22-33-44-55 dynamic if let Ok(out) = output {
if parts.len() >= 2 && parts[0] == ip { if out.status.success() {
return Some(parts[1].replace('-', ":")); let stdout = String::from_utf8_lossy(&out.stdout);
for line in stdout.lines() {
let cols: Vec<&str> = line.split_whitespace().collect();
// Typisch: "192.168.1.1 00-11-22-33-44-55 dynamic"
if cols.get(0).copied() == Some(ip) && cols.len() >= 2 {
let mac_colon = cols[1].replace('-', ":");
let seen_norm = normalize_mac(&mac_colon);
if seen_norm.len() == 12 {
return Some(canonicalize_mac(&seen_norm));
}
}
} }
} }
None
} }
Err(_) => None
std::thread::sleep(std::time::Duration::from_millis(100));
} }
None
} }
#[cfg(not(any(target_os = "linux", target_os = "windows")))] #[cfg(not(any(target_os = "linux", target_os = "windows")))]
@@ -280,6 +331,7 @@ pub fn get_mac_from_ip(ip: &str) -> Option<String> {
None None
} }
} }
/// Sendet ein Wake-on-LAN Magic Packet an eine MAC-Adresse /// Sendet ein Wake-on-LAN Magic Packet an eine MAC-Adresse
/// ///
/// # Parameter /// # Parameter