96c94f030a
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.
107 lines
3.0 KiB
Rust
107 lines
3.0 KiB
Rust
// ABOUTME: Power actions — reboot and shutdown via loginctl.
|
|
// ABOUTME: Wrappers around system commands for the greeter UI.
|
|
|
|
use std::fmt;
|
|
use std::process::Command;
|
|
|
|
#[derive(Debug)]
|
|
pub enum PowerError {
|
|
CommandFailed { action: &'static str, message: String },
|
|
}
|
|
|
|
impl fmt::Display for PowerError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
PowerError::CommandFailed { action, message } => {
|
|
write!(f, "{action} failed: {message}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for PowerError {}
|
|
|
|
/// Run a command and return a PowerError on failure.
|
|
fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> {
|
|
log::debug!("Power action: {action} ({program} {args:?})");
|
|
let child = Command::new(program)
|
|
.args(args)
|
|
.spawn()
|
|
.map_err(|e| PowerError::CommandFailed {
|
|
action,
|
|
message: e.to_string(),
|
|
})?;
|
|
|
|
let output = child
|
|
.wait_with_output()
|
|
.map_err(|e| PowerError::CommandFailed {
|
|
action,
|
|
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 {
|
|
action,
|
|
message: format!("exit code {}: {}", output.status, stderr.trim()),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Reboot the system via loginctl.
|
|
pub fn reboot() -> Result<(), PowerError> {
|
|
run_command("reboot", "/usr/bin/loginctl", &["reboot"])
|
|
}
|
|
|
|
/// Shut down the system via loginctl.
|
|
pub fn shutdown() -> Result<(), PowerError> {
|
|
run_command("shutdown", "/usr/bin/loginctl", &["poweroff"])
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn power_error_command_failed_display() {
|
|
let err = PowerError::CommandFailed {
|
|
action: "reboot",
|
|
message: "No such file or directory".to_string(),
|
|
};
|
|
assert_eq!(err.to_string(), "reboot failed: No such file or directory");
|
|
}
|
|
|
|
#[test]
|
|
fn run_command_returns_error_for_missing_binary() {
|
|
let result = run_command("test", "nonexistent-binary-xyz", &[]);
|
|
assert!(result.is_err());
|
|
let err = result.unwrap_err();
|
|
assert!(matches!(err, PowerError::CommandFailed { action: "test", .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn run_command_returns_error_on_nonzero_exit() {
|
|
let result = run_command("test", "false", &[]);
|
|
assert!(result.is_err());
|
|
let err = result.unwrap_err();
|
|
assert!(matches!(err, PowerError::CommandFailed { action: "test", .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn run_command_succeeds_for_true() {
|
|
let result = run_command("test", "true", &[]);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn run_command_passes_args() {
|
|
let result = run_command("test", "echo", &["hello", "world"]);
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|