blob: 02a89bb5c94bf2e18824c0fd2b5475a797024ae2 [file] [log] [blame]
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::{Flag, FlagSource, FlagValue};
use anyhow::{anyhow, bail, Result};
use regex::Regex;
use std::collections::HashMap;
use std::process::Command;
use std::str;
pub struct DeviceConfigSource {}
#[allow(dead_code)]
fn parse_device_config_output(raw: &str) -> Result<HashMap<String, FlagValue>> {
let mut flags = HashMap::new();
let regex = Regex::new(r"(?m)^([[[:alnum:]]:_/\.]+)=(true|false)$")?;
for capture in regex.captures_iter(raw) {
let key =
capture.get(1).ok_or(anyhow!("invalid device_config output"))?.as_str().to_string();
let value = FlagValue::try_from(
capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str(),
)?;
flags.insert(key, value);
}
Ok(flags)
}
fn execute_device_config_command(command: &[&str]) -> Result<String> {
let output = Command::new("/system/bin/device_config").args(command).output()?;
if !output.status.success() {
let reason = match output.status.code() {
Some(code) => {
let output = str::from_utf8(&output.stdout)?;
if !output.is_empty() {
format!("exit code {code}, output was {output}")
} else {
format!("exit code {code}")
}
}
None => "terminated by signal".to_string(),
};
bail!("failed to access flag storage: {}", reason);
}
Ok(str::from_utf8(&output.stdout)?.to_string())
}
impl FlagSource for DeviceConfigSource {
fn list_flags() -> Result<Vec<Flag>> {
Err(anyhow!("new storage should be source of truth"))
}
fn override_flag(
namespace: &str,
qualified_name: &str,
value: &str,
_immediate: bool,
) -> Result<()> {
// device config override command always immediate change the boot value
execute_device_config_command(&["override", namespace, qualified_name, value]).map(|_| ())
}
fn unset_flag(namespace: &str, qualified_name: &str, _immediate: bool) -> Result<()> {
// device config clear_override command always clear the boot value as well
execute_device_config_command(&["clear_override", namespace, qualified_name]).map(|_| ())
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::Rng;
#[test]
fn test_parse_device_config_output() {
let input = r#"
namespace_one/com.foo.bar.flag_one=true
com.foo.bar.flag_two=false
random_noise;
namespace_two:android.flag_one=true
android.flag_two=nonsense
"#;
let expected = HashMap::from([
("namespace_one/com.foo.bar.flag_one".to_string(), FlagValue::Enabled),
("com.foo.bar.flag_two".to_string(), FlagValue::Disabled),
("namespace_two:android.flag_one".to_string(), FlagValue::Enabled),
]);
let actual = parse_device_config_output(input).unwrap();
assert_eq!(expected, actual);
}
#[test]
#[cfg(not(feature = "cargo"))]
fn test_override_flag() {
let mut rng = rand::thread_rng();
let namespace = rng.gen::<u32>().to_string();
DeviceConfigSource::override_flag(
&namespace,
"aflags_test_package.aflags_test_flag",
"false",
false,
)
.unwrap();
let result = execute_device_config_command(&["list", &namespace]).unwrap();
let flags = parse_device_config_output(&result).unwrap();
let flag_value = flags.get("aflags_test_package.aflags_test_flag").unwrap();
assert_eq!(*flag_value, FlagValue::Disabled);
let result = execute_device_config_command(&["list", "device_config_overrides"]).unwrap();
let flags = parse_device_config_output(&result).unwrap();
println!("{:?}", flags);
let flag_value =
flags.get(&format!("{namespace}:aflags_test_package.aflags_test_flag")).unwrap();
assert_eq!(*flag_value, FlagValue::Disabled);
}
#[test]
#[cfg(not(feature = "cargo"))]
fn test_unset_flag() {
let mut rng = rand::thread_rng();
let namespace = rng.gen::<u32>().to_string();
DeviceConfigSource::override_flag(
&namespace,
"aflags_test_package.aflags_test_flag",
"false",
false,
)
.unwrap();
DeviceConfigSource::unset_flag(&namespace, "aflags_test_package.aflags_test_flag", false)
.unwrap();
let result = execute_device_config_command(&["list", &namespace]).unwrap();
let flags = parse_device_config_output(&result).unwrap();
assert!(!flags.contains_key("aflags_test_package.aflags_test_flag"));
let result = execute_device_config_command(&["list", "device_config_overrides"]).unwrap();
let flags = parse_device_config_output(&result).unwrap();
assert!(!flags.contains_key(&format!("{namespace}:aflags_test_package.aflags_test_flag")));
}
}