All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Third triple audit (quality, performance, security). Key fixes: - Blur padding offset: texture at (-pad,-pad) prevents edge darkening on all sides - Wallpaper loads after lock.lock() — disk I/O no longer delays lock acquisition - begin_verification disconnects old signal handler before registering new one - resume_async resets failed_attempts to prevent premature exhaustion - Unknown VerifyStatus with done=true triggers restart instead of hanging - symlink_metadata() replaces separate is_file()+is_symlink() (TOCTOU) - faillock_warning dead code removed, blur sigma clamped to [0,100] - Redundant Zeroizing<Vec<u8>> removed, on_verify_status restricted to pub(crate) - Warn logging for non-UTF-8 GECOS and avatar path errors - Default impl for FingerprintListener, 3 new tests (47 total)
152 lines
5.9 KiB
Rust
152 lines
5.9 KiB
Rust
// ABOUTME: Configuration loading for the lockscreen.
|
|
// ABOUTME: Reads moonlock.toml for wallpaper and fingerprint settings with fallback hierarchy.
|
|
|
|
use serde::Deserialize;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
const MOONARCH_WALLPAPER: &str = "/usr/share/moonarch/wallpaper.jpg";
|
|
|
|
fn default_config_paths() -> Vec<PathBuf> {
|
|
let mut paths = vec![PathBuf::from("/etc/moonlock/moonlock.toml")];
|
|
if let Some(home) = std::env::var_os("HOME") {
|
|
paths.push(PathBuf::from(home).join(".config/moonlock/moonlock.toml"));
|
|
}
|
|
paths
|
|
}
|
|
|
|
/// Raw deserialization struct — fingerprint_enabled is optional so that
|
|
/// an empty user config does not override the system config's value.
|
|
#[derive(Debug, Clone, Default, Deserialize)]
|
|
struct RawConfig {
|
|
pub background_path: Option<String>,
|
|
pub background_blur: Option<f32>,
|
|
pub fingerprint_enabled: Option<bool>,
|
|
}
|
|
|
|
/// Resolved configuration with concrete values.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Config {
|
|
pub background_path: Option<String>,
|
|
pub background_blur: Option<f32>,
|
|
pub fingerprint_enabled: bool,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Config {
|
|
background_path: None,
|
|
background_blur: None,
|
|
fingerprint_enabled: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
|
|
let default_paths = default_config_paths();
|
|
let paths = config_paths.unwrap_or(&default_paths);
|
|
let mut merged = Config::default();
|
|
for path in paths {
|
|
if let Ok(content) = fs::read_to_string(path) {
|
|
match toml::from_str::<RawConfig>(&content) {
|
|
Ok(parsed) => {
|
|
if parsed.background_path.is_some() { merged.background_path = parsed.background_path; }
|
|
if let Some(blur) = parsed.background_blur {
|
|
merged.background_blur = Some(blur.clamp(0.0, 100.0));
|
|
}
|
|
if let Some(fp) = parsed.fingerprint_enabled { merged.fingerprint_enabled = fp; }
|
|
}
|
|
Err(e) => {
|
|
log::warn!("Failed to parse {}: {e}", path.display());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
merged
|
|
}
|
|
|
|
pub fn resolve_background_path(config: &Config) -> Option<PathBuf> {
|
|
resolve_background_path_with(config, Path::new(MOONARCH_WALLPAPER))
|
|
}
|
|
|
|
pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) -> Option<PathBuf> {
|
|
if let Some(ref bg) = config.background_path {
|
|
let path = PathBuf::from(bg);
|
|
if let Ok(meta) = path.symlink_metadata() {
|
|
if meta.is_file() && !meta.file_type().is_symlink() { return Some(path); }
|
|
}
|
|
}
|
|
if moonarch_wallpaper.is_file() { return Some(moonarch_wallpaper.to_path_buf()); }
|
|
None
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test] fn default_config() { let c = Config::default(); assert!(c.background_path.is_none()); assert!(c.background_blur.is_none()); assert!(c.fingerprint_enabled); }
|
|
#[test] fn load_default_fingerprint_true() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let conf = dir.path().join("moonlock.toml");
|
|
fs::write(&conf, "").unwrap();
|
|
let c = load_config(Some(&[conf]));
|
|
assert!(c.fingerprint_enabled);
|
|
}
|
|
#[test] fn load_background() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let conf = dir.path().join("moonlock.toml");
|
|
fs::write(&conf, "background_path = \"/custom/bg.jpg\"\nbackground_blur = 15.0\nfingerprint_enabled = false\n").unwrap();
|
|
let c = load_config(Some(&[conf]));
|
|
assert_eq!(c.background_path.as_deref(), Some("/custom/bg.jpg"));
|
|
assert_eq!(c.background_blur, Some(15.0));
|
|
assert!(!c.fingerprint_enabled);
|
|
}
|
|
#[test] fn load_blur_optional() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let conf = dir.path().join("moonlock.toml");
|
|
fs::write(&conf, "background_path = \"/bg.jpg\"\n").unwrap();
|
|
let c = load_config(Some(&[conf]));
|
|
assert!(c.background_blur.is_none());
|
|
}
|
|
#[test] fn resolve_config_path() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let wp = dir.path().join("bg.jpg"); fs::write(&wp, "fake").unwrap();
|
|
let c = Config { background_path: Some(wp.to_str().unwrap().to_string()), ..Config::default() };
|
|
assert_eq!(resolve_background_path_with(&c, Path::new("/nonexistent")), Some(wp));
|
|
}
|
|
#[test] fn empty_user_config_preserves_system_fingerprint() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let sys_conf = dir.path().join("system.toml");
|
|
let usr_conf = dir.path().join("user.toml");
|
|
fs::write(&sys_conf, "fingerprint_enabled = false\n").unwrap();
|
|
fs::write(&usr_conf, "").unwrap();
|
|
let c = load_config(Some(&[sys_conf, usr_conf]));
|
|
assert!(!c.fingerprint_enabled);
|
|
}
|
|
#[test] fn resolve_no_wallpaper_returns_none() {
|
|
let c = Config::default();
|
|
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
|
|
assert!(r.is_none());
|
|
}
|
|
#[test] fn toml_parse_error_returns_default() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let conf = dir.path().join("moonlock.toml");
|
|
fs::write(&conf, "this is not valid toml {{{{").unwrap();
|
|
let c = load_config(Some(&[conf]));
|
|
assert!(c.fingerprint_enabled);
|
|
assert!(c.background_path.is_none());
|
|
}
|
|
#[cfg(unix)]
|
|
#[test] fn symlink_rejected_for_background() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let real = dir.path().join("bg.jpg");
|
|
let link = dir.path().join("link.jpg");
|
|
fs::write(&real, "fake").unwrap();
|
|
std::os::unix::fs::symlink(&real, &link).unwrap();
|
|
let c = Config { background_path: Some(link.to_str().unwrap().to_string()), ..Config::default() };
|
|
// Symlink should be rejected — falls through to None
|
|
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
|
|
assert!(r.is_none());
|
|
}
|
|
}
|