// 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()); } }