From b97d86bdeef06f812b3a95cd9bec264c6cb7cf03 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 20 Aug 2026 18:22:24 +0200 Subject: [PATCH 1/2] chore(cardwired): Replace anyhow with CardwireError --- Cargo.lock | 1 - crates/cardwire-daemon/Cargo.toml | 1 - crates/cardwire-daemon/src/analyzer/models.rs | 19 ++++--- .../src/analyzer/static_analysis.rs | 3 +- crates/cardwire-daemon/src/core/errors.rs | 50 +++++++++++++++---- crates/cardwire-daemon/src/core/inode.rs | 2 +- crates/cardwire-daemon/src/core/pci/iommu.rs | 6 ++- .../src/core/pci/pci_device.rs | 6 ++- crates/cardwire-daemon/src/daemon.rs | 5 +- crates/cardwire-daemon/src/file/common.rs | 41 +++++++-------- crates/cardwire-daemon/src/file/config.rs | 12 ++--- crates/cardwire-daemon/src/file/sql.rs | 6 +-- crates/cardwire-daemon/src/file/state.rs | 42 ++++++++-------- .../cardwire-daemon/src/interface/config.rs | 4 +- .../cardwire-daemon/src/interface/context.rs | 4 +- crates/cardwire-daemon/src/interface/debug.rs | 11 ++-- crates/cardwire-daemon/src/interface/gpu.rs | 4 +- crates/cardwire-daemon/src/interface/mode.rs | 9 ++-- crates/cardwire-daemon/src/manager.rs | 21 +++----- .../src/tasks/watch_power_state.rs | 4 +- crates/cardwire-daemon/src/types.rs | 33 ++---------- 21 files changed, 139 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef3ccfd4..28b595c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -765,7 +765,6 @@ dependencies = [ name = "cardwire-daemon" version = "0.12.1" dependencies = [ - "anyhow", "aya", "aya-log", "cardwire-ebpf-userspace", diff --git a/crates/cardwire-daemon/Cargo.toml b/crates/cardwire-daemon/Cargo.toml index 9aca703a..22f0751b 100644 --- a/crates/cardwire-daemon/Cargo.toml +++ b/crates/cardwire-daemon/Cargo.toml @@ -15,7 +15,6 @@ zbus.workspace = true serde.workspace = true log.workspace = true thiserror.workspace = true -anyhow.workspace = true env_logger.workspace = true toml.workspace = true serde_json.workspace = true diff --git a/crates/cardwire-daemon/src/analyzer/models.rs b/crates/cardwire-daemon/src/analyzer/models.rs index b9292c4f..39d5d1a8 100644 --- a/crates/cardwire-daemon/src/analyzer/models.rs +++ b/crates/cardwire-daemon/src/analyzer/models.rs @@ -1,3 +1,10 @@ +use crate::{ + Result, analyzer::{ + dynamic_analysis::{check_env, get_app_id_wayland_with_retry, get_steam_app_id}, helpers::{ + comm_to_string, get_real_process_name, is_proc_still_alive, normalized_candidates + }, static_analysis::{self, AppMetadata, watch_fdo_folders} + }, file::{DbusAppMetadata, GpuPolicy}, interface::{LogEntry, LoggerInterfaceSignals, SmartPolicyInterface} +}; use aya::maps::{HashMap as AyaHashMap, RingBuf}; use aya_log::EbpfLogger; use cardwire_ebpf_userspace::EbpfBlocker; @@ -9,14 +16,6 @@ use tokio::{ io::{Interest, unix::AsyncFd}, sync::{Mutex, RwLock, Semaphore, mpsc, oneshot}, task, time::Instant }; use zbus::object_server::SignalEmitter; - -use crate::{ - analyzer::{ - dynamic_analysis::{check_env, get_app_id_wayland_with_retry, get_steam_app_id}, helpers::{ - comm_to_string, get_real_process_name, is_proc_still_alive, normalized_candidates - }, static_analysis::{self, AppMetadata, watch_fdo_folders} - }, file::{DbusAppMetadata, GpuPolicy}, interface::{LogEntry, LoggerInterfaceSignals, SmartPolicyInterface} -}; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ExecEvent { @@ -71,7 +70,7 @@ impl CardwireAnalyzer { db_cache: Arc>>, db_tx: mpsc::Sender<(String, AppMetadata, oneshot::Sender)>, new_app_signal: Arc>>, - ) -> anyhow::Result { + ) -> Result { let mut blocker = blocker.write().await; let exec_ring = blocker.get_exec_ring()?; let report_ring = blocker.get_report_ring()?; @@ -112,7 +111,7 @@ impl CardwireAnalyzer { new_app_signal, }) } - pub async fn run(self) -> anyhow::Result<()> { + pub async fn run(self) -> Result<()> { // Clone the Arcs and Sender to move into the background task let exec_arc = self.exec_ring.clone(); let logger_arc = self.ebpf_logger.clone(); diff --git a/crates/cardwire-daemon/src/analyzer/static_analysis.rs b/crates/cardwire-daemon/src/analyzer/static_analysis.rs index f1b33fbd..30c58eae 100644 --- a/crates/cardwire-daemon/src/analyzer/static_analysis.rs +++ b/crates/cardwire-daemon/src/analyzer/static_analysis.rs @@ -1,5 +1,6 @@ //! Functions for static analysis, contains: //! - FDO desktop entries analysis +use crate::Result; use freedesktop_desktop_entry::{DesktopEntry, get_languages_from_env}; use inotify::{EventMask, Inotify, StreamExt, WatchDescriptor, WatchMask}; use log::error; @@ -17,7 +18,7 @@ pub struct AppMetadata { } /// Return a list of fdo apps present in the system -pub async fn get_fdo_apps() -> anyhow::Result<(HashMap, Vec)> { +pub async fn get_fdo_apps() -> Result<(HashMap, Vec)> { let mut app_directories: Vec = Vec::new(); // get from ENV let xdg_dir = BaseDirectories::new(); diff --git a/crates/cardwire-daemon/src/core/errors.rs b/crates/cardwire-daemon/src/core/errors.rs index 808fc67c..94ca97ed 100644 --- a/crates/cardwire-daemon/src/core/errors.rs +++ b/crates/cardwire-daemon/src/core/errors.rs @@ -2,13 +2,22 @@ use std::{io, path}; use thiserror::Error; #[derive(Error, Debug)] -pub enum Error { +pub enum CardwireError { #[error("IO Error: {0}")] Io(#[from] io::Error), #[error("ebpf error: {0}")] CardwireEbpfError(#[from] cardwire_ebpf_userspace::CardwireEbpfError), + #[error("zbus error: {0}")] + ZbusError(#[from] zbus::Error), + + #[error("fdo error: {0}")] + FdoError(#[from] zbus::fdo::Error), + + #[error("rustqlite error: {0}")] + RusqliteError(#[from] rusqlite::Error), + #[error("parse int error: {0}")] ParseInt(#[from] std::num::ParseIntError), @@ -19,15 +28,36 @@ pub enum Error { #[error("Missing 'devices' directory in group path: {0}")] MissingDevicesDir(path::PathBuf), + // Config Error + #[error("Couldn't create the default var folder: {0}")] + VarFolderError(io::Error), + + #[error("Couldn't generate default config: {0}")] + DefaultConfigError(toml::ser::Error), + + #[error("Couldn't generate default json state: {0}")] + DefaultStateError(serde_json::Error), + + #[error("Error with cardwire.toml: {0}")] + CardwireConfigError(io::Error), + + #[error("Error with state_file {0}: {1}")] + CardwireStateError(String, serde_json::Error), + + // Mode errors + #[error("unknown mode: {0}")] + UnknownMode(u32), + #[error("{0}")] Other(String), } -impl From<&str> for Error { +impl From<&str> for CardwireError { fn from(s: &str) -> Self { - Error::Other(s.to_string()) + CardwireError::Other(s.to_string()) } } +pub type Result = core::result::Result; #[cfg(test)] mod tests { @@ -35,42 +65,42 @@ mod tests { #[test] fn test_error_from_str() { - let err = Error::from("something went wrong"); + let err = CardwireError::from("something went wrong"); match err { - Error::Other(msg) => assert_eq!(msg, "something went wrong"), + CardwireError::Other(msg) => assert_eq!(msg, "something went wrong"), _ => panic!("expected Error::Other"), } } #[test] fn test_error_display_iommu_not_enabled() { - let err = Error::IommuNotEnabled; + let err = CardwireError::IommuNotEnabled; assert_eq!(err.to_string(), "IOMMU Not Enabled"); } #[test] fn test_error_display_missing_devices_dir() { - let err = Error::MissingDevicesDir(std::path::PathBuf::from("/sys/test")); + let err = CardwireError::MissingDevicesDir(std::path::PathBuf::from("/sys/test")); assert!(err.to_string().contains("/sys/test")); } #[test] fn test_error_display_io_error() { let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found"); - let err = Error::Io(io_err); + let err = CardwireError::Io(io_err); assert!(err.to_string().contains("file not found")); } #[test] fn test_error_display_parse_int() { let parse_err = "abc".parse::().unwrap_err(); - let err = Error::ParseInt(parse_err); + let err = CardwireError::ParseInt(parse_err); assert!(err.to_string().contains("parse")); } #[test] fn test_error_display_other() { - let err = Error::Other("custom error".to_string()); + let err = CardwireError::Other("custom error".to_string()); assert_eq!(err.to_string(), "custom error"); } } diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index 6a1d9f5e..965110ef 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -3,7 +3,7 @@ use std::{ collections::BTreeMap, fs::{self}, os::unix::fs::MetadataExt, path::Path }; -use anyhow::Result; +use crate::Result; use log::{error, warn}; use cardwire_ebpf_userspace::InodeKey; diff --git a/crates/cardwire-daemon/src/core/pci/iommu.rs b/crates/cardwire-daemon/src/core/pci/iommu.rs index 880e102b..cb2fc069 100644 --- a/crates/cardwire-daemon/src/core/pci/iommu.rs +++ b/crates/cardwire-daemon/src/core/pci/iommu.rs @@ -1,8 +1,10 @@ -use crate::core::{errors::Error as CardwireError, pci::IommuGroup}; +use crate::{ + Result, core::{errors::CardwireError, pci::IommuGroup} +}; use log::error; use std::{collections::BTreeMap, fs, path::Path}; -pub fn read_iommu_groups() -> Result, CardwireError> { +pub fn read_iommu_groups() -> Result> { let base_path = Path::new("/sys/kernel/iommu_groups"); let mut dir_iter = base_path.read_dir().map_err(|e| { error!( diff --git a/crates/cardwire-daemon/src/core/pci/pci_device.rs b/crates/cardwire-daemon/src/core/pci/pci_device.rs index d41c14e2..5495ac71 100644 --- a/crates/cardwire-daemon/src/core/pci/pci_device.rs +++ b/crates/cardwire-daemon/src/core/pci/pci_device.rs @@ -1,5 +1,7 @@ -use crate::core::{ - errors::Error as CardwireError, pci::{PciDevice, is_iommu_enabled, read_iommu_groups} +use crate::{ + Result, core::{ + errors::CardwireError, pci::{PciDevice, is_iommu_enabled, read_iommu_groups} + } }; use log::{error, info, warn}; use std::{ diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index e6742352..8b6e672b 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -7,8 +7,7 @@ mod manager; mod tasks; pub mod types; -use crate::{manager::DaemonManager, tasks::watch_power_state}; -use anyhow::Result; +use crate::{core::errors::Result, manager::DaemonManager, tasks::watch_power_state}; use env_logger::Env; use log::info; use std::{future::pending, sync::Arc}; @@ -128,7 +127,7 @@ async fn main() -> Result<()> { async fn spawn_dbus_api( object_server: &zbus::ObjectServer, daemon: &mut DaemonManager, -) -> anyhow::Result<()> { +) -> Result<()> { let path = "/org/opengamingcollective/cardwire"; let gpu_interfaces = daemon.inner.gpu_list.read().await; diff --git a/crates/cardwire-daemon/src/file/common.rs b/crates/cardwire-daemon/src/file/common.rs index e765b4fc..8b1a81ed 100644 --- a/crates/cardwire-daemon/src/file/common.rs +++ b/crates/cardwire-daemon/src/file/common.rs @@ -1,7 +1,8 @@ //! helper to manage cardwired configs, include the user config .toml, and the .json states like //! gpu, mode or pci -use crate::file::{CardwireConfig, CardwireGpuUnit, CardwireModeState}; -use anyhow::{Context, Ok}; +use crate::{ + Result, core::errors::CardwireError, file::{CardwireConfig, CardwireGpuUnit, CardwireModeState} +}; use std::{collections::BTreeMap, fs, io}; #[allow(dead_code)] @@ -12,30 +13,30 @@ pub enum FileKind { } /// Create all folders cardwire need -pub fn create_default_folder(kind: FileKind) -> anyhow::Result<()> { +pub fn create_default_folder(kind: FileKind) -> Result<(), io::Error> { let directory = match kind { FileKind::Config => crate::CONFIG_PATH, _ => crate::STATE_PATH, }; // fs error that should make the daemon exit if let Err(e) = fs::create_dir_all(directory) { - let _ = match e.kind() { - io::ErrorKind::PermissionDenied => return Err(e.into()), - io::ErrorKind::ReadOnlyFilesystem => return Err(e.into()), - io::ErrorKind::NotADirectory => return Err(e.into()), - _ => Ok(()), + match e.kind() { + io::ErrorKind::PermissionDenied => return Err(e), + io::ErrorKind::ReadOnlyFilesystem => return Err(e), + io::ErrorKind::NotADirectory => return Err(e), + _ => {} }; } Ok(()) } /// Helper function to create default file, used for all config struct -pub fn create_default_file(kind: FileKind) -> anyhow::Result<()> { +pub fn create_default_file(kind: FileKind) -> Result<()> { let result = match kind { FileKind::Config => { - create_default_folder(FileKind::Config) - .context("could not create default folder for cardwire.toml")?; + create_default_folder(FileKind::Config).map_err(CardwireError::VarFolderError)?; // Default config for cardwire - let default_config = toml::to_string_pretty(&CardwireConfig::default())?; + let default_config = toml::to_string_pretty(&CardwireConfig::default()) + .map_err(CardwireError::DefaultConfigError)?; // write fs::write( format!("{}/cardwire.toml", crate::CONFIG_PATH), @@ -43,12 +44,12 @@ pub fn create_default_file(kind: FileKind) -> anyhow::Result<()> { ) } FileKind::GpuState => { - create_default_folder(FileKind::GpuState) - .context("could not create default folder for gpu_state.json")?; + create_default_folder(FileKind::GpuState).map_err(CardwireError::VarFolderError)?; // Default gpu_state for cardwire let mut gpu_hash: BTreeMap = BTreeMap::new(); gpu_hash.insert("Null".to_string(), CardwireGpuUnit::default()); - let default_gpu_state = serde_json::to_string_pretty(&gpu_hash)?; + let default_gpu_state = serde_json::to_string_pretty(&gpu_hash) + .map_err(CardwireError::DefaultStateError)?; // write fs::write( format!("{}/gpu_state.json", crate::STATE_PATH), @@ -56,11 +57,11 @@ pub fn create_default_file(kind: FileKind) -> anyhow::Result<()> { ) } FileKind::ModeState => { - create_default_folder(FileKind::ModeState) - .context("could not create default folder for mode.json")?; + create_default_folder(FileKind::ModeState).map_err(CardwireError::VarFolderError)?; // Default mode for cardwire let default_state = CardwireModeState::default(); - let default_mode_state = serde_json::to_string_pretty(&default_state)?; + let default_mode_state = serde_json::to_string_pretty(&default_state) + .map_err(CardwireError::DefaultStateError)?; // write fs::write( format!("{}/mode.json", crate::STATE_PATH), @@ -69,7 +70,7 @@ pub fn create_default_file(kind: FileKind) -> anyhow::Result<()> { } }; // Handle the fs error here - let result: anyhow::Result<()> = match result { + let result: Result<()> = match result { std::result::Result::Ok(()) => Ok(()), std::result::Result::Err(e) => match e.kind() { io::ErrorKind::PermissionDenied => return Err(e.into()), @@ -81,7 +82,7 @@ pub fn create_default_file(kind: FileKind) -> anyhow::Result<()> { // ignore this one io::ErrorKind::AlreadyExists => Ok(()), // if directory not found, try to create again - io::ErrorKind::NotFound => create_default_folder(kind), + io::ErrorKind::NotFound => create_default_folder(kind).map_err(CardwireError::Io), _ => Ok(()), }, }; diff --git a/crates/cardwire-daemon/src/file/config.rs b/crates/cardwire-daemon/src/file/config.rs index 97522110..9948c1c1 100644 --- a/crates/cardwire-daemon/src/file/config.rs +++ b/crates/cardwire-daemon/src/file/config.rs @@ -1,9 +1,8 @@ //! helper to manage cardwired configs, include the user config .toml, and the .json states like //! gpu, mode or pci use crate::{ - file::common::{FileKind, create_default_file}, types::Modes + Result, core::errors::CardwireError::CardwireConfigError, file::common::{FileKind, create_default_file}, types::Modes }; -use anyhow::Context; use log::warn; use tokio::io::AsyncWriteExt; @@ -50,17 +49,16 @@ impl CardwireConfig { } } /// Read TOML config file and return it's settings as a struct - pub fn build() -> anyhow::Result { + pub fn build() -> Result { let config_file = format!("{}/cardwire.toml", crate::CONFIG_PATH); // create the config if it doesnt exist if !(fs::exists(&config_file)?) { - Self::create_default_config().context("Could not create default dir for config")?; + Self::create_default_config()?; } // remove leftover temp files from a save interrupted by a crash Self::cleanup_stale_tmp_files(); // read the config into a string and parse it - let config_content = - fs::read_to_string(&config_file).context("Could not read cardwire.toml")?; + let config_content = fs::read_to_string(&config_file).map_err(CardwireConfigError)?; Ok(Self::parse_or_default(&config_content)) } /// Remove leftover cardwire.toml.*.tmp files from a save interrupted by a crash @@ -90,7 +88,7 @@ impl CardwireConfig { } } /// Create a default cardwire.toml if not present - fn create_default_config() -> anyhow::Result<()> { + fn create_default_config() -> Result<()> { create_default_file(FileKind::Config)?; Ok(()) } diff --git a/crates/cardwire-daemon/src/file/sql.rs b/crates/cardwire-daemon/src/file/sql.rs index 96b8ff4e..da7c85e4 100644 --- a/crates/cardwire-daemon/src/file/sql.rs +++ b/crates/cardwire-daemon/src/file/sql.rs @@ -1,8 +1,8 @@ use std::{collections::HashMap, sync::Arc}; -use crate::{STATE_PATH, analyzer::AppMetadata}; +use crate::{Result, STATE_PATH, analyzer::AppMetadata}; use log::error; -use rusqlite::{Connection, OptionalExtension, Result}; +use rusqlite::{Connection, OptionalExtension}; use tokio::sync::{RwLock, mpsc, oneshot}; use zbus::zvariant; @@ -171,7 +171,7 @@ impl CardwireDatabase { rusqlite::params![gpu_policy, binary_name], )?; if affected == 0 { - return Err(rusqlite::Error::QueryReturnedNoRows); + return Err(rusqlite::Error::QueryReturnedNoRows.into()); } Ok(()) } diff --git a/crates/cardwire-daemon/src/file/state.rs b/crates/cardwire-daemon/src/file/state.rs index ba695216..f292b060 100644 --- a/crates/cardwire-daemon/src/file/state.rs +++ b/crates/cardwire-daemon/src/file/state.rs @@ -1,9 +1,8 @@ //! helper to manage cardwired configs, include the user config .toml, and the .json states like //! gpu, mode or pci use crate::{ - core::gpu::GpuDevice, file::common::{FileKind, create_default_file}, types::Modes + Result, core::{errors::CardwireError::CardwireStateError, gpu::GpuDevice}, file::common::{FileKind, create_default_file}, types::Modes }; -use anyhow::{Context, Ok}; use log::{info, warn}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fs}; @@ -23,7 +22,7 @@ impl Default for CardwireModeState { impl CardwireModeState { /// Read a mode.json file and return into a struct - pub fn build() -> anyhow::Result { + pub fn build() -> Result { let mode_file = format!("{}/mode.json", crate::STATE_PATH); let mode = Self::parse_mode_state(&mode_file); @@ -31,19 +30,19 @@ impl CardwireModeState { warn!("mode.json could not get parsed {e}, overwriting with default one..."); Self::create_default_mode()?; } - let mode = Self::parse_mode_state(&mode_file).context("couldn't fix mode.json")?; + let mode = Self::parse_mode_state(&mode_file)?; Ok(mode) } - fn parse_mode_state(mode_file: &str) -> anyhow::Result { + fn parse_mode_state(mode_file: &str) -> Result { if !(fs::exists(mode_file)?) { Self::create_default_mode()?; } let mode_state = fs::read_to_string(mode_file)?; - let string_content: CardwireModeState = - serde_json::from_str(&mode_state).context("Failed to parse json to str")?; + let string_content: CardwireModeState = serde_json::from_str(&mode_state) + .map_err(|err| CardwireStateError(String::from("mode.json"), err))?; Ok(string_content) } - fn create_default_mode() -> anyhow::Result<()> { + fn create_default_mode() -> Result<()> { create_default_file(FileKind::ModeState)?; Ok(()) } @@ -51,12 +50,13 @@ impl CardwireModeState { self.mode } /// Update the mode in daemon state, persisting it to mode_state.json only when `save` is true - pub async fn save_state(&mut self, new_mode: Modes, save: bool) -> anyhow::Result<()> { + pub async fn save_state(&mut self, new_mode: Modes, save: bool) -> Result<()> { // Save to daemon state self.mode = new_mode; // Save the whole state into the json if save { - let state_file = serde_json::to_string_pretty(&self)?; + let state_file = serde_json::to_string_pretty(&self) + .map_err(|err| CardwireStateError(String::from("mode.json"), err))?; tokio::fs::write(format!("{}/mode.json", crate::STATE_PATH), state_file).await?; } Ok(()) @@ -85,7 +85,7 @@ pub struct CardwireGpuUnit { impl CardwireGpuState { /// Build a CardwireGpuState struct - pub fn build() -> anyhow::Result { + pub fn build() -> Result { let state_file = format!("{}/gpu_state.json", crate::STATE_PATH); let gpu_hash = Self::parse_gpu_state(&state_file); @@ -93,29 +93,28 @@ impl CardwireGpuState { warn!("gpu_hash.json could not get parsed {e}, overwriting with default one..."); Self::create_default_state()?; } - let gpu_hash = Self::parse_gpu_state(&state_file).context("couldn't fix gpu_hash.json")?; + let gpu_hash = Self::parse_gpu_state(&state_file)?; let gpu_state = CardwireGpuState { gpu: gpu_hash }; Ok(gpu_state) } // Parse directly into CardwireGpuState - fn parse_gpu_state(state_file: &str) -> anyhow::Result> { + fn parse_gpu_state(state_file: &str) -> Result> { if !(fs::exists(state_file)?) { - Self::create_default_state().context("Could not create default gpu_state.json")?; + Self::create_default_state()?; } - let gpu_state = fs::read_to_string(state_file) - .with_context(|| format!("Could not read file {}", state_file))?; + let gpu_state = fs::read_to_string(state_file)?; - let content: BTreeMap = - serde_json::from_str(&gpu_state).context("Could not parse string into json")?; + let content: BTreeMap = serde_json::from_str(&gpu_state) + .map_err(|err| CardwireStateError(String::from("gpu_state.json"), err))?; Ok(content) } /// Create default gpu_state.json, including folders if missing - fn create_default_state() -> anyhow::Result<()> { + fn create_default_state() -> Result<()> { create_default_file(FileKind::GpuState)?; Ok(()) } /// Save the new state into the daemon and to the gpu_state.json file - pub async fn save_state(&mut self, gpu: &GpuDevice, state: bool) -> anyhow::Result<()> { + pub async fn save_state(&mut self, gpu: &GpuDevice, state: bool) -> Result<()> { // Prevent overwriting default config if it's not replaceable if self.gpu.contains_key("Null") { info!("detected default gpu_state file, overwriting it..."); @@ -127,7 +126,8 @@ impl CardwireGpuState { CardwireGpuUnit { block: state }, ); // Save the whole hashmap into json - let state_file = serde_json::to_string_pretty(&self.gpu)?; + let state_file = serde_json::to_string_pretty(&self.gpu) + .map_err(|err| CardwireStateError(String::from("mode.json"), err))?; tokio::fs::write(format!("{}/gpu_state.json", crate::STATE_PATH), state_file).await?; Ok(()) } diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 2da16c81..79851b36 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - file::CardwireConfig, interface::{DaemonContext, Modes} + Result, file::CardwireConfig, interface::{DaemonContext, Modes} }; use cardwire_ebpf_userspace::{EbpfBlocker, EbpfSettings}; use log::warn; @@ -50,7 +50,7 @@ pub struct ConfigInterface { blocker: Arc>, } impl ConfigInterface { - pub fn build(context: &DaemonContext) -> anyhow::Result { + pub fn build(context: &DaemonContext) -> Result { Ok(Self { config: context.config.clone(), blocker: context.blocker.clone(), diff --git a/crates/cardwire-daemon/src/interface/context.rs b/crates/cardwire-daemon/src/interface/context.rs index f90b4c31..8bcd09d2 100644 --- a/crates/cardwire-daemon/src/interface/context.rs +++ b/crates/cardwire-daemon/src/interface/context.rs @@ -1,6 +1,6 @@ //! Shared daemon state passed to interface constructors. use crate::{ - core::pci::PciDevice, file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface} + Result, core::pci::PciDevice, file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface} }; use cardwire_ebpf_userspace::EbpfBlocker; use std::{collections::BTreeMap, sync::Arc}; @@ -13,6 +13,6 @@ pub struct DaemonContext { pub gpu_list: Arc>>>, pub config: Arc, pub blocker: Arc>, - pub power_tasks: Arc>>>>, + pub power_tasks: Arc>>>>, pub pci_list: Arc>>, } diff --git a/crates/cardwire-daemon/src/interface/debug.rs b/crates/cardwire-daemon/src/interface/debug.rs index 44ea59b6..b36862c4 100644 --- a/crates/cardwire-daemon/src/interface/debug.rs +++ b/crates/cardwire-daemon/src/interface/debug.rs @@ -3,7 +3,6 @@ use crate::{ env::compute_switcheroo_env, gpu::{GpuEnumerator, GpuVendor}, inode::exp_nvidia_inodes, pci::{self, DbusPciDevice, PciDevice} }, interface::SwitcherooInterface, tasks::watch_power_state }; -use anyhow::Context; use cardwire_ebpf_userspace::{EbpfBlocker, InodeKey}; use log::{error, info, warn}; use std::{ @@ -13,7 +12,7 @@ use tokio::{sync::RwLock, task}; use zbus::{fdo, interface}; use crate::{ - file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, DaemonContext, GpuInterface, ModeInterface, Modes} + Result, file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, DaemonContext, GpuInterface, ModeInterface, Modes} }; #[derive(Clone)] @@ -26,7 +25,7 @@ pub struct DebugInterface { pub blocker: Arc>, pub pci_list: Arc>>, pub object_server: Option, - pub power_tasks: Arc>>>>, + pub power_tasks: Arc>>>>, pub switcheroo: SwitcherooInterface, } impl DebugInterface { @@ -35,7 +34,7 @@ impl DebugInterface { mode_interface: ModeInterface, object_server: Option, switcheroo: SwitcherooInterface, - ) -> anyhow::Result { + ) -> Result { Ok(DebugInterface { mode_state: context.mode_state.clone(), mode_interface, @@ -54,7 +53,7 @@ impl DebugInterface { /// /// Missing inodes only warn, the map keeps what it holds. A failed map /// write is returned: the block would be advertised but not enforced - pub async fn sync_nvidia_inodes(&self) -> anyhow::Result<()> { + pub async fn sync_nvidia_inodes(&self) -> Result<()> { let target = { let gpu_list = self.gpu_list.read().await; gpu_list @@ -84,7 +83,7 @@ impl DebugInterface { Some(gpu_id) => blocker.sync_exp_inodes(inodes, gpu_id), None => blocker.clear_exp_inodes(), } - .context("failed to write the CW_EXP_BLK_INO map") + .map_err(|err| err.into()) } async fn drop_unclaimed_inodes(&self, previous: Vec) { diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 288262ee..aebe9325 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - core::{ + Result, core::{ env::is_gpu_launchable, gpu::{DbusGpuDevice, GpuDevice, is_gpu_active, send_drm_uevent}, inode::{card_to_inode, get_inodes, nvidia_to_inode, render_to_inode, single_pci_to_inode}, pci::PciDevice, procfs }, file::{CardwireGpuState, CardwireModeState}, interface::{Modes, SwitcherooInterface} }; @@ -51,7 +51,7 @@ impl GpuInterface { gpu_state: Arc>, mode_state: Arc>, switcheroo_int: SwitcherooInterface, - ) -> anyhow::Result { + ) -> Result { Ok(Self { id, device: Arc::new(device), diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index 2c8c72ae..d9c7a91b 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -1,8 +1,7 @@ //! Define the mode dbus use crate::{ - core::gpu::{send_drm_uevent, start_nvidia_powerd, stop_nvidia_powerd}, file::{CardwireGpuState, CardwireModeState}, interface::{DaemonContext, GpuInterface, SwitcherooInterface, config::ConfigMemory}, types::SystemType + Result, core::gpu::{send_drm_uevent, start_nvidia_powerd, stop_nvidia_powerd}, file::{CardwireGpuState, CardwireModeState}, interface::{DaemonContext, GpuInterface, SwitcherooInterface, config::ConfigMemory}, types::SystemType }; -use anyhow::Result; use aya::maps::Array as AyaArray; use log::{error, info, warn}; use std::{ @@ -63,7 +62,7 @@ impl ModeInterface { } /// Apply a mode and optionally persist it to the state file - pub async fn internal_set_mode(&self, mode: Modes, save: bool) -> fdo::Result<()> { + pub async fn internal_set_mode(&self, mode: Modes, save: bool) -> Result<()> { let _transition = self.transition.lock().await; self.apply_mode(mode).await?; // Save @@ -209,7 +208,9 @@ impl ModeInterface { #[zbus(property)] pub async fn set_mode(&self, mode: u32) -> fdo::Result<()> { let mode = Modes::try_from(mode).map_err(|err| fdo::Error::InvalidArgs(err.to_string()))?; - self.internal_set_mode(mode, true).await?; + self.internal_set_mode(mode, true) + .await + .map_err(|err| fdo::Error::Failed(err.to_string()))?; Ok(()) } diff --git a/crates/cardwire-daemon/src/manager.rs b/crates/cardwire-daemon/src/manager.rs index 68b01005..7bee3b7f 100644 --- a/crates/cardwire-daemon/src/manager.rs +++ b/crates/cardwire-daemon/src/manager.rs @@ -1,13 +1,12 @@ //! Daemon composition root: builds the shared [`DaemonContext`] and every D-Bus interface, owns //! startup tasks and background-task futures. use crate::{ - analyzer::CardwireAnalyzer, core::{ + Result, analyzer::CardwireAnalyzer, core::{ env::compute_switcheroo_env, gpu::GpuEnumerator, pci::{self} }, file::{CardwireConfig, CardwireDatabase, CardwireGpuState, CardwireModeState}, interface::{ ConfigInterface, ConfigMemory, DaemonContext, DebugInterface, GpuInterface, LoggerInterface, ModeInterface, Modes, SmartPolicyInterface, SwitcherooInterface }, tasks }; -use anyhow::{Context, Result}; use cardwire_ebpf_userspace::{EbpfBlocker, EbpfSettings}; use log::error; use std::{collections::BTreeMap, sync::Arc}; @@ -27,12 +26,10 @@ pub struct DaemonManager { impl DaemonManager { pub async fn new() -> Result { - let mode_state: CardwireModeState = - CardwireModeState::build().context("Error building mode")?; + let mode_state: CardwireModeState = CardwireModeState::build()?; let mode_state: Arc> = Arc::new(RwLock::new(mode_state)); - let user_config: CardwireConfig = - CardwireConfig::build().context("Error building toml config")?; + let user_config: CardwireConfig = CardwireConfig::build()?; let user_config = Arc::new(ConfigMemory::build(user_config)); let gpu_state: CardwireGpuState = CardwireGpuState::build()?; @@ -120,10 +117,7 @@ impl DaemonManager { self.set_nvidia_setting().await?; // Fatal: the setting is already on, so an unwritable map advertises a // block that is never enforced - self.debug_interface - .sync_nvidia_inodes() - .await - .context("failed to prime the experimental nvidia block")?; + self.debug_interface.sync_nvidia_inodes().await?; // Add some programs to the whitelisted comm map self.whitelist_programs().await?; @@ -214,10 +208,7 @@ impl DaemonManager { // On first attempt: don't save (already persisted) // On fallback: persist so the broken mode isn't retried on every boot let save = mode_arg.is_some(); - self.mode_interface - .internal_set_mode(mode, save) - .await - .map_err(anyhow::Error::from) + self.mode_interface.internal_set_mode(mode, save).await } pub fn battery_switch_future(&self) -> impl Future> + 'static { let auto_switch = Arc::clone(&self.inner.config.battery_auto_switch); @@ -256,7 +247,7 @@ impl DaemonManager { res } } - pub fn run_analyzer(&self) -> impl Future> + 'static { + pub fn run_analyzer(&self) -> impl Future> + 'static { let blocker = Arc::clone(&self.inner.blocker); let logger = Arc::clone(&self.logger_interface.report_logs); let signal = Arc::clone(&self.logger_interface.signal_emitter); diff --git a/crates/cardwire-daemon/src/tasks/watch_power_state.rs b/crates/cardwire-daemon/src/tasks/watch_power_state.rs index c1e66be9..a9c7561a 100644 --- a/crates/cardwire-daemon/src/tasks/watch_power_state.rs +++ b/crates/cardwire-daemon/src/tasks/watch_power_state.rs @@ -1,6 +1,6 @@ //! Watch the power state and send a signal when it changes, one task is spawned per gpu use crate::{ - core::gpu::PowerState, interface::{GpuInterface, GpuInterfaceSignals} + Result, core::gpu::PowerState, interface::{GpuInterface, GpuInterfaceSignals} }; use log::{error, info, warn}; use std::{fs, str::FromStr, sync::Arc, time::Duration}; @@ -10,7 +10,7 @@ use zbus::object_server::{self}; pub async fn watch_power_state( gpu: Arc, interface: object_server::InterfaceRef, -) -> anyhow::Result<()> { +) -> Result<()> { let power_path = format!( "/sys/bus/pci/devices/{}/power_state", gpu.device.pci.pci_address() diff --git a/crates/cardwire-daemon/src/types.rs b/crates/cardwire-daemon/src/types.rs index e87a191f..ea5c84ee 100644 --- a/crates/cardwire-daemon/src/types.rs +++ b/crates/cardwire-daemon/src/types.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt, sync::Arc}; -use crate::interface::GpuInterface; +use crate::{Result, core::errors::CardwireError, interface::GpuInterface}; #[derive(Deserialize, Serialize, PartialEq, zbus::zvariant::Type, Clone, Copy, Default, Debug)] #[serde(rename_all = "snake_case")] @@ -26,38 +26,18 @@ impl fmt::Display for Modes { } } -/// Error returned when a u32 does not map to a known GPU mode -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct InvalidModeError { - value: u32, -} - -impl InvalidModeError { - pub fn value(&self) -> u32 { - self.value - } -} - -impl fmt::Display for InvalidModeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "unknown mode: {}", self.value) - } -} - -impl std::error::Error for InvalidModeError {} - /// Try to convert a u32 into a mode. /// /// This is the deserialization side of the D-Bus/eBPF encoding contract. impl TryFrom for Modes { - type Error = InvalidModeError; + type Error = CardwireError; fn try_from(value: u32) -> Result { match value { 0 => Ok(Self::Integrated), 1 => Ok(Self::Hybrid), 2 => Ok(Self::Manual), 3 => Ok(Self::Smart), - _ => Err(InvalidModeError { value }), + _ => Err(CardwireError::UnknownMode(value)), } } } @@ -129,13 +109,6 @@ mod tests { assert_eq!(Modes::try_from(3).unwrap(), Modes::Smart); } - #[test] - fn test_modes_try_from_invalid_value() { - assert!(Modes::try_from(4).is_err()); - assert!(Modes::try_from(u32::MAX).is_err()); - assert_eq!(Modes::try_from(4).unwrap_err().value(), 4); - } - #[test] fn test_modes_into_u32_roundtrip() { for i in 0..=3u32 { From 968e7f81525d297390ea756bb9e61e1d8ca4245d Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 20 Aug 2026 18:33:21 +0200 Subject: [PATCH 2/2] fix(cardwired): preserve dbus error --- crates/cardwire-daemon/src/interface/mode.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index d9c7a91b..104bbefc 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -1,6 +1,8 @@ //! Define the mode dbus use crate::{ - Result, core::gpu::{send_drm_uevent, start_nvidia_powerd, stop_nvidia_powerd}, file::{CardwireGpuState, CardwireModeState}, interface::{DaemonContext, GpuInterface, SwitcherooInterface, config::ConfigMemory}, types::SystemType + Result, core::{ + errors::CardwireError, gpu::{send_drm_uevent, start_nvidia_powerd, stop_nvidia_powerd} + }, file::{CardwireGpuState, CardwireModeState}, interface::{DaemonContext, GpuInterface, SwitcherooInterface, config::ConfigMemory}, types::SystemType }; use aya::maps::Array as AyaArray; use log::{error, info, warn}; @@ -208,10 +210,11 @@ impl ModeInterface { #[zbus(property)] pub async fn set_mode(&self, mode: u32) -> fdo::Result<()> { let mode = Modes::try_from(mode).map_err(|err| fdo::Error::InvalidArgs(err.to_string()))?; - self.internal_set_mode(mode, true) - .await - .map_err(|err| fdo::Error::Failed(err.to_string()))?; - Ok(()) + match self.internal_set_mode(mode, true).await { + Ok(()) => Ok(()), + Err(CardwireError::FdoError(err)) => Err(err), + Err(err) => Err(fdo::Error::Failed(err.to_string())), + } } /// Return the mode currently applied