diff --git a/src/cli/drive.rs b/src/cli/drive.rs index 2beeede..e9e1ef5 100644 --- a/src/cli/drive.rs +++ b/src/cli/drive.rs @@ -149,6 +149,7 @@ async fn remove(id: &str) -> anyhow::Result<()> { creds.delete(id, None).await?; smart_mount::mount::cleanup_credentials(&pair, &cfg.settings); + logger_ctdra::info("drive", &format!("drive pair '{id}' removed")); println!("Drive pair '{id}' removed."); Ok(()) } @@ -241,6 +242,7 @@ async fn add(args: DriveArgs) -> anyhow::Result<()> { .await?; } + logger_ctdra::info("drive", &format!("drive pair '{id}' created")); println!("Drive pair '{id}' created."); Ok(()) } @@ -373,6 +375,7 @@ async fn edit(id: &str, args: DriveArgs) -> anyhow::Result<()> { .await?; } + logger_ctdra::info("drive", &format!("drive pair '{id}' updated")); println!("Drive pair '{id}' updated."); Ok(()) } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 3e5b8a8..f8cd2e3 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -20,9 +20,14 @@ use clap_complete::Shell; /// `sudo` nicht gefunden). pub(crate) fn require_root(context: &str) -> anyhow::Result<()> { if !sudo_ctdra::is_run_as_root() { + logger_ctdra::info( + "cli", + &format!("'{context}' requires root - re-executing via sudo"), + ); let err = sudo_ctdra::run_as_root(); anyhow::bail!("'{context}' requires root privileges: could not re-exec via sudo: {err}"); } + logger_ctdra::debug("cli", &format!("'{context}': already running as root")); Ok(()) } @@ -88,8 +93,27 @@ pub enum Commands { Completions { shell: Shell }, } +/// Kurzname eines Subcommands fürs Logging (siehe [`dispatch`]) - kein `Debug`-Derive auf +/// `Commands` nötig, das würde auch die (teils sensiblen) Argument-Felder mit abdrucken. +fn command_label(cmd: &Commands) -> &'static str { + match cmd { + Commands::Drive { .. } => "drive", + Commands::Mount { .. } => "mount", + Commands::Unmount { .. } => "unmount", + Commands::Status { .. } => "status", + Commands::Watch => "watch", + Commands::Service { .. } => "service", + Commands::Doctor { .. } => "doctor", + Commands::Completions { .. } => "completions", + } +} + /// Führt das per `Cli` geparste Subcommand aus. pub async fn dispatch(cli: Cli) -> anyhow::Result<()> { + logger_ctdra::debug( + "cli", + &format!("dispatching '{}'", command_label(&cli.command)), + ); match cli.command { Commands::Drive { action } => drive::run(*action).await, Commands::Mount { name, all } => mount_cmd::run_mount(name, all).await, diff --git a/src/cli/mount_cmd.rs b/src/cli/mount_cmd.rs index 6fad7d5..82e0d77 100644 --- a/src/cli/mount_cmd.rs +++ b/src/cli/mount_cmd.rs @@ -26,6 +26,10 @@ pub async fn run_mount(name: Option, all: bool) -> anyhow::Result<()> { println!("No matching drive pairs found."); return Ok(()); } + logger_ctdra::info( + "mount", + &format!("mount: processing {} drive pair(s)", pairs.len()), + ); let creds = CredentialStore::open().await?; for pair in &pairs { @@ -43,6 +47,10 @@ pub async fn run_unmount(name: Option, all: bool) -> anyhow::Result<()> println!("No matching drive pairs found."); return Ok(()); } + logger_ctdra::info( + "mount", + &format!("unmount: processing {} drive pair(s)", pairs.len()), + ); for pair in &pairs { match reconcile::unmount_pair(pair, &cfg.settings).await { diff --git a/src/cli/service.rs b/src/cli/service.rs index 9539da5..8c36fe1 100644 --- a/src/cli/service.rs +++ b/src/cli/service.rs @@ -31,6 +31,10 @@ fn install() -> anyhow::Result<()> { match systemd::install_cron(interval)? { systemd::CronInstallOutcome::SystemFile(path) => { + logger_ctdra::info( + "service", + &format!("cron entry written: {}", path.display()), + ); println!("Cron entry written: {}", path.display()); } systemd::CronInstallOutcome::Unavailable => { @@ -45,7 +49,10 @@ fn install() -> anyhow::Result<()> { fn uninstall() -> anyhow::Result<()> { match systemd::uninstall_cron()? { - systemd::CronUninstallOutcome::Removed => println!("Cron entry removed."), + systemd::CronUninstallOutcome::Removed => { + logger_ctdra::info("service", "cron entry removed"); + println!("Cron entry removed."); + } systemd::CronUninstallOutcome::NotPresent => { println!("Nothing to remove - no cron entry was installed.") } diff --git a/src/cli/watch.rs b/src/cli/watch.rs index df4e677..8352c87 100644 --- a/src/cli/watch.rs +++ b/src/cli/watch.rs @@ -19,6 +19,18 @@ pub async fn run() -> anyhow::Result<()> { had_failure = true; } } + logger_ctdra::info( + "watch", + &format!( + "watch pass done: {} pair(s), {}", + outcomes.len(), + if had_failure { + "with failures" + } else { + "no failures" + } + ), + ); if had_failure { anyhow::bail!("at least one drive pair could not be reconciled"); diff --git a/src/config/mod.rs b/src/config/mod.rs index cbb053e..56ab9c9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,5 +19,7 @@ pub use schema::{ pub fn init() { config_ctdra::set_config_name("config"); let program = config_ctdra::get_program_name(); - config_ctdra::set_custom_dir(std::path::PathBuf::from("/etc").join(program)); + let dir = std::path::PathBuf::from("/etc").join(program); + logger_ctdra::debug("config", &format!("config directory: {}", dir.display())); + config_ctdra::set_custom_dir(dir); } diff --git a/src/config/pairs.rs b/src/config/pairs.rs index 63aa67c..5be8d74 100644 --- a/src/config/pairs.rs +++ b/src/config/pairs.rs @@ -10,6 +10,10 @@ pub fn load() -> Result { /// Fügt ein neues Laufwerkspaar hinzu (load-mutate-store in einem atomaren Schritt). pub fn add_pair(pair: DrivePair) -> Result { + logger_ctdra::debug( + "config", + &format!("writing new pair '{}' to config", pair.id), + ); Ok(config_ctdra::modify::(|cfg| { cfg.pairs.push(pair.clone()); })?) @@ -18,6 +22,7 @@ pub fn add_pair(pair: DrivePair) -> Result { /// Ersetzt ein bestehendes Laufwerkspaar (Vergleich über `id`). pub fn update_pair(pair: DrivePair) -> Result { let id = pair.id.clone(); + logger_ctdra::debug("config", &format!("writing updated pair '{id}' to config")); let updated = config_ctdra::modify::(move |cfg| { if let Some(existing) = cfg.pairs.iter_mut().find(|p| p.id == pair.id) { *existing = pair.clone(); @@ -31,6 +36,7 @@ pub fn update_pair(pair: DrivePair) -> Result { /// Entfernt ein Laufwerkspaar per ID. pub fn remove_pair(id: &str) -> Result { + logger_ctdra::debug("config", &format!("removing pair '{id}' from config")); // Die "vorher"-Länge wird INNERHALB desselben `modify`-Aufrufs (auf der bereits frisch // geladenen `cfg`) ermittelt statt über einen separaten, vorgelagerten `load()`-Aufruf: // zwischen zwei getrennten Aufrufen könnte ein nebenläufiger Schreiber die Paarliste diff --git a/src/crypto/key.rs b/src/crypto/key.rs index 6b34aab..13695a6 100644 --- a/src/crypto/key.rs +++ b/src/crypto/key.rs @@ -32,8 +32,16 @@ mod file_key { pub fn load_or_create(path: &Path) -> Result<[u8; 32]> { if path.exists() { + logger_ctdra::debug( + "crypto", + &format!("loading master key from '{}'", path.display()), + ); return read(path); } + logger_ctdra::info( + "crypto", + &format!("creating new master key at '{}'", path.display()), + ); create(path) } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index a73d600..c4e9ff9 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -29,6 +29,7 @@ pub const NONCE_LEN: usize = 12; /// aes-gcms Re-Export-Kette nicht automatisch aktiviert wird). `getrandom::fill` ist /// unabhängig davon stabil und genau für diesen Zweck gedacht. pub fn encrypt(plaintext: &[u8], key: &[u8; 32]) -> Result<(Vec, Vec)> { + logger_ctdra::debug("crypto", "encrypting credential data"); let cipher = Aes256Gcm::new(&Key::::from(*key)); let mut nonce_bytes = [0u8; NONCE_LEN]; @@ -44,6 +45,7 @@ pub fn encrypt(plaintext: &[u8], key: &[u8; 32]) -> Result<(Vec, Vec)> { /// Entschlüsselt einen zuvor mit [`encrypt`] erzeugten Ciphertext. pub fn decrypt(ciphertext: &[u8], nonce: &[u8], key: &[u8; 32]) -> Result> { + logger_ctdra::debug("crypto", "decrypting credential data"); if nonce.len() != NONCE_LEN { return Err(Error::Crypto(format!( "invalid nonce length: expected {NONCE_LEN}, got {}", diff --git a/src/db/credentials.rs b/src/db/credentials.rs index 8090314..748f858 100644 --- a/src/db/credentials.rs +++ b/src/db/credentials.rs @@ -58,6 +58,13 @@ impl CredentialStore { domain: Option<&str>, password: &str, ) -> Result<()> { + logger_ctdra::debug( + "db", + &format!( + "storing credential for pair '{pair_id}' ({})", + side.as_str() + ), + ); let key = resolve_master_key()?; let (ciphertext, nonce) = crypto::encrypt(password.as_bytes(), &key)?; let now = now_unix(); @@ -85,6 +92,13 @@ impl CredentialStore { /// Liest und entschlüsselt die Zugangsdaten für `pair_id`/`side`, falls vorhanden. pub async fn get(&self, pair_id: &str, side: Side) -> Result> { + logger_ctdra::debug( + "db", + &format!( + "reading credential for pair '{pair_id}' ({})", + side.as_str() + ), + ); let mut rows = self .conn .query( @@ -117,6 +131,13 @@ impl CredentialStore { /// Löscht Zugangsdaten. `side = None` löscht beide Seiten (z. B. beim Entfernen eines Paars). pub async fn delete(&self, pair_id: &str, side: Option) -> Result<()> { + logger_ctdra::debug( + "db", + &format!( + "deleting credential(s) for pair '{pair_id}' ({})", + side.map(|s| s.as_str()).unwrap_or("both sides") + ), + ); match side { Some(side) => { self.conn diff --git a/src/db/mod.rs b/src/db/mod.rs index db3a816..8c9c4f1 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -21,6 +21,7 @@ pub fn resolve_db_path() -> PathBuf { /// Öffnet (und initialisiert bei Bedarf) die lokale Datenbank am aufgelösten Pfad. pub async fn open() -> Result { let path = resolve_db_path(); + logger_ctdra::debug("db", &format!("opening database at '{}'", path.display())); if let Some(dir) = path.parent() { tokio::fs::create_dir_all(dir) .await diff --git a/src/main.rs b/src/main.rs index f9238f8..e7c55b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,13 @@ async fn main() -> ExitCode { Err(_) => "info".to_string(), }; logger_ctdra::set_log_level(parse_log_level(&log_level)); + logger_ctdra::info( + "main", + &format!( + "smart-mount {} starting (log level: {log_level})", + env!("CARGO_PKG_VERSION") + ), + ); let cli = cli::Cli::parse(); match cli::dispatch(cli).await { diff --git a/src/mount/lock.rs b/src/mount/lock.rs index fe45cff..0b96b65 100644 --- a/src/mount/lock.rs +++ b/src/mount/lock.rs @@ -23,9 +23,16 @@ use crate::error::{Error, Result}; /// Datei-Deskriptor), bis der Guard gedroppt wird - Schließen des Deskriptors gibt die Sperre /// implizit frei, ein explizites `unlock()` ist dafür nicht nötig. pub struct PairLock { + pair_id: String, _file: File, } +impl Drop for PairLock { + fn drop(&mut self) { + logger_ctdra::debug("lock", &format!("pair '{}': lock released", self.pair_id)); + } +} + /// Verzeichnis für die Sperrdateien: `/run/smart-mount/locks` im Root-/System-Kontext (root ist /// dort ohnehin die einzige Partei, die Paare in diesem Kontext mountet), sonst /// `$XDG_RUNTIME_DIR` (per-Nutzer, von systemd `0700`-geschützt angelegt) mit Fallback auf das @@ -59,8 +66,13 @@ fn acquire_blocking(pair_id: &str) -> Result { // Blockiert, bis die Sperre frei wird - `flock(2)` kennt keinen Async-Mechanismus, daher // läuft dieser gesamte Aufruf über `spawn_blocking` (siehe [`acquire`]) auf einem // Blocking-Thread statt einem Tokio-Worker-Thread. + logger_ctdra::debug("lock", &format!("pair '{pair_id}': waiting for lock")); file.lock().map_err(|e| Error::io(&path, e))?; - Ok(PairLock { _file: file }) + logger_ctdra::debug("lock", &format!("pair '{pair_id}': lock acquired")); + Ok(PairLock { + pair_id: pair_id.to_string(), + _file: file, + }) } /// Sperrt ein Laufwerkspaar prozessübergreifend für die Dauer des zurückgegebenen Guards. diff --git a/src/mount/mod.rs b/src/mount/mod.rs index 96974b2..d8096e1 100644 --- a/src/mount/mod.rs +++ b/src/mount/mod.rs @@ -93,6 +93,17 @@ fn run_tolerating_already_done_with_timeout( tolerate_timeout: bool, timeout_secs: u64, ) -> Result<()> { + logger_ctdra::debug( + "mount", + &format!( + "{context}: running '{} {}' (timeout {timeout_secs}s)", + cmd.get_program().to_string_lossy(), + cmd.get_args() + .map(|a| a.to_string_lossy().into_owned()) + .collect::>() + .join(" ") + ), + ); let output = run_with_timeout(cmd, timeout_secs).map_err(|e| crate::error::Error::MountFailed { context: context.to_string(), @@ -100,6 +111,7 @@ fn run_tolerating_already_done_with_timeout( })?; if output.status.success() { + logger_ctdra::debug("mount", &format!("{context}: succeeded")); return Ok(()); } @@ -173,16 +185,20 @@ fn cleanup_side_credentials( match kind { MountKind::Smb => { let path = smb::credentials_path(&pair.id, side); - if path.exists() - && let Err(e) = std::fs::remove_file(&path) - { - logger_ctdra::warn( - "mount", - &format!( - "Could not delete credentials file '{}': {e}", - path.display() + if path.exists() { + match std::fs::remove_file(&path) { + Ok(()) => logger_ctdra::debug( + "mount", + &format!("deleted credentials file '{}'", path.display()), ), - ); + Err(e) => logger_ctdra::warn( + "mount", + &format!( + "Could not delete credentials file '{}': {e}", + path.display() + ), + ), + } } } MountKind::WebDav => { diff --git a/src/mount/smb.rs b/src/mount/smb.rs index 848e286..e889aa2 100644 --- a/src/mount/smb.rs +++ b/src/mount/smb.rs @@ -29,7 +29,12 @@ impl MountBackend for SmbBackend { fn prepare(&self, target: &MountTarget, cred: Option<&Credential>) -> Result<()> { if let Some(cred) = cred { - write_credentials_file(&credentials_path(&target.pair_id, target.side), cred)?; + let path = credentials_path(&target.pair_id, target.side); + logger_ctdra::debug( + "mount", + &format!("cifs: writing credentials file '{}'", path.display()), + ); + write_credentials_file(&path, cred)?; } Ok(()) } diff --git a/src/mount/target.rs b/src/mount/target.rs index 90d38d2..62cadd9 100644 --- a/src/mount/target.rs +++ b/src/mount/target.rs @@ -301,6 +301,15 @@ pub fn activate_symlink(pair: &DrivePair, side: Side) -> Result<()> { let _ = std::fs::remove_file(&tmp_path); std::os::unix::fs::symlink(&target, &tmp_path).map_err(|e| Error::io(&tmp_path, e))?; std::fs::rename(&tmp_path, link_path).map_err(|e| Error::io(link_path, e))?; + logger_ctdra::info( + "mount", + &format!( + "pair '{}': activated '{}' -> '{}'", + pair.id, + link_path.display(), + target.display() + ), + ); Ok(()) } diff --git a/src/mount/webdav.rs b/src/mount/webdav.rs index 60eedc6..c7920b0 100644 --- a/src/mount/webdav.rs +++ b/src/mount/webdav.rs @@ -46,6 +46,10 @@ impl MountBackend for WebDavBackend { "davfs2 credential has an empty username or password".to_string(), )); } + logger_ctdra::debug( + "mount", + &format!("davfs2: writing secrets entry for '{}'", target.source), + ); write_secrets_entry( &davfs2_secrets_path(), &target.source, diff --git a/src/network/mac2ip.rs b/src/network/mac2ip.rs index 2ca991f..d89d512 100644 --- a/src/network/mac2ip.rs +++ b/src/network/mac2ip.rs @@ -38,6 +38,12 @@ enum Mac2IpOutput { /// `binary` ist der konfigurierte Binary-Name/-Pfad (`GlobalSettings::mac2ip_binary`, /// standardmäßig `"mac2ip"`, per PATH aufgelöst). pub fn resolve(mac: &str, binary: &str) -> Result { + logger_ctdra::info( + "network", + &format!( + "resolving MAC '{mac}' via '{binary}' (may take up to {MAC2IP_TIMEOUT_SECS}s, e.g. for an nmap scan)" + ), + ); let output = Command::new("timeout") .arg(MAC2IP_TIMEOUT_SECS.to_string()) .arg(binary) @@ -49,13 +55,25 @@ pub fn resolve(mac: &str, binary: &str) -> Result { })?; if output.status.code() == Some(124) { + logger_ctdra::warn( + "network", + &format!("'{binary}' did not respond within {MAC2IP_TIMEOUT_SECS}s for MAC '{mac}'"), + ); return Err(Error::Mac2Ip { mac: mac.to_string(), reason: format!("'{binary}' did not respond within {MAC2IP_TIMEOUT_SECS}s (timed out)"), }); } - parse_output(&output.stdout, mac) + let result = parse_output(&output.stdout, mac); + match &result { + Ok(ip) => logger_ctdra::info("network", &format!("MAC '{mac}' resolved to {ip}")), + Err(e) => logger_ctdra::debug( + "network", + &format!("MAC '{mac}' could not be resolved: {e}"), + ), + } + result } /// Prüft, ob das konfigurierte `mac2ip`-Binary über `PATH` auffindbar ist. diff --git a/src/network/mod.rs b/src/network/mod.rs index 4531136..c078235 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -17,6 +17,15 @@ use std::process::{Command, Stdio}; /// (z. B. ein kaputter Reverse-Proxy) sonst bei jedem `watch`-Durchlauf einen vollen, /// letztlich erfolglosen Umschaltversuch (inkl. `MOUNT_TIMEOUT_SECS`-Wartezeit) auslösen würde. pub fn is_reachable(addr: &str) -> bool { + let reachable = is_reachable_inner(addr); + logger_ctdra::debug( + "network", + &format!("reachability check for '{addr}': {reachable}"), + ); + reachable +} + +fn is_reachable_inner(addr: &str) -> bool { if addr.starts_with("http://") || addr.starts_with("https://") { Command::new("curl") .args([ diff --git a/src/reconcile/mod.rs b/src/reconcile/mod.rs index ed59c47..8be20e1 100644 --- a/src/reconcile/mod.rs +++ b/src/reconcile/mod.rs @@ -50,6 +50,13 @@ pub struct ReconcileOutcome { /// Risiko für einen Effizienzgewinn, den die Reachability-Parallelisierung bereits liefert. pub async fn watch_once(cfg: &AppConfig, creds: &CredentialStore) -> Vec { let enabled: Vec<&DrivePair> = cfg.pairs.iter().filter(|p| p.enabled).collect(); + logger_ctdra::debug( + "reconcile", + &format!( + "watch: checking reachability for {} enabled pair(s)", + enabled.len() + ), + ); let mut checks = Vec::with_capacity(enabled.len()); for pair in &enabled { @@ -123,16 +130,28 @@ async fn reconcile_pair_checked( ) .await { - Ok(action) => ReconcileOutcome { - pair_id, - pair_name, - action, - }, - Err(e) => ReconcileOutcome { - pair_id, - pair_name, - action: Action::Failed(e.to_string()), - }, + Ok(action) => { + logger_ctdra::info( + "reconcile", + &format!("pair '{pair_name}' ({pair_id}): {action:?}"), + ); + ReconcileOutcome { + pair_id, + pair_name, + action, + } + } + Err(e) => { + logger_ctdra::error( + "reconcile", + &format!("pair '{pair_name}' ({pair_id}): reconcile failed: {e}"), + ); + ReconcileOutcome { + pair_id, + pair_name, + action: Action::Failed(e.to_string()), + } + } } } @@ -147,6 +166,13 @@ async fn reconcile_pair_inner( let _guard = lock::acquire(&pair.id).await?; let active = target::active_side(pair); + logger_ctdra::debug( + "reconcile", + &format!( + "pair '{}': local_reachable={local_reachable} cloud_reachable={cloud_reachable} active={:?}", + pair.id, active + ), + ); cleanup_orphaned_mounts(pair, settings, active).await; if local_reachable { @@ -182,7 +208,13 @@ async fn reconcile_pair_inner( fn check_local_reachable(local: &LocalSide, settings: &GlobalSettings) -> (bool, Option) { match address::resolve_ip(&local.address, settings) { Ok(ip) => (network::is_reachable(&ip.to_string()), Some(ip)), - Err(_) => (false, None), + Err(e) => { + logger_ctdra::debug( + "reconcile", + &format!("local address could not be resolved: {e}"), + ); + (false, None) + } } } @@ -251,6 +283,15 @@ async fn switch_to( old_active: Option, cached_local_ip: Option, ) -> Result<()> { + logger_ctdra::info( + "reconcile", + &format!( + "pair '{}': switching to '{}' (previously active: {:?})", + pair.id, + new_side.as_str(), + old_active + ), + ); mount_side(pair, settings, new_side, creds, cached_local_ip).await?; // Falls das Aktivieren des Symlinks fehlschlägt, muss die gerade gemountete `new_side` @@ -280,6 +321,16 @@ async fn mount_side( ) -> Result<()> { let mount_target = target::build_target_with_cached_local_ip(pair, settings, side, cached_local_ip)?; + logger_ctdra::debug( + "reconcile", + &format!( + "pair '{}' ({}): resolved source '{}' -> '{}'", + pair.id, + side.as_str(), + mount_target.source, + mount_target.mount_point.display() + ), + ); std::fs::create_dir_all(&mount_target.mount_point) .map_err(|e| crate::error::Error::io(&mount_target.mount_point, e))?; // Backing-Verzeichnis auf `owner_user` chownen (0700) - relevant vor allem für NFS, wo es @@ -293,6 +344,15 @@ async fn mount_side( backend.check_available()?; let cred = creds.get(&pair.id, side).await?; backend.prepare(&mount_target, cred.as_ref())?; + logger_ctdra::info( + "reconcile", + &format!( + "pair '{}' ({}): mounting via {} ...", + pair.id, + side.as_str(), + backend.name() + ), + ); backend.mount(&mount_target) } @@ -301,6 +361,7 @@ async fn mount_side( /// ein leeres, ausgehängtes Backing-Verzeichnis) - der nächste `mount`/`watch`-Lauf räumt das /// beim erneuten Aktivieren automatisch auf. pub async fn unmount_pair(pair: &DrivePair, settings: &GlobalSettings) -> Result { + logger_ctdra::info("reconcile", &format!("pair '{}': unmounting", pair.id)); let _guard = lock::acquire(&pair.id).await?; // Beide Seiten werden unabhängig voneinander versucht - ein Fehler (auch ein @@ -363,7 +424,18 @@ async fn unmount_side(pair: &DrivePair, settings: &GlobalSettings, side: Side) - owner_user: pair.owner_user.clone(), } }); - mount::backend_for(target::side_kind(pair, side)).unmount(&mount_target) + let backend = mount::backend_for(target::side_kind(pair, side)); + logger_ctdra::debug( + "reconcile", + &format!( + "pair '{}' ({}): unmounting via {} ({})", + pair.id, + side.as_str(), + backend.name(), + mount_target.mount_point.display() + ), + ); + backend.unmount(&mount_target) } #[cfg(test)] diff --git a/src/systemd/mod.rs b/src/systemd/mod.rs index 4a751da..180d3fd 100644 --- a/src/systemd/mod.rs +++ b/src/systemd/mod.rs @@ -67,6 +67,7 @@ pub fn install_cron(watch_interval_secs: u64) -> Result { std::fs::set_permissions(CRON_D_PATH, std::fs::Permissions::from_mode(0o644)) .map_err(|e| Error::io(CRON_D_PATH, e))?; } + logger_ctdra::info("systemd", &format!("cron entry written to '{CRON_D_PATH}'")); Ok(CronInstallOutcome::SystemFile(PathBuf::from(CRON_D_PATH))) } @@ -96,6 +97,10 @@ pub fn uninstall_cron() -> Result { return Ok(CronUninstallOutcome::NotPresent); } std::fs::remove_file(path).map_err(|e| Error::io(CRON_D_PATH, e))?; + logger_ctdra::info( + "systemd", + &format!("cron entry removed from '{CRON_D_PATH}'"), + ); Ok(CronUninstallOutcome::Removed) }