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
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
.idea
|
||||||
|
|
||||||
|
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
|
|||||||
+15
-1
@@ -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"]
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user