From 7ece40bb2cb8c06f50e38881fc034d19cc4b0800 Mon Sep 17 00:00:00 2001 From: Ylarod Date: Mon, 3 Nov 2025 09:52:32 +0800 Subject: [PATCH] ksud: add managed_feature --- userspace/ksud/src/cli.rs | 7 +++ userspace/ksud/src/feature.rs | 98 +++++++++++++++++++++++++++++++++-- userspace/ksud/src/module.rs | 87 +++++++++++++++++++++++++------ 3 files changed, 173 insertions(+), 19 deletions(-) diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index eca619a6..64ea5a3e 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -306,6 +306,12 @@ enum Feature { /// List all available features List, + /// Check feature status (supported/unsupported/managed) + Check { + /// Feature ID or name (su_compat, kernel_umount) + id: String, + }, + /// Load configuration from file and apply to kernel Load, @@ -404,6 +410,7 @@ pub fn run() -> Result<()> { Feature::Get { id } => crate::feature::get_feature(id), Feature::Set { id, value } => crate::feature::set_feature(id, value), Feature::List => crate::feature::list_features(), + Feature::Check { id } => crate::feature::check_feature(id), Feature::Load => crate::feature::load_config_and_apply(), Feature::Save => crate::feature::save_config(), }, diff --git a/userspace/ksud/src/feature.rs b/userspace/ksud/src/feature.rs index b8ce7b97..103ffabb 100644 --- a/userspace/ksud/src/feature.rs +++ b/userspace/ksud/src/feature.rs @@ -209,6 +209,20 @@ pub fn list_features() -> Result<()> { println!("Available Features:"); println!("{}", "=".repeat(80)); + // Get managed features from modules + let managed_features_map = crate::module::get_managed_features().unwrap_or_default(); + + // Build a reverse map: feature_name -> Vec + let mut feature_to_modules: HashMap> = HashMap::new(); + for (module_id, feature_list) in managed_features_map.iter() { + for feature_name in feature_list { + feature_to_modules + .entry(feature_name.clone()) + .or_insert_with(Vec::new) + .push(module_id.clone()); + } + } + let all_features = [FeatureId::SuCompat, FeatureId::KernelUmount]; for feature_id in all_features.iter() { @@ -223,8 +237,23 @@ pub fn list_features() -> Result<()> { "DISABLED".to_string() }; - println!("[{}] {} (ID={})", status, feature_id.name(), id); + let managed_by = feature_to_modules.get(feature_id.name()); + let managed_mark = if managed_by.is_some() { + " [MODULE_MANAGED]" + } else { + "" + }; + + println!("[{}] {} (ID={}){}", status, feature_id.name(), id, managed_mark); println!(" {}", feature_id.description()); + + if let Some(modules) = managed_by { + println!( + " ⚠️ Managed by module(s): {} (forced to 0 on initialization)", + modules.join(", ") + ); + } + println!(); } @@ -267,17 +296,80 @@ pub fn save_config() -> Result<()> { Ok(()) } +pub fn check_feature(id: String) -> Result<()> { + let feature_id = parse_feature_id(&id)?; + + // Check if this feature is managed by any module + let managed_features_map = crate::module::get_managed_features().unwrap_or_default(); + let is_managed = managed_features_map + .values() + .any(|features| features.iter().any(|f| f == feature_id.name())); + + if is_managed { + println!("managed"); + return Ok(()); + } + + // Check if the feature is supported by kernel + let (_value, supported) = crate::ksucalls::get_feature(feature_id as u32) + .with_context(|| format!("Failed to get feature {}", id))?; + + if supported { + println!("supported"); + } else { + println!("unsupported"); + } + + Ok(()) +} + pub fn init_features() -> Result<()> { log::info!("Initializing features from config..."); - let features = load_binary_config()?; + let mut features = load_binary_config()?; + + // Get managed features from active modules + if let Ok(managed_features_map) = crate::module::get_managed_features() { + if !managed_features_map.is_empty() { + log::info!("Found {} modules managing features", managed_features_map.len()); + + // Force override managed features to 0 + for (module_id, feature_list) in managed_features_map.iter() { + log::info!("Module '{}' manages {} feature(s)", module_id, feature_list.len()); + + for feature_name in feature_list { + if let Ok(feature_id) = parse_feature_id(feature_name) { + let feature_id_u32 = feature_id as u32; + log::info!( + " - Force overriding managed feature '{}' to 0 (by module: {})", + feature_name, + module_id + ); + features.insert(feature_id_u32, 0); + } else { + log::warn!( + " - Unknown managed feature '{}' from module '{}', ignoring", + feature_name, + module_id + ); + } + } + } + } + } else { + log::warn!("Failed to get managed features from modules, continuing with normal initialization"); + } if features.is_empty() { - log::info!("No feature config found, skipping initialization"); + log::info!("No features to apply, skipping initialization"); return Ok(()); } apply_config(&features)?; + // Save the final configuration (including managed features forced to 0) + save_binary_config(&features)?; + log::info!("Saved final feature configuration to file"); + Ok(()) } diff --git a/userspace/ksud/src/module.rs b/userspace/ksud/src/module.rs index 04de0e76..f7819961 100644 --- a/userspace/ksud/src/module.rs +++ b/userspace/ksud/src/module.rs @@ -434,6 +434,24 @@ fn mark_all_modules(flag_file: &str) -> Result<()> { Ok(()) } +/// Read module.prop from the given module path and return as a HashMap +pub fn read_module_prop(module_path: &Path) -> Result> { + let module_prop = module_path.join("module.prop"); + ensure!(module_prop.exists(), "module.prop not found in {}", module_path.display()); + + let content = std::fs::read(&module_prop) + .with_context(|| format!("Failed to read module.prop: {}", module_prop.display()))?; + + let mut prop_map: HashMap = HashMap::new(); + PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8) + .read_into(|k, v| { + prop_map.insert(k, v); + }) + .with_context(|| format!("Failed to parse module.prop: {}", module_prop.display()))?; + + Ok(prop_map) +} + fn _list_modules(path: &str) -> Vec> { // first check enabled modules let dir = std::fs::read_dir(path); @@ -446,22 +464,19 @@ fn _list_modules(path: &str) -> Vec> { for entry in dir.flatten() { let path = entry.path(); info!("path: {}", path.display()); - let module_prop = path.join("module.prop"); - if !module_prop.exists() { + + if !path.join("module.prop").exists() { continue; } - let content = std::fs::read(&module_prop); - let Ok(content) = content else { - warn!("Failed to read file: {}", module_prop.display()); - continue; + let mut module_prop_map = match read_module_prop(&path) { + Ok(prop) => prop, + Err(e) => { + warn!("Failed to read module.prop for {}: {}", path.display(), e); + continue; + } }; - let mut module_prop_map: HashMap = HashMap::new(); - let encoding = encoding_rs::UTF_8; - let result = - PropertiesIter::new_with_encoding(Cursor::new(content), encoding).read_into(|k, v| { - module_prop_map.insert(k, v); - }); + // If id is missing or empty, use directory name as fallback let dir_id = entry.file_name().to_string_lossy().to_string(); module_prop_map.insert("dir_id".to_owned(), dir_id.clone()); @@ -483,10 +498,6 @@ fn _list_modules(path: &str) -> Vec> { module_prop_map.insert("web".to_owned(), web.to_string()); module_prop_map.insert("action".to_owned(), action.to_string()); - if result.is_err() { - warn!("Failed to parse module.prop: {}", module_prop.display()); - continue; - } modules.push(module_prop_map); } @@ -498,3 +509,47 @@ pub fn list_modules() -> Result<()> { println!("{}", serde_json::to_string_pretty(&modules)?); Ok(()) } + +/// Get all managed features from active modules +/// Modules can specify ksu_managed_features in their module.prop +/// Format: ksu_managed_features=feature1,feature2,feature3 +/// Returns: HashMap> +pub fn get_managed_features() -> Result>> { + let mut managed_features_map: HashMap> = HashMap::new(); + + foreach_active_module(|module_path| { + let prop_map = match read_module_prop(module_path) { + Ok(prop) => prop, + Err(e) => { + warn!("Failed to read module.prop for {}: {}", module_path.display(), e); + return Ok(()); + } + }; + + if let Some(features_str) = prop_map.get("ksu_managed_features") { + let module_id = prop_map + .get("id") + .map(|s| s.to_string()) + .unwrap_or_else(|| "unknown".to_string()); + + info!("Module {} manages features: {}", module_id, features_str); + + let mut feature_list = Vec::new(); + for feature in features_str.split(',') { + let feature = feature.trim(); + if !feature.is_empty() { + info!(" - Adding managed feature: {}", feature); + feature_list.push(feature.to_string()); + } + } + + if !feature_list.is_empty() { + managed_features_map.insert(module_id, feature_list); + } + } + + Ok(()) + })?; + + Ok(managed_features_map) +} \ No newline at end of file