Compare commits
10
Commits
04d0fd5532
...
f871353795
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f871353795 | ||
|
|
acc7a887bc | ||
|
|
c4a8b74f8d | ||
|
|
c6cfc4cb3b | ||
|
|
c2d92bb807 | ||
|
|
c57ffe4580 | ||
|
|
bd8d9709c5 | ||
|
|
918b5ff1d1 | ||
|
|
cf9132fbb1 | ||
|
|
14876c7e1e |
+1
-2
@@ -505,8 +505,7 @@ FodyWeavers.xsd
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
|
||||
.idea
|
||||
|
||||
# Added by cargo
|
||||
|
||||
|
||||
+15
-1
@@ -1,11 +1,25 @@
|
||||
[package]
|
||||
name = "SmartMount"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
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]
|
||||
time = { version="0.3.41", features = ["formatting", "macros", "local-offset"] }
|
||||
serde = { version="1.0.219", features = ["derive"] }
|
||||
confy = "1.0.0"
|
||||
libc = "1.0.0-alpha.1"
|
||||
|
||||
[profile.release]
|
||||
debug = "none"
|
||||
|
||||
[package.metadata.deb]
|
||||
section = "utils"
|
||||
priority = "optional"
|
||||
|
||||
provides = ["smartmount"]
|
||||
depends = ["nmap", "$auto"]
|
||||
@@ -1,3 +1,14 @@
|
||||
# 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
@@ -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")
|
||||
|
||||
+100
-20
@@ -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");
|
||||
let norm_mp = normalize_mount_point(mount_point);
|
||||
read_proc_mounts()
|
||||
.into_iter()
|
||||
.any(|(mp, _, _, _)| mp == norm_mp)
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
return false;
|
||||
#[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"))
|
||||
}
|
||||
|
||||
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
@@ -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::mount::{mount, unmount};
|
||||
use crate::filesystem::mounted::{is_mounted, is_mounted_as};
|
||||
use crate::log::log;
|
||||
use crate::log::LogLevel;
|
||||
use crate::log::log;
|
||||
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 std::process::exit;
|
||||
use std::thread;
|
||||
use std::thread::{sleep, JoinHandle};
|
||||
use std::thread::{JoinHandle, sleep};
|
||||
use std::time::Duration;
|
||||
|
||||
mod config;
|
||||
mod filesystem;
|
||||
mod log;
|
||||
mod network;
|
||||
mod config;
|
||||
mod program;
|
||||
mod filesystem;
|
||||
mod sudo;
|
||||
|
||||
fn main() {
|
||||
log("main", "========== PROGRAM START ==========", LogLevel::Info);
|
||||
log(
|
||||
"main",
|
||||
"========== PROGRAM START ==========",
|
||||
LogLevel::Info,
|
||||
);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
let network_interface : String;
|
||||
let network_interface: String;
|
||||
|
||||
let mut count : i32 = 0;
|
||||
let mut count: i32 = 0;
|
||||
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() {
|
||||
network_interface = interface_str;
|
||||
break;
|
||||
} 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);
|
||||
}
|
||||
|
||||
log("main", "No active network card found, waiting 1 second.", LogLevel::Warn);
|
||||
count = count+1;
|
||||
log(
|
||||
"main",
|
||||
"No active network card found, waiting 1 second.",
|
||||
LogLevel::Warn,
|
||||
);
|
||||
count = count + 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();
|
||||
log("main", &*format!("Interface address: {}", interface_address), LogLevel::Info);
|
||||
let interface_address = get_interface_ip_address(network_interface.as_str())
|
||||
.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();
|
||||
log("main", &*format!("Network address: {}", network_address), LogLevel::Info);
|
||||
let network_address = get_network_address(interface_address.as_str())
|
||||
.unwrap()
|
||||
.trim()
|
||||
.to_string();
|
||||
log(
|
||||
"main",
|
||||
&*format!("Network address: {}", network_address),
|
||||
LogLevel::Info,
|
||||
);
|
||||
|
||||
count = 0;
|
||||
loop {
|
||||
if is_reachable(network_address.as_str()) {
|
||||
break;
|
||||
} 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);
|
||||
}
|
||||
|
||||
log("main", "Network address not reachable, waiting 1 second.", LogLevel::Warn);
|
||||
count = count+1;
|
||||
log(
|
||||
"main",
|
||||
"Network address not reachable, waiting 1 second.",
|
||||
LogLevel::Warn,
|
||||
);
|
||||
count = count + 1;
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
log("main", "Network address is reachable.", LogLevel::Info);
|
||||
@@ -78,7 +122,11 @@ fn main() {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -90,7 +138,64 @@ fn mount_local(network_address: String) {
|
||||
if get_config().storage.is_some() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -109,7 +214,11 @@ fn mount_local(network_address: String) {
|
||||
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);
|
||||
|
||||
log("main", "Waiting 30 seconds...", LogLevel::Info);
|
||||
@@ -119,28 +228,52 @@ fn mount_local(network_address: String) {
|
||||
}
|
||||
|
||||
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) {
|
||||
log("main", "Filesystem is mounted locally. Unmounting...", LogLevel::Info);
|
||||
log(
|
||||
"main",
|
||||
"Filesystem is mounted locally. Unmounting...",
|
||||
LogLevel::Info,
|
||||
);
|
||||
unmount(mount_point);
|
||||
}
|
||||
|
||||
mount_remote();
|
||||
} else {
|
||||
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, get_config().remote.mount_type.as_str()) {
|
||||
log("main", "Filesystem is mounted remotely. Unmounting...", LogLevel::Info);
|
||||
if is_mounted(mount_point) {
|
||||
log(
|
||||
"main",
|
||||
"Filesystem is mounted. Unmounting...",
|
||||
LogLevel::Info,
|
||||
);
|
||||
unmount(mount_point);
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
if could_reach {
|
||||
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) {
|
||||
log("main", "Filesystem is already mounted. Doing nothing.", LogLevel::Info);
|
||||
|
||||
if mount_type == "davfs" || mount_type == "webdav" {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
if mount_type == "davfs" || mount_type == "webdav" {
|
||||
save_credentials_webdav();
|
||||
}
|
||||
|
||||
log("main", "Mounting remote filesystem...", LogLevel::Info);
|
||||
mount(mount_point, &*get_config().remote.mount_path, mount_type);
|
||||
}
|
||||
|
||||
+85
-33
@@ -11,9 +11,12 @@ use std::process::Command;
|
||||
/// # Rückgabe
|
||||
/// - `bool`: True wenn die Adresse erreichbar ist, False wenn nicht
|
||||
pub fn is_reachable(address: &str) -> bool {
|
||||
let addr = &*if let Some(parts) = address.split('/').nth(0) {
|
||||
if let Ok(ip) = parts.parse::<Ipv4Addr>() {
|
||||
let next_ip = Ipv4Addr::from(u32::from(ip) + 1);
|
||||
let addr_string = if let Some((ip_str, mask_str)) = address.split_once('/') {
|
||||
if ip_str.parse::<Ipv4Addr>().is_ok()
|
||||
&& 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()
|
||||
} else {
|
||||
address.to_string()
|
||||
@@ -21,6 +24,7 @@ pub fn is_reachable(address: &str) -> bool {
|
||||
} else {
|
||||
address.to_string()
|
||||
};
|
||||
let addr = addr_string.as_str();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@@ -30,6 +34,9 @@ pub fn is_reachable(address: &str) -> bool {
|
||||
.output();
|
||||
|
||||
if let Ok(out) = output {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
log("network_utils", &*stdout, LogLevel::Debug);
|
||||
|
||||
out.status.success()
|
||||
} else {
|
||||
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
|
||||
/// - Option<String>: Die MAC-Adresse des Geräts oder None wenn nicht gefunden
|
||||
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")]
|
||||
{
|
||||
let output = Command::new("ip")
|
||||
.args(["neigh", "show", ip])
|
||||
// 1) Ziel kurz anpingen, damit ein ARP-Eintrag entsteht
|
||||
let _ = Command::new("ping")
|
||||
.args(["-c", "1", "-W", "1", ip])
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if !out.status.success() {
|
||||
return None;
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
// Format: 192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE
|
||||
for line in stdout.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 4 && parts[0] == ip && parts[2] == "lladdr" {
|
||||
return Some(parts[3].to_string());
|
||||
// 2) Bis zu drei Versuche, ARP/Neigh einzulesen (kleine Wartezeit)
|
||||
for _ in 0..3 {
|
||||
let output = Command::new("ip")
|
||||
.args(["neigh", "show", ip])
|
||||
.output();
|
||||
|
||||
if let Ok(out) = output {
|
||||
if out.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
// Beispiel: 192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE
|
||||
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")]
|
||||
{
|
||||
let output = Command::new("arp")
|
||||
.args(["-a", ip])
|
||||
// 1) Ziel kurz anpingen, damit ein ARP-Eintrag entsteht
|
||||
let _ = Command::new("ping")
|
||||
.args(["-n", "1", "-w", "500", ip])
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if !out.status.success() {
|
||||
return None;
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
// Format: 192.168.1.1 00-11-22-33-44-55 dynamic
|
||||
if parts.len() >= 2 && parts[0] == ip {
|
||||
return Some(parts[1].replace('-', ":"));
|
||||
// 2) Bis zu drei Versuche, ARP einzulesen (kleine Wartezeit)
|
||||
for _ in 0..3 {
|
||||
// Hinweis: `arp -a` auf Windows zeigt die gesamte Tabelle; mit IP filtert es i. d. R. auf Interface,
|
||||
// daher filtern wir inhaltlich auf die Zeile mit der Ziel-IP.
|
||||
let output = Command::new("arp")
|
||||
.args(["-a"])
|
||||
.output();
|
||||
|
||||
if let Ok(out) = output {
|
||||
if out.status.success() {
|
||||
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")))]
|
||||
@@ -280,6 +331,7 @@ pub fn get_mac_from_ip(ip: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Sendet ein Wake-on-LAN Magic Packet an eine MAC-Adresse
|
||||
///
|
||||
/// # Parameter
|
||||
|
||||
Reference in New Issue
Block a user