From 34dff79a918d49e69d340961890d8488665d5b57 Mon Sep 17 00:00:00 2001 From: Leonardo Forchini Date: Fri, 25 Sep 2026 09:11:23 +0000 Subject: [PATCH] Refactor config loading mechanism Fixed a bug where the `util::Path` prefix was appended twice in some cases, and also consolidated config loading into a single function. It attempts to read the config file and falls back the a default implementation if not found. Signed-off-by: Leonardo Forchini --- src/backends/qemu/mod.rs | 10 +++------- src/config.rs | 18 +++++++++++++++++- src/engines/threshold.rs | 11 +++-------- src/main.rs | 18 +++--------------- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/backends/qemu/mod.rs b/src/backends/qemu/mod.rs index c6892c2..6d9f73e 100644 --- a/src/backends/qemu/mod.rs +++ b/src/backends/qemu/mod.rs @@ -31,7 +31,7 @@ use serde::{Deserialize, Serialize}; use crate::{ backends::{Backend, BackendClientError, BackendRegistration}, - config::{ConfigError, load_config}, + config::{self, ConfigError}, instance::{Instance, InstanceClient}, util::Path, }; @@ -82,12 +82,8 @@ impl QemuConfig { /// Read `/qemu.json` if present, otherwise return the /// built-in defaults. pub fn from_dir(dir: &Path) -> Result { - let path = Path::new(&dir.join("qemu.json")); - if path.exists() { - load_config(path) - } else { - Ok(Self::default()) - } + let path = dir.join("qemu.json"); + config::load_config_or_default(&path) } } diff --git a/src/config.rs b/src/config.rs index 7d9a558..38f9da5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -136,8 +136,24 @@ impl Default for Config { } } +/// Attempt to load a JSON config file. If not found, fall back on the +/// Default implementation. +pub fn load_config_or_default Deserialize<'de> + Default>( + path: impl AsRef, +) -> Result { + match load_config(path) { + r @ Ok(_) => r, + Err(ConfigError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => { + Ok(Default::default()) + } + e @ Err(_) => e, + } +} + /// Load a JSON config file. -pub fn load_config Deserialize<'de>>(path: impl AsRef) -> Result { +pub fn load_config Deserialize<'de>>( + path: impl AsRef, +) -> Result { let path = path.as_ref(); let data = std::fs::read_to_string(path)?; let val: T = serde_json::from_str(&data)?; diff --git a/src/engines/threshold.rs b/src/engines/threshold.rs index d7afe06..ff3cadb 100644 --- a/src/engines/threshold.rs +++ b/src/engines/threshold.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; use crate::{ - config::{ConfigError, deserialize_percent, serialize_percent}, + config::{self, ConfigError, deserialize_percent, serialize_percent}, engines::{ AppliedOutcome, EngineError, EngineRegistration, EngineTickContext, ScaleAction, ScalingEngine, @@ -211,13 +211,8 @@ impl ThresholdEngine { /// Load `threshold.json`, falling back to built-in defaults when absent. pub fn from_config_dir(dir: &Path) -> Result { - let path = Path::new(&dir.join(format!("{ENGINE_NAME}.json"))); - // TODO TOCTOU, blindly load and return default if ENOENT - let cfg: ThresholdConfig = if path.exists() { - crate::config::load_config(path)? - } else { - ThresholdConfig::default() - }; + let path = dir.join(format!("{ENGINE_NAME}.json")); + let cfg: ThresholdConfig = config::load_config_or_default(&path)?; cfg.validate()?; Ok(Self::new(cfg)) } diff --git a/src/main.rs b/src/main.rs index 4d6f959..1865ecb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,10 @@ //! `io-thread-controller` daemon entry point. -use std::io::ErrorKind; - use clap::{self, CommandFactory, FromArgMatches, Parser}; use io_thread_controller::{ - backends::BackendClientError, - backends::registered_backends, - config::{Config, ConfigError, dump_default_config, load_config, validate_config}, + backends::{BackendClientError, registered_backends}, + config::{Config, ConfigError, dump_default_config, load_config_or_default, validate_config}, daemon::{DaemonError, VERSION, run}, util::Path, }; @@ -122,7 +119,7 @@ async fn main() -> Result<(), IoThreadControllerError> { return Err(IoThreadControllerError::NoSuchBackend(name.to_string())); } - let mut cfg = load_daemon_config(&cli.config)?; + let mut cfg: Config = load_config_or_default(&cli.config)?; if cli.print_status_header { cfg.print_status_header = true; } @@ -135,15 +132,6 @@ async fn main() -> Result<(), IoThreadControllerError> { Ok(()) } -/// Load defaults only for an absent file; propagate every other open failure. -fn load_daemon_config(path: &Path) -> Result { - match std::fs::File::open(path) { - Ok(_) => load_config(path), - Err(error) if error.kind() == ErrorKind::NotFound => Ok(Config::default()), - Err(error) => Err(error)?, - } -} - /// Install the process-wide tracing subscriber. fn init_logging(filter: &str, style: LogStyle) { // Auto-detection: systemd sets `JOURNAL_STREAM=:`