From 96c94f030a3a2aa49526f2e1824ad59ed076de7e Mon Sep 17 00:00:00 2001 From: nevaforget Date: Sat, 28 Mar 2026 01:23:18 +0100 Subject: [PATCH] feat: switch to systemd-journal-logger, add debug logging (v0.4.0) Replace env_logger file-based logging with systemd-journal-logger for consistency with moonlock and native journalctl integration. Add debug-level logging at all decision points: config loading, user/session detection, avatar resolution, locale detection, IPC messages, login flow, and persistence. No credentials are ever logged. --- CLAUDE.md | 5 +- Cargo.lock | 188 ++++-------------------------------------------- Cargo.toml | 4 +- src/config.rs | 42 +++++++---- src/greeter.rs | 30 +++++++- src/i18n.rs | 11 ++- src/ipc.rs | 5 ++ src/main.rs | 30 +++----- src/power.rs | 4 ++ src/sessions.rs | 19 ++++- src/users.rs | 29 +++++--- 11 files changed, 137 insertions(+), 230 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 12a68d2..f3c239a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,7 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git--x86_64.pkg.tar.z - `i18n.rs` — Locale-Erkennung (LANG / /etc/locale.conf) und String-Tabellen (DE/EN), alle UI- und Login-Fehlermeldungen - `config.rs` — TOML-Config ([appearance] background, gtk-theme) + Wallpaper-Fallback - `greeter.rs` — GTK4 UI (Overlay-Layout), Login-Flow via greetd IPC, Faillock-Warnung, Avatar-Cache, Last-User/Last-Session Persistence (0o600 Permissions) -- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor, Log-Datei (0o640) +- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor, systemd-journal-logger - `resources/style.css` — Catppuccin-inspiriertes Theme ## Design Decisions @@ -59,5 +59,6 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git--x86_64.pkg.tar.z - **Symmetrie mit moonset**: Gleiche Patterns (i18n, config, users, power, GResource) - **Session-Validierung**: Relative Pfade erlaubt (greetd löst PATH auf), nur `..`/Null-Bytes werden abgelehnt - **GTK-Theme-Validierung**: Nur alphanumerisch + `_-+.` erlaubt, verhindert Path-Traversal über Config -- **File Permissions**: Cache-Dateien 0o600, Log-Datei 0o640 +- **Journal-Logging**: `systemd-journal-logger` statt File-Logging — `journalctl -t moongreet` +- **File Permissions**: Cache-Dateien 0o600 - **Testbare Persistence**: `save_*_to`/`load_*_from` Varianten mit konfigurierbarem Pfad für Unit-Tests diff --git a/Cargo.lock b/Cargo.lock index 82d0117..8ee77ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,65 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - [[package]] name = "anyhow" version = "1.0.102" @@ -118,35 +59,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "env_filter" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -604,42 +516,12 @@ dependencies = [ "serde_core", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jiff" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "khronos_api" version = "3.1.0" @@ -687,9 +569,8 @@ dependencies = [ [[package]] name = "moongreet" -version = "0.3.2" +version = "0.4.0" dependencies = [ - "env_logger", "gdk-pixbuf", "gdk4", "gio", @@ -700,6 +581,7 @@ dependencies = [ "log", "serde", "serde_json", + "systemd-journal-logger", "tempfile", "toml 0.8.23", ] @@ -710,12 +592,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "pango" version = "0.22.0" @@ -752,21 +628,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-atomic-util" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" -dependencies = [ - "portable-atomic", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -810,35 +671,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - [[package]] name = "rustc_version" version = "0.4.1" @@ -964,6 +796,16 @@ dependencies = [ "version-compare", ] +[[package]] +name = "systemd-journal-logger" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7266304d24ca5a4b230545fc558c80e18bd3e1d2eb1be149b6bcd04398d3e79c" +dependencies = [ + "log", + "rustix", +] + [[package]] name = "target-lexicon" version = "0.13.3" @@ -1096,12 +938,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "version-compare" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 2d48f6d..4dfb7b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moongreet" -version = "0.3.2" +version = "0.4.0" edition = "2024" description = "A greetd greeter for Wayland with GTK4 and Layer Shell" license = "MIT" @@ -16,7 +16,7 @@ toml = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" log = "0.4" -env_logger = "0.11" +systemd-journal-logger = "2.2" [dev-dependencies] tempfile = "3" diff --git a/src/config.rs b/src/config.rs index 6635fd9..c529647 100644 --- a/src/config.rs +++ b/src/config.rs @@ -40,27 +40,39 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config { let mut merged = Config::default(); for path in paths { - if let Ok(content) = fs::read_to_string(path) { - if let Ok(parsed) = toml::from_str::(&content) { - if let Some(appearance) = parsed.appearance { - if let Some(bg) = appearance.background { - // Resolve relative paths against config file directory - let bg_path = PathBuf::from(&bg); - if bg_path.is_absolute() { - merged.background_path = Some(bg); - } else if let Some(parent) = path.parent() { - merged.background_path = - Some(parent.join(&bg).to_string_lossy().to_string()); + match fs::read_to_string(path) { + Ok(content) => { + match toml::from_str::(&content) { + Ok(parsed) => { + log::debug!("Config loaded: {}", path.display()); + if let Some(appearance) = parsed.appearance { + if let Some(bg) = appearance.background { + // Resolve relative paths against config file directory + let bg_path = PathBuf::from(&bg); + if bg_path.is_absolute() { + merged.background_path = Some(bg); + } else if let Some(parent) = path.parent() { + merged.background_path = + Some(parent.join(&bg).to_string_lossy().to_string()); + } + } + if appearance.gtk_theme.is_some() { + merged.gtk_theme = appearance.gtk_theme; + } } } - if appearance.gtk_theme.is_some() { - merged.gtk_theme = appearance.gtk_theme; + Err(e) => { + log::warn!("Config parse error in {}: {e}", path.display()); } } } + Err(_) => { + log::debug!("Config not found: {}", path.display()); + } } } + log::debug!("Config result: background={:?}, gtk_theme={:?}", merged.background_path, merged.gtk_theme); merged } @@ -77,16 +89,20 @@ pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) if let Some(ref bg) = config.background_path { let path = PathBuf::from(bg); if path.is_file() { + log::debug!("Wallpaper: using config path {}", path.display()); return path; } + log::debug!("Wallpaper: config path {} not found, trying fallbacks", path.display()); } // Moonarch ecosystem default if moonarch_wallpaper.is_file() { + log::debug!("Wallpaper: using moonarch default {}", moonarch_wallpaper.display()); return moonarch_wallpaper.to_path_buf(); } // GResource fallback path (loaded from compiled resources at runtime) + log::debug!("Wallpaper: using GResource fallback"); PathBuf::from(format!("{GRESOURCE_PREFIX}/wallpaper.jpg")) } diff --git a/src/greeter.rs b/src/greeter.rs index 6e5b889..4bc9e51 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -163,6 +163,10 @@ pub fn create_greeter_window( let strings = load_strings(None); let all_users = users::get_users(None); let all_sessions = sessions::get_sessions(None, None); + log::debug!("Greeter window: {} user(s), {} session(s)", all_users.len(), all_sessions.len()); + if let Some(ref theme) = config.gtk_theme { + log::debug!("GTK theme: {theme}"); + } let state = Rc::new(RefCell::new(GreeterState { selected_user: None, @@ -489,6 +493,7 @@ fn select_initial_user( .as_ref() .and_then(|name| users.iter().find(|u| &u.username == name)) .unwrap_or(&users[0]); + log::debug!("Initial user: {} (last_user={:?})", target.username, last_username); switch_to_user( target, @@ -515,6 +520,7 @@ fn switch_to_user( sessions: &[Session], window: >k::ApplicationWindow, ) { + log::debug!("Switching to user: {}", user.username); { let mut s = state.borrow_mut(); s.selected_user = Some(user.clone()); @@ -531,8 +537,10 @@ fn switch_to_user( }; if let Some(texture) = cached { + log::debug!("Avatar cache hit for {}", user.username); avatar_image.set_paintable(Some(&texture)); } else { + log::debug!("Avatar cache miss for {}", user.username); let avatar_path = users::get_avatar_path(&user.username, &user.home); if let Some(path) = avatar_path { // get_avatar_path already checks existence — go straight to loading @@ -558,6 +566,7 @@ fn set_avatar_from_file( // Reject oversized files if let Ok(meta) = std::fs::metadata(path) { if meta.len() > MAX_AVATAR_FILE_SIZE { + log::debug!("Avatar file too large ({} bytes): {}", meta.len(), path.display()); image.set_icon_name(Some("avatar-default-symbolic")); return; } @@ -574,7 +583,8 @@ fn set_avatar_from_file( } image.set_paintable(Some(&texture)); } - Err(_) => { + Err(e) => { + log::debug!("Failed to load avatar {}: {e}", path.display()); image.set_icon_name(Some("avatar-default-symbolic")); } } @@ -590,10 +600,12 @@ fn set_default_avatar( { let s = state.borrow(); if let Some(ref texture) = s.default_avatar_texture { + log::debug!("Default avatar: using cached texture"); image.set_paintable(Some(texture)); return; } } + log::debug!("Default avatar: tinting SVG from GResource"); let resource_path = users::get_default_avatar_path(); if let Ok(bytes) = @@ -698,6 +710,7 @@ fn show_greetd_error( /// Cancel any in-progress greetd session. fn cancel_pending_session(state: &Rc>) { + log::debug!("Cancelling pending greetd session"); let s = state.borrow(); s.login_cancelled .store(true, std::sync::atomic::Ordering::SeqCst); @@ -731,6 +744,7 @@ fn attempt_login( password_entry: >k::PasswordEntry, session_dropdown: >k::DropDown, ) { + log::debug!("Login attempt for user: {}", user.username); let sock_path = match std::env::var("GREETD_SOCK") { Ok(p) if !p.is_empty() => p, _ => { @@ -740,6 +754,7 @@ fn attempt_login( }; // Validate socket path + log::debug!("GREETD_SOCK: {sock_path}"); let sock_pathbuf = PathBuf::from(&sock_path); if !sock_pathbuf.is_absolute() { show_error( @@ -886,9 +901,11 @@ fn login_worker( strings: &Strings, ) -> Result { if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) { + log::debug!("Login cancelled before connect"); return Ok(LoginResult::Cancelled); } + log::debug!("Connecting to greetd socket: {sock_path}"); let mut sock = UnixStream::connect(sock_path).map_err(|e| e.to_string())?; if let Err(e) = sock.set_read_timeout(Some(std::time::Duration::from_secs(10))) { log::warn!("Failed to set read timeout: {e}"); @@ -902,6 +919,7 @@ fn login_worker( } // Step 1: Create session — if a stale session exists, cancel it and retry + log::debug!("Creating greetd session for {username}"); let mut response = ipc::create_session(&mut sock, username).map_err(|e| e.to_string())?; if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) { @@ -909,6 +927,7 @@ fn login_worker( } if response.get("type").and_then(|v| v.as_str()) == Some("error") { + log::debug!("Stale session detected, cancelling and retrying"); let _ = ipc::cancel_session(&mut sock); response = ipc::create_session(&mut sock, username).map_err(|e| e.to_string())?; if login_cancelled.load(std::sync::atomic::Ordering::SeqCst) { @@ -927,6 +946,7 @@ fn login_worker( // Step 2: Send password if auth message received if response.get("type").and_then(|v| v.as_str()) == Some("auth_message") { + log::debug!("Sending auth response for {username}"); response = ipc::post_auth_response(&mut sock, Some(password)).map_err(|e| e.to_string())?; @@ -953,6 +973,7 @@ fn login_worker( // Step 3: Start session if response.get("type").and_then(|v| v.as_str()) == Some("success") { + log::debug!("Auth successful, starting session: {exec_cmd}"); let cmd = match split_shell_words(exec_cmd) { Some(words) if !words.is_empty() => words, _ => { @@ -981,6 +1002,7 @@ fn login_worker( } if response.get("type").and_then(|v| v.as_str()) == Some("success") { + log::info!("Login successful for {username}"); return Ok(LoginResult::Success { username: username.to_string(), }); @@ -1039,8 +1061,10 @@ fn load_last_user_from(path: &Path) -> Option { let content = std::fs::read_to_string(path).ok()?; let username = content.trim(); if is_valid_username(username) { + log::debug!("Loaded last user: {username}"); Some(username.to_string()) } else { + log::debug!("Invalid last user in {}", path.display()); None } } @@ -1050,6 +1074,7 @@ fn save_last_user(username: &str) { } fn save_last_user_to(path: &Path, username: &str) { + log::debug!("Saving last user: {username}"); if let Some(parent) = path.parent() { let _ = std::fs::create_dir_all(parent); } @@ -1072,8 +1097,10 @@ fn load_last_session_from(path: &Path) -> Option { let content = std::fs::read_to_string(path).ok()?; let name = content.trim(); if is_valid_session_name(name) { + log::debug!("Loaded last session: {name}"); Some(name.to_string()) } else { + log::debug!("Invalid last session in {}", path.display()); None } } @@ -1101,6 +1128,7 @@ fn save_last_session(username: &str, session_name: &str) { } fn save_last_session_to(path: &Path, session_name: &str) { + log::debug!("Saving last session: {session_name}"); use std::os::unix::fs::OpenOptionsExt; use std::io::Write; let _ = std::fs::OpenOptions::new() diff --git a/src/i18n.rs b/src/i18n.rs index c15bfe5..85eab27 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -127,10 +127,15 @@ pub fn detect_locale() -> String { .filter(|s| !s.is_empty()) .or_else(|| read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF))); - match lang { - Some(l) => parse_lang_prefix(&l), + let result = match lang { + Some(ref l) => parse_lang_prefix(l), None => "en".to_string(), - } + }; + log::debug!("Detected locale: {result} (source: {})", match lang { + Some(_) => "LANG env or locale.conf", + None => "default", + }); + result } /// Return the string table for the given locale, defaulting to English. diff --git a/src/ipc.rs b/src/ipc.rs index 51d9f7a..1c92f84 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -84,6 +84,9 @@ pub fn send_message( return Err(IpcError::PayloadTooLarge(payload.len())); } + let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("unknown"); + log::debug!("IPC send: type={msg_type}, size={} bytes", payload.len()); + let header = (payload.len() as u32).to_le_bytes(); stream.write_all(&header)?; stream.write_all(&payload)?; @@ -101,6 +104,8 @@ pub fn recv_message(stream: &mut UnixStream) -> Result Result<(), PowerError> { + log::debug!("Power action: {action} ({program} {args:?})"); let child = Command::new(program) .args(args) .spawn() @@ -38,6 +39,9 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), message: e.to_string(), })?; + if output.status.success() { + log::debug!("Power action {action} completed successfully"); + } if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(PowerError::CommandFailed { diff --git a/src/sessions.rs b/src/sessions.rs index 7bd7d03..9f9acbd 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -46,8 +46,17 @@ fn parse_desktop_file(path: &Path, session_type: &str) -> Option { } } - let name = name.filter(|s| !s.is_empty())?; - let exec_cmd = exec_cmd.filter(|s| !s.is_empty())?; + let name = name.filter(|s| !s.is_empty()); + let exec_cmd = exec_cmd.filter(|s| !s.is_empty()); + + if name.is_none() || exec_cmd.is_none() { + log::debug!("Skipping {}: missing Name={} Exec={}", path.display(), + name.is_some(), exec_cmd.is_some()); + return None; + } + + let name = name?; + let exec_cmd = exec_cmd?; Some(Session { name, @@ -74,7 +83,10 @@ pub fn get_sessions( for (dirs, session_type) in [(wayland, "wayland"), (xsession, "x11")] { for directory in dirs { let entries = match fs::read_dir(directory) { - Ok(e) => e, + Ok(e) => { + log::debug!("Scanning session directory: {}", directory.display()); + e + } Err(_) => continue, }; @@ -93,6 +105,7 @@ pub fn get_sessions( } } + log::debug!("Found {} session(s)", sessions.len()); sessions } diff --git a/src/users.rs b/src/users.rs index 981efa9..49a2b15 100644 --- a/src/users.rs +++ b/src/users.rs @@ -46,7 +46,10 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec { let content = match fs::read_to_string(path) { Ok(c) => c, - Err(_) => return Vec::new(), + Err(e) => { + log::warn!("Failed to read passwd file {}: {e}", path.display()); + return Vec::new(); + } }; let mut users = Vec::new(); @@ -88,6 +91,7 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec { }); } + log::debug!("Found {} login user(s)", users.len()); users } @@ -106,21 +110,28 @@ pub fn get_avatar_path_with( // AccountsService icon takes priority if accountsservice_dir.exists() { let icon = accountsservice_dir.join(username); - if let Ok(meta) = icon.symlink_metadata() - && !meta.file_type().is_symlink() - { - return Some(icon); + if let Ok(meta) = icon.symlink_metadata() { + if meta.file_type().is_symlink() { + log::warn!("Rejecting symlink avatar for {username}: {}", icon.display()); + } else { + log::debug!("Avatar for {username}: AccountsService {}", icon.display()); + return Some(icon); + } } } // ~/.face fallback let face = home.join(".face"); - if let Ok(meta) = face.symlink_metadata() - && !meta.file_type().is_symlink() - { - return Some(face); + if let Ok(meta) = face.symlink_metadata() { + if meta.file_type().is_symlink() { + log::warn!("Rejecting symlink avatar for {username}: {}", face.display()); + } else { + log::debug!("Avatar for {username}: ~/.face {}", face.display()); + return Some(face); + } } + log::debug!("No avatar found for {username}"); None }