ksud: add managed_feature
This commit is contained in:
@@ -306,6 +306,12 @@ enum Feature {
|
|||||||
/// List all available features
|
/// List all available features
|
||||||
List,
|
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 configuration from file and apply to kernel
|
||||||
Load,
|
Load,
|
||||||
|
|
||||||
@@ -404,6 +410,7 @@ pub fn run() -> Result<()> {
|
|||||||
Feature::Get { id } => crate::feature::get_feature(id),
|
Feature::Get { id } => crate::feature::get_feature(id),
|
||||||
Feature::Set { id, value } => crate::feature::set_feature(id, value),
|
Feature::Set { id, value } => crate::feature::set_feature(id, value),
|
||||||
Feature::List => crate::feature::list_features(),
|
Feature::List => crate::feature::list_features(),
|
||||||
|
Feature::Check { id } => crate::feature::check_feature(id),
|
||||||
Feature::Load => crate::feature::load_config_and_apply(),
|
Feature::Load => crate::feature::load_config_and_apply(),
|
||||||
Feature::Save => crate::feature::save_config(),
|
Feature::Save => crate::feature::save_config(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -209,6 +209,20 @@ pub fn list_features() -> Result<()> {
|
|||||||
println!("Available Features:");
|
println!("Available Features:");
|
||||||
println!("{}", "=".repeat(80));
|
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<module_id>
|
||||||
|
let mut feature_to_modules: HashMap<String, Vec<String>> = 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];
|
let all_features = [FeatureId::SuCompat, FeatureId::KernelUmount];
|
||||||
|
|
||||||
for feature_id in all_features.iter() {
|
for feature_id in all_features.iter() {
|
||||||
@@ -223,8 +237,23 @@ pub fn list_features() -> Result<()> {
|
|||||||
"DISABLED".to_string()
|
"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());
|
println!(" {}", feature_id.description());
|
||||||
|
|
||||||
|
if let Some(modules) = managed_by {
|
||||||
|
println!(
|
||||||
|
" ⚠️ Managed by module(s): {} (forced to 0 on initialization)",
|
||||||
|
modules.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,17 +296,80 @@ pub fn save_config() -> Result<()> {
|
|||||||
Ok(())
|
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<()> {
|
pub fn init_features() -> Result<()> {
|
||||||
log::info!("Initializing features from config...");
|
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() {
|
if features.is_empty() {
|
||||||
log::info!("No feature config found, skipping initialization");
|
log::info!("No features to apply, skipping initialization");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_config(&features)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,6 +434,24 @@ fn mark_all_modules(flag_file: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read module.prop from the given module path and return as a HashMap
|
||||||
|
pub fn read_module_prop(module_path: &Path) -> Result<HashMap<String, String>> {
|
||||||
|
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<String, String> = 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<HashMap<String, String>> {
|
fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
||||||
// first check enabled modules
|
// first check enabled modules
|
||||||
let dir = std::fs::read_dir(path);
|
let dir = std::fs::read_dir(path);
|
||||||
@@ -446,22 +464,19 @@ fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
|||||||
for entry in dir.flatten() {
|
for entry in dir.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
info!("path: {}", path.display());
|
info!("path: {}", path.display());
|
||||||
let module_prop = path.join("module.prop");
|
|
||||||
if !module_prop.exists() {
|
if !path.join("module.prop").exists() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let content = std::fs::read(&module_prop);
|
let mut module_prop_map = match read_module_prop(&path) {
|
||||||
let Ok(content) = content else {
|
Ok(prop) => prop,
|
||||||
warn!("Failed to read file: {}", module_prop.display());
|
Err(e) => {
|
||||||
|
warn!("Failed to read module.prop for {}: {}", path.display(), e);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut module_prop_map: HashMap<String, String> = 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();
|
let dir_id = entry.file_name().to_string_lossy().to_string();
|
||||||
module_prop_map.insert("dir_id".to_owned(), dir_id.clone());
|
module_prop_map.insert("dir_id".to_owned(), dir_id.clone());
|
||||||
|
|
||||||
@@ -483,10 +498,6 @@ fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
|||||||
module_prop_map.insert("web".to_owned(), web.to_string());
|
module_prop_map.insert("web".to_owned(), web.to_string());
|
||||||
module_prop_map.insert("action".to_owned(), action.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);
|
modules.push(module_prop_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,3 +509,47 @@ pub fn list_modules() -> Result<()> {
|
|||||||
println!("{}", serde_json::to_string_pretty(&modules)?);
|
println!("{}", serde_json::to_string_pretty(&modules)?);
|
||||||
Ok(())
|
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<ModuleId, Vec<ManagedFeature>>
|
||||||
|
pub fn get_managed_features() -> Result<HashMap<String, Vec<String>>> {
|
||||||
|
let mut managed_features_map: HashMap<String, Vec<String>> = 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user