Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3bb79884 authored by Mårten Kongstad's avatar Mårten Kongstad
Browse files

printflags: include device_config values

Amend the default flag values read from
/<partition>/etc/aconfig_flags.pb with the currently used values as
stored in device_config.

Bug: 301547297
Test: atest printflags.test
Test: adb shell printflags
Change-Id: Ic11702a0ae093d2e9dc3ff543b5ca0684b67e0dc
parent 65aa4c84
Loading
Loading
Loading
Loading
+13 −2
Original line number Diff line number Diff line
@@ -2,8 +2,8 @@ package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

rust_binary {
    name: "printflags",
rust_defaults {
    name: "printflags.defaults",
    edition: "2021",
    clippy_lints: "android",
    lints: "android",
@@ -12,5 +12,16 @@ rust_binary {
        "libaconfig_protos",
        "libanyhow",
        "libprotobuf",
        "libregex",
    ],
}

rust_binary {
    name: "printflags",
    defaults: ["printflags.defaults"],
}

rust_test_host {
    name: "printflags.test",
    defaults: ["printflags.defaults"],
}
+72 −7
Original line number Diff line number Diff line
@@ -16,12 +16,43 @@

//! `printflags` is a device binary to print feature flags.

use aconfig_protos::aconfig::Flag_state as State;
use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags;
use anyhow::Result;
use anyhow::{bail, Result};
use regex::Regex;
use std::collections::HashMap;
use std::fs;
use std::process::Command;
use std::{fs, str};

fn parse_device_config(raw: &str) -> HashMap<String, String> {
    let mut flags = HashMap::new();
    let regex = Regex::new(r"(?m)^([[[:alnum:]]_]+/[[[:alnum:]]_\.]+)=(true|false)$").unwrap();
    for capture in regex.captures_iter(raw) {
        let key = capture.get(1).unwrap().as_str().to_string();
        let value = match capture.get(2).unwrap().as_str() {
            "true" => format!("{:?} (device_config)", State::ENABLED),
            "false" => format!("{:?} (device_config)", State::DISABLED),
            _ => panic!(),
        };
        flags.insert(key, value);
    }
    flags
}

fn main() -> Result<()> {
    // read device_config
    let output = Command::new("/system/bin/device_config").arg("list").output()?;
    if !output.status.success() {
        let reason = match output.status.code() {
            Some(code) => format!("exit code {}", code),
            None => "terminated by signal".to_string(),
        };
        bail!("failed to execute device_config: {}", reason);
    }
    let dc_stdout = str::from_utf8(&output.stdout)?;
    let device_config_flags = parse_device_config(dc_stdout);

    // read aconfig_flags.pb files
    let mut flags: HashMap<String, Vec<String>> = HashMap::new();
    for partition in ["system", "system_ext", "product", "vendor"] {
        let path = format!("/{}/etc/aconfig_flags.pb", partition);
@@ -31,15 +62,49 @@ fn main() -> Result<()> {
        };
        let parsed_flags: ProtoParsedFlags = protobuf::Message::parse_from_bytes(&bytes)?;
        for flag in parsed_flags.parsed_flag {
            let key = format!("{}.{}", flag.package(), flag.name());
            let key = format!("{}/{}.{}", flag.namespace(), flag.package(), flag.name());
            let value = format!("{:?} + {:?} ({})", flag.permission(), flag.state(), partition);
            flags.entry(key).or_default().push(value);
        }
    }
    for (key, value) in flags {
        // TODO: if the flag is READ_WRITE (for any partition), call "device_config get" to obtain
        // the flag's current state, and append value to the output
        println!("{}: {}", key, value.join(", "));

    // print flags
    for (key, mut value) in flags {
        let (_, package_and_name) = key.split_once('/').unwrap();
        if let Some(dc_value) = device_config_flags.get(&key) {
            value.push(dc_value.to_string());
        }
        println!("{}: {}", package_and_name, value.join(", "));
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_foo() {
        let input = r#"
namespace_one/com.foo.bar.flag_one=true
namespace_one/com.foo.bar.flag_two=false
random_noise;
namespace_two/android.flag_one=true
namespace_two/android.flag_two=nonsense
"#;
        let expected = HashMap::from([
            (
                "namespace_one/com.foo.bar.flag_one".to_string(),
                "ENABLED (device_config)".to_string(),
            ),
            (
                "namespace_one/com.foo.bar.flag_two".to_string(),
                "DISABLED (device_config)".to_string(),
            ),
            ("namespace_two/android.flag_one".to_string(), "ENABLED (device_config)".to_string()),
        ]);
        let actual = parse_device_config(input);
        assert_eq!(expected, actual);
    }
}