Revert "Remove device-side printflags source"
This reverts commit f6453aac47f5a0219c027f1e223126177378a208.
Reason for revert: submitted before other tool was migrated away
Change-Id: I55a3735a423df50c3a55773ba988b77af134c5db
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 3ea7020..90d45a8 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -253,6 +253,7 @@
preinstalled-packages-asl-files.xml \
preinstalled-packages-platform.xml \
preinstalled-packages-strict-signature.xml \
+ printflags \
privapp-permissions-platform.xml \
prng_seeder \
recovery-persist \
diff --git a/target/product/generic/Android.bp b/target/product/generic/Android.bp
index 02e3396..978d3b1 100644
--- a/target/product/generic/Android.bp
+++ b/target/product/generic/Android.bp
@@ -594,6 +594,7 @@
"preinstalled-packages-platform.xml", // base_system
"preinstalled-packages-strict-signature.xml", // base_system
"preloaded-classes", // ok
+ "printflags", // base_system
"privapp-permissions-platform.xml", // base_system
"prng_seeder", // base_system
"public.libraries.android.txt",
diff --git a/tools/aconfig/printflags/Android.bp b/tools/aconfig/printflags/Android.bp
new file mode 100644
index 0000000..6f7bca3
--- /dev/null
+++ b/tools/aconfig/printflags/Android.bp
@@ -0,0 +1,31 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "printflags.defaults",
+ edition: "2021",
+ clippy_lints: "android",
+ lints: "android",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libaconfig_protos",
+ "libanyhow",
+ "libprotobuf",
+ "libregex",
+ ],
+}
+
+rust_binary {
+ name: "printflags",
+ defaults: ["printflags.defaults"],
+ apex_available: [
+ "//apex_available:platform",
+ ],
+}
+
+rust_test_host {
+ name: "printflags.test",
+ defaults: ["printflags.defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/printflags/Cargo.toml b/tools/aconfig/printflags/Cargo.toml
new file mode 100644
index 0000000..7313f5d
--- /dev/null
+++ b/tools/aconfig/printflags/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "printflags"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+paste = "1.0.11"
+protobuf = "3.2.0"
+regex = "1.10.3"
+aconfig_protos = { path = "../aconfig_protos" }
diff --git a/tools/aconfig/printflags/src/main.rs b/tools/aconfig/printflags/src/main.rs
new file mode 100644
index 0000000..7838b51
--- /dev/null
+++ b/tools/aconfig/printflags/src/main.rs
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! `printflags` is a device binary to print feature flags.
+
+use aconfig_protos::ProtoFlagState as State;
+use aconfig_protos::ProtoParsedFlags;
+use anyhow::{bail, Context, Result};
+use regex::Regex;
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+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 xxd(bytes: &[u8]) -> String {
+ let n = 8.min(bytes.len());
+ let mut v = Vec::with_capacity(n);
+ for byte in bytes.iter().take(n) {
+ v.push(format!("{:02x}", byte));
+ }
+ let trailer = match bytes.len() {
+ 0..=8 => "",
+ _ => " ..",
+ };
+ format!("[{}{}]", v.join(" "), trailer)
+}
+
+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 apex_pattern = Regex::new(r"^/apex/[^@]+\.[^@]+$").unwrap();
+ let mut mount_points = vec![
+ "system".to_string(),
+ "system_ext".to_string(),
+ "product".to_string(),
+ "vendor".to_string(),
+ ];
+ for apex in fs::read_dir("/apex")? {
+ let path_name = apex?.path().display().to_string();
+ if let Some(canonical_path) = apex_pattern.captures(&path_name) {
+ mount_points.push(canonical_path.get(0).unwrap().as_str().to_owned());
+ }
+ }
+
+ let mut flags: BTreeMap<String, Vec<String>> = BTreeMap::new();
+ for mount_point in mount_points {
+ let path = format!("/{}/etc/aconfig_flags.pb", mount_point);
+ let Ok(bytes) = fs::read(&path) else {
+ eprintln!("warning: failed to read {}", path);
+ continue;
+ };
+ let parsed_flags: ProtoParsedFlags = protobuf::Message::parse_from_bytes(&bytes)
+ .with_context(|| {
+ format!("failed to parse {} ({}, {} byte(s))", path, xxd(&bytes), bytes.len())
+ })?;
+ for flag in parsed_flags.parsed_flag {
+ let key = format!("{}/{}.{}", flag.namespace(), flag.package(), flag.name());
+ let value = format!("{:?} + {:?} ({})", flag.permission(), flag.state(), mount_point);
+ flags.entry(key).or_default().push(value);
+ }
+ }
+
+ // print flags
+ for (key, mut value) in flags {
+ if let Some(dc_value) = device_config_flags.get(&key) {
+ value.push(dc_value.to_string());
+ }
+ println!("{}: {}", key, value.join(", "));
+ }
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_device_config() {
+ 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);
+ }
+
+ #[test]
+ fn test_xxd() {
+ let input = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9];
+ assert_eq!("[]", &xxd(&input[0..0]));
+ assert_eq!("[00]", &xxd(&input[0..1]));
+ assert_eq!("[00 01]", &xxd(&input[0..2]));
+ assert_eq!("[00 01 02 03 04 05 06]", &xxd(&input[0..7]));
+ assert_eq!("[00 01 02 03 04 05 06 07]", &xxd(&input[0..8]));
+ assert_eq!("[00 01 02 03 04 05 06 07 ..]", &xxd(&input[0..9]));
+ assert_eq!("[00 01 02 03 04 05 06 07 ..]", &xxd(&input));
+ }
+}