From c94608a2eb6b3737b3586dafc1bfb545ff7da23a Mon Sep 17 00:00:00 2001 From: Ylarod Date: Sat, 22 Nov 2025 17:12:40 +0800 Subject: [PATCH] ksud: config set support read from stdin, and less restriction --- userspace/ksud/src/cli.rs | 32 +++++++++++++++++----- userspace/ksud/src/module_config.rs | 41 ++++++++++------------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index e0877ffc..f06548d3 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -1,4 +1,4 @@ -use anyhow::{Ok, Result}; +use anyhow::{Context, Ok, Result}; use clap::Parser; use std::path::PathBuf; @@ -322,8 +322,11 @@ enum ModuleConfigCmd { Set { /// config key key: String, - /// config value - value: String, + /// config value (omit to read from stdin) + value: Option, + /// read value from stdin (default if value not provided) + #[arg(long)] + stdin: bool, /// use temporary config (cleared on reboot) #[arg(short, long)] temp: bool, @@ -576,17 +579,32 @@ pub fn run() -> Result<()> { None => anyhow::bail!("Key '{key}' not found"), } } - ModuleConfigCmd::Set { key, value, temp } => { - // Validate input at CLI layer for better user experience + ModuleConfigCmd::Set { key, value, stdin, temp } => { + // Validate key at CLI layer for better user experience module_config::validate_config_key(&key)?; - module_config::validate_config_value(&value)?; + + // Read value from stdin or argument + let value_str = match value { + Some(v) if !stdin => v, + _ => { + // Read from stdin + use std::io::Read; + let mut buffer = String::new(); + std::io::stdin().read_to_string(&mut buffer) + .context("Failed to read from stdin")?; + buffer + } + }; + + // Validate value + module_config::validate_config_value(&value_str)?; let config_type = if temp { module_config::ConfigType::Temp } else { module_config::ConfigType::Persist }; - module_config::set_config_value(&module_id, &key, &value, config_type) + module_config::set_config_value(&module_id, &key, &value_str, config_type) } ModuleConfigCmd::List => { let config = module_config::merge_configs(&module_id)?; diff --git a/userspace/ksud/src/module_config.rs b/userspace/ksud/src/module_config.rs index 7c50b7e7..bcac87e1 100644 --- a/userspace/ksud/src/module_config.rs +++ b/userspace/ksud/src/module_config.rs @@ -40,7 +40,10 @@ pub fn parse_bool_config(value: &str) -> bool { } /// Validate config key -/// Rejects keys with control characters, newlines, or excessive length +/// Uses the same validation rules as module_id: ^[a-zA-Z][a-zA-Z0-9._-]+$ +/// - Must start with a letter (a-zA-Z) +/// - Followed by one or more alphanumeric, dot, underscore, or hyphen characters +/// - Minimum length: 2 characters pub fn validate_config_key(key: &str) -> Result<()> { if key.is_empty() { bail!("Config key cannot be empty"); @@ -54,27 +57,21 @@ pub fn validate_config_key(key: &str) -> Result<()> { ); } - // Check for control characters and newlines - for ch in key.chars() { - if ch.is_control() { - bail!( - "Config key contains control character: {:?} (U+{:04X})", - ch, - ch as u32 - ); - } - } - - // Reject keys with path separators to prevent path traversal - if key.contains('/') || key.contains('\\') { - bail!("Config key cannot contain path separators"); + // Use same pattern as module_id for consistency + let re = regex_lite::Regex::new(r"^[a-zA-Z][a-zA-Z0-9._-]+$")?; + if !re.is_match(key) { + bail!( + "Invalid config key: '{}'. Must match /^[a-zA-Z][a-zA-Z0-9._-]+$/", + key + ); } Ok(()) } /// Validate config value -/// Rejects values with control characters (except tab), newlines, or excessive length +/// Only enforces maximum length - no character restrictions +/// Values are stored in binary format with length prefix, so any UTF-8 data is safe pub fn validate_config_value(value: &str) -> Result<()> { if value.len() > MAX_CONFIG_VALUE_LEN { bail!( @@ -84,17 +81,7 @@ pub fn validate_config_value(value: &str) -> Result<()> { ); } - // Check for control characters (allow tab but reject newlines and others) - for ch in value.chars() { - if ch.is_control() && ch != '\t' { - bail!( - "Config value contains invalid control character: {:?} (U+{:04X})", - ch, - ch as u32 - ); - } - } - + // No character restrictions - binary storage format handles all UTF-8 safely Ok(()) }