1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

treewide: unify error handling with special error type across modules

This commit is contained in:
NotAShelf 2025-05-17 07:17:09 +03:00 committed by raf
parent 506151ac79
commit e83941d64b
4 changed files with 137 additions and 63 deletions

View file

@ -1,13 +1,13 @@
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::cpu; use crate::cpu;
use crate::monitor; use crate::monitor;
use std::error::Error; use crate::util::error::AppError;
use std::fs; use std::fs;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::time::Duration; use std::time::Duration;
/// Prints comprehensive debug information about the system /// Prints comprehensive debug information about the system
pub fn run_debug(config: &AppConfig) -> Result<(), Box<dyn Error>> { pub fn run_debug(config: &AppConfig) -> Result<(), AppError> {
println!("=== SUPERFREQ DEBUG INFORMATION ==="); println!("=== SUPERFREQ DEBUG INFORMATION ===");
println!("Version: {}", env!("CARGO_PKG_VERSION")); println!("Version: {}", env!("CARGO_PKG_VERSION"));
@ -201,26 +201,31 @@ pub fn run_debug(config: &AppConfig) -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
Err(e) => Err(Box::new(e) as Box<dyn Error>), Err(e) => Err(AppError::Monitor(e)),
} }
} }
/// Get kernel version information /// Get kernel version information
fn get_kernel_info() -> Result<String, Box<dyn Error>> { fn get_kernel_info() -> Result<String, AppError> {
let output = Command::new("uname").arg("-r").output()?; let output = Command::new("uname")
.arg("-r")
.output()
.map_err(AppError::Io)?;
let kernel_version = String::from_utf8(output.stdout)?; let kernel_version = String::from_utf8(output.stdout)
.map_err(|e| AppError::Generic(format!("Failed to parse kernel version: {e}")))?;
Ok(kernel_version.trim().to_string()) Ok(kernel_version.trim().to_string())
} }
/// Get system uptime /// Get system uptime
fn get_system_uptime() -> Result<Duration, Box<dyn Error>> { fn get_system_uptime() -> Result<Duration, AppError> {
let uptime_str = fs::read_to_string("/proc/uptime")?; let uptime_str = fs::read_to_string("/proc/uptime").map_err(AppError::Io)?;
let uptime_secs = uptime_str let uptime_secs = uptime_str
.split_whitespace() .split_whitespace()
.next() .next()
.ok_or("Invalid uptime format")? .ok_or_else(|| AppError::Generic("Invalid uptime format".to_string()))?
.parse::<f64>()?; .parse::<f64>()
.map_err(|e| AppError::Generic(format!("Failed to parse uptime: {e}")))?;
Ok(Duration::from_secs_f64(uptime_secs)) Ok(Duration::from_secs_f64(uptime_secs))
} }
@ -237,14 +242,16 @@ fn check_and_print_sysfs_path(path: &str, description: &str) {
} }
/// Check if a systemd service is active /// Check if a systemd service is active
fn is_systemd_service_active(service_name: &str) -> Result<bool, Box<dyn Error>> { fn is_systemd_service_active(service_name: &str) -> Result<bool, AppError> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
.arg("is-active") .arg("is-active")
.arg(format!("{service_name}.service")) .arg(format!("{service_name}.service"))
.stdout(Stdio::piped()) // capture stdout instead of letting it print .stdout(Stdio::piped()) // capture stdout instead of letting it print
.stderr(Stdio::null()) // redirect stderr to null .stderr(Stdio::null()) // redirect stderr to null
.output()?; .output()
.map_err(AppError::Io)?;
let status = String::from_utf8(output.stdout)?; let status = String::from_utf8(output.stdout)
.map_err(|e| AppError::Generic(format!("Failed to parse systemctl output: {e}")))?;
Ok(status.trim() == "active") Ok(status.trim() == "active")
} }

View file

@ -3,6 +3,7 @@ use crate::config::{AppConfig, LogLevel};
use crate::core::SystemReport; use crate::core::SystemReport;
use crate::engine; use crate::engine;
use crate::monitor; use crate::monitor;
use crate::util::error::AppError;
use log::{LevelFilter, debug, error, info, warn}; use log::{LevelFilter, debug, error, info, warn};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fs::File; use std::fs::File;
@ -342,7 +343,7 @@ impl SystemHistory {
} }
/// Run the daemon /// Run the daemon
pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box<dyn std::error::Error>> { pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), AppError> {
// Set effective log level based on config and verbose flag // Set effective log level based on config and verbose flag
let effective_log_level = if verbose { let effective_log_level = if verbose {
LogLevel::Debug LogLevel::Debug
@ -372,7 +373,7 @@ pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box<dyn st
info!("Received shutdown signal, exiting..."); info!("Received shutdown signal, exiting...");
r.store(false, Ordering::SeqCst); r.store(false, Ordering::SeqCst);
}) })
.expect("Error setting Ctrl-C handler"); .map_err(|e| AppError::Generic(format!("Error setting Ctrl-C handler: {e}")))?;
info!( info!(
"Daemon initialized with poll interval: {}s", "Daemon initialized with poll interval: {}s",

View file

@ -10,10 +10,11 @@ mod util;
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::core::{GovernorOverrideMode, TurboSetting};
use crate::util::error::ControlError; use crate::util::error::{AppError, ControlError};
use clap::{Parser, value_parser}; use clap::{Parser, value_parser};
use env_logger::Builder; use env_logger::Builder;
use log::{debug, error, info}; use log::{debug, error, info};
use std::error::Error;
use std::sync::Once; use std::sync::Once;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -88,7 +89,7 @@ enum Commands {
}, },
} }
fn main() { fn main() -> Result<(), AppError> {
// Initialize logger once for the entire application // Initialize logger once for the entire application
init_logger(); init_logger();
@ -105,7 +106,7 @@ fn main() {
} }
}; };
let command_result = match cli.command { let command_result: Result<(), AppError> = match cli.command {
// TODO: This will be moved to a different module in the future. // TODO: This will be moved to a different module in the future.
Some(Commands::Info) => match monitor::collect_system_report(&config) { Some(Commands::Info) => match monitor::collect_system_report(&config) {
Ok(report) => { Ok(report) => {
@ -341,41 +342,30 @@ fn main() {
); );
Ok(()) Ok(())
} }
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>), Err(e) => Err(AppError::Monitor(e)),
}, },
Some(Commands::SetGovernor { governor, core_id }) => cpu::set_governor(&governor, core_id) Some(Commands::SetGovernor { governor, core_id }) => {
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>), cpu::set_governor(&governor, core_id).map_err(AppError::Control)
}
Some(Commands::ForceGovernor { mode }) => { Some(Commands::ForceGovernor { mode }) => {
cpu::force_governor(mode).map_err(|e| Box::new(e) as Box<dyn std::error::Error>) cpu::force_governor(mode).map_err(AppError::Control)
}
Some(Commands::SetTurbo { setting }) => {
cpu::set_turbo(setting).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
} }
Some(Commands::SetTurbo { setting }) => cpu::set_turbo(setting).map_err(AppError::Control),
Some(Commands::SetEpp { epp, core_id }) => { Some(Commands::SetEpp { epp, core_id }) => {
cpu::set_epp(&epp, core_id).map_err(|e| Box::new(e) as Box<dyn std::error::Error>) cpu::set_epp(&epp, core_id).map_err(AppError::Control)
} }
Some(Commands::SetEpb { epb, core_id }) => { Some(Commands::SetEpb { epb, core_id }) => {
cpu::set_epb(&epb, core_id).map_err(|e| Box::new(e) as Box<dyn std::error::Error>) cpu::set_epb(&epb, core_id).map_err(AppError::Control)
} }
Some(Commands::SetMinFreq { freq_mhz, core_id }) => { Some(Commands::SetMinFreq { freq_mhz, core_id }) => {
// Basic validation for reasonable CPU frequency values // Basic validation for reasonable CPU frequency values
if let Err(e) = validate_freq(freq_mhz, "Minimum") { validate_freq(freq_mhz, "Minimum")?;
error!("{e}"); cpu::set_min_frequency(freq_mhz, core_id).map_err(AppError::Control)
Err(e)
} else {
cpu::set_min_frequency(freq_mhz, core_id)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
}
} }
Some(Commands::SetMaxFreq { freq_mhz, core_id }) => { Some(Commands::SetMaxFreq { freq_mhz, core_id }) => {
// Basic validation for reasonable CPU frequency values // Basic validation for reasonable CPU frequency values
if let Err(e) = validate_freq(freq_mhz, "Maximum") { validate_freq(freq_mhz, "Maximum")?;
error!("{e}"); cpu::set_max_frequency(freq_mhz, core_id).map_err(AppError::Control)
Err(e)
} else {
cpu::set_max_frequency(freq_mhz, core_id)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
}
} }
Some(Commands::SetPlatformProfile { profile }) => { Some(Commands::SetPlatformProfile { profile }) => {
// Get available platform profiles and validate early if possible // Get available platform profiles and validate early if possible
@ -383,26 +373,24 @@ fn main() {
Ok(available_profiles) => { Ok(available_profiles) => {
if available_profiles.contains(&profile) { if available_profiles.contains(&profile) {
info!("Setting platform profile to '{profile}'"); info!("Setting platform profile to '{profile}'");
cpu::set_platform_profile(&profile) cpu::set_platform_profile(&profile).map_err(AppError::Control)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
} else { } else {
error!( error!(
"Invalid platform profile: '{}'. Available profiles: {}", "Invalid platform profile: '{}'. Available profiles: {}",
profile, profile,
available_profiles.join(", ") available_profiles.join(", ")
); );
Err(Box::new(ControlError::InvalidProfile(format!( Err(ControlError::InvalidProfile(format!(
"Invalid platform profile: '{}'. Available profiles: {}", "Invalid platform profile: '{}'. Available profiles: {}",
profile, profile,
available_profiles.join(", ") available_profiles.join(", ")
))) as Box<dyn std::error::Error>) ))
.into())
} }
} }
Err(_) => { Err(_e) => {
// If we can't get profiles (e.g., feature not supported), pass through to the function // If we can't get profiles (e.g., feature not supported), pass through to the function
// which will provide appropriate error cpu::set_platform_profile(&profile).map_err(AppError::Control)
cpu::set_platform_profile(&profile)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
} }
} }
} }
@ -415,15 +403,15 @@ fn main() {
error!( error!(
"Start threshold ({start_threshold}) must be less than stop threshold ({stop_threshold})" "Start threshold ({start_threshold}) must be less than stop threshold ({stop_threshold})"
); );
Err(Box::new(ControlError::InvalidValueError(format!( Err(ControlError::InvalidValueError(format!(
"Start threshold ({start_threshold}) must be less than stop threshold ({stop_threshold})" "Start threshold ({start_threshold}) must be less than stop threshold ({stop_threshold})"
))) as Box<dyn std::error::Error>) )).into())
} else { } else {
info!( info!(
"Setting battery thresholds: start at {start_threshold}%, stop at {stop_threshold}%" "Setting battery thresholds: start at {start_threshold}%, stop at {stop_threshold}%"
); );
battery::set_battery_charge_thresholds(start_threshold, stop_threshold) battery::set_battery_charge_thresholds(start_threshold, stop_threshold)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>) .map_err(AppError::Control)
} }
} }
Some(Commands::Daemon { verbose }) => daemon::run_daemon(config, verbose), Some(Commands::Daemon { verbose }) => daemon::run_daemon(config, verbose),
@ -440,11 +428,9 @@ fn main() {
if let Some(source) = e.source() { if let Some(source) = e.source() {
error!("Caused by: {source}"); error!("Caused by: {source}");
} }
// TODO: Consider specific error handling for PermissionDenied from the cpu module here.
// For example, check if `e.downcast_ref::<cpu::ControlError>()` matches `PermissionDenied` // Check for permission denied errors
// and print a more specific message like "Try running with sudo." if let AppError::Control(control_error) = &e {
// We'll revisit this in the future once CPU logic is more stable.
if let Some(control_error) = e.downcast_ref::<ControlError>() {
if matches!(control_error, ControlError::PermissionDenied(_)) { if matches!(control_error, ControlError::PermissionDenied(_)) {
error!( error!(
"Hint: This operation may require administrator privileges (e.g., run with sudo)." "Hint: This operation may require administrator privileges (e.g., run with sudo)."
@ -454,6 +440,8 @@ fn main() {
std::process::exit(1); std::process::exit(1);
} }
Ok(())
} }
/// Initialize the logger for the entire application /// Initialize the logger for the entire application
@ -474,18 +462,17 @@ fn init_logger() {
} }
/// Validate CPU frequency input values /// Validate CPU frequency input values
fn validate_freq(freq_mhz: u32, label: &str) -> Result<(), Box<dyn std::error::Error>> { fn validate_freq(freq_mhz: u32, label: &str) -> Result<(), AppError> {
if freq_mhz == 0 { if freq_mhz == 0 {
error!("{label} frequency cannot be zero"); error!("{label} frequency cannot be zero");
Err(Box::new(ControlError::InvalidValueError(format!( Err(ControlError::InvalidValueError(format!("{label} frequency cannot be zero")).into())
"{label} frequency cannot be zero"
))) as Box<dyn std::error::Error>)
} else if freq_mhz > 10000 { } else if freq_mhz > 10000 {
// Extremely high value unlikely to be valid // Extremely high value unlikely to be valid
error!("{label} frequency ({freq_mhz} MHz) is unreasonably high"); error!("{label} frequency ({freq_mhz} MHz) is unreasonably high");
Err(Box::new(ControlError::InvalidValueError(format!( Err(ControlError::InvalidValueError(format!(
"{label} frequency ({freq_mhz} MHz) is unreasonably high" "{label} frequency ({freq_mhz} MHz) is unreasonably high"
))) as Box<dyn std::error::Error>) ))
.into())
} else { } else {
Ok(()) Ok(())
} }

View file

@ -113,3 +113,82 @@ impl std::error::Error for EngineError {
} }
} }
} }
// A unified error type for the entire application
#[derive(Debug)]
pub enum AppError {
Control(ControlError),
Monitor(SysMonitorError),
Engine(EngineError),
Config(crate::config::ConfigError),
Generic(String),
Io(io::Error),
}
impl From<ControlError> for AppError {
fn from(err: ControlError) -> Self {
Self::Control(err)
}
}
impl From<SysMonitorError> for AppError {
fn from(err: SysMonitorError) -> Self {
Self::Monitor(err)
}
}
impl From<EngineError> for AppError {
fn from(err: EngineError) -> Self {
Self::Engine(err)
}
}
impl From<crate::config::ConfigError> for AppError {
fn from(err: crate::config::ConfigError) -> Self {
Self::Config(err)
}
}
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<String> for AppError {
fn from(err: String) -> Self {
Self::Generic(err)
}
}
impl From<&str> for AppError {
fn from(err: &str) -> Self {
Self::Generic(err.to_string())
}
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Control(e) => write!(f, "{e}"),
Self::Monitor(e) => write!(f, "{e}"),
Self::Engine(e) => write!(f, "{e}"),
Self::Config(e) => write!(f, "{e}"),
Self::Generic(s) => write!(f, "{s}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Control(e) => Some(e),
Self::Monitor(e) => Some(e),
Self::Engine(e) => Some(e),
Self::Config(e) => Some(e),
Self::Generic(_) => None,
Self::Io(e) => Some(e),
}
}
}