Merge "Look for release_config_map.mk in more projects" into main
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index c089d54..2a606bf 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -6,7 +6,6 @@
     name: "aconfig_storage_file.defaults",
     edition: "2021",
     lints: "none",
-    srcs: ["src/lib.rs"],
     rustlibs: [
         "libanyhow",
         "libthiserror",
@@ -22,12 +21,21 @@
     crate_name: "aconfig_storage_file",
     host_supported: true,
     defaults: ["aconfig_storage_file.defaults"],
+    srcs: ["src/lib.rs"],
+}
+
+rust_binary_host {
+    name: "aconfig-storage",
+    defaults: ["aconfig_storage_file.defaults"],
+    srcs: ["src/main.rs"],
+    rustlibs: ["libaconfig_storage_file"],
 }
 
 rust_test_host {
     name: "aconfig_storage_file.test",
     test_suites: ["general-tests"],
     defaults: ["aconfig_storage_file.defaults"],
+    srcs: ["src/lib.rs"],
 }
 
 rust_protobuf {
diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml
index 9b9a615..641f481 100644
--- a/tools/aconfig/aconfig_storage_file/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_file/Cargo.toml
@@ -14,6 +14,10 @@
 thiserror = "1.0.56"
 clap = { version = "4.1.8", features = ["derive"] }
 
+[[bin]]
+name = "aconfig-storage"
+path = "src/main.rs"
+
 [build-dependencies]
 protobuf-codegen = "3.2.0"
 cxx-build = "1.0"
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index ec41a4e..202f6a4 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -78,7 +78,9 @@
             "package_map" => Ok(Self::PackageMap),
             "flag_map" => Ok(Self::FlagMap),
             "flag_val" => Ok(Self::FlagVal),
-            _ => Err(anyhow!("Invalid storage file to create")),
+            _ => Err(anyhow!(
+                "Invalid storage file type, valid types are package_map|flag_map|flag_val]"
+            )),
         }
     }
 }
diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs
index 2c7b87c..b5e6158 100644
--- a/tools/aconfig/aconfig_storage_file/src/main.rs
+++ b/tools/aconfig/aconfig_storage_file/src/main.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-//! `aconfig_storage` is a debugging tool to parse storage files
+//! `aconfig-storage` is a debugging tool to parse storage files
 
 use aconfig_storage_file::{
     list_flags, read_file_to_bytes, AconfigStorageError, FlagTable, FlagValueList, PackageTable,
@@ -24,7 +24,7 @@
 use clap::{builder::ArgAction, Arg, Command};
 
 fn cli() -> Command {
-    Command::new("aconfig_storage_file")
+    Command::new("aconfig-storage")
         .subcommand_required(true)
         .subcommand(
             Command::new("print")
@@ -39,13 +39,13 @@
         .subcommand(
             Command::new("list")
                 .arg(
-                    Arg::new("package_map")
-                        .long("package_map")
+                    Arg::new("package-map")
+                        .long("package-map")
                         .required(true)
                         .action(ArgAction::Set),
                 )
-                .arg(Arg::new("flag_map").long("flag_map").required(true).action(ArgAction::Set))
-                .arg(Arg::new("flag_val").long("flag_val").required(true).action(ArgAction::Set)),
+                .arg(Arg::new("flag-map").long("flag-map").required(true).action(ArgAction::Set))
+                .arg(Arg::new("flag-val").long("flag-val").required(true).action(ArgAction::Set)),
         )
 }
 
@@ -80,9 +80,9 @@
             print_storage_file(file_path, file_type)?
         }
         Some(("list", sub_matches)) => {
-            let package_map = sub_matches.get_one::<String>("package_map").unwrap();
-            let flag_map = sub_matches.get_one::<String>("flag_map").unwrap();
-            let flag_val = sub_matches.get_one::<String>("flag_val").unwrap();
+            let package_map = sub_matches.get_one::<String>("package-map").unwrap();
+            let flag_map = sub_matches.get_one::<String>("flag-map").unwrap();
+            let flag_val = sub_matches.get_one::<String>("flag-val").unwrap();
             let flags = list_flags(package_map, flag_map, flag_val)?;
             for flag in flags.iter() {
                 println!("{}: {}", flag.0, flag.1);
diff --git a/tools/aconfig/aflags/Android.bp b/tools/aconfig/aflags/Android.bp
index c65da97..b36aa34 100644
--- a/tools/aconfig/aflags/Android.bp
+++ b/tools/aconfig/aflags/Android.bp
@@ -12,6 +12,7 @@
         "libaconfig_protos",
         "libanyhow",
         "libclap",
+        "libnix",
         "libprotobuf",
         "libregex",
     ],
diff --git a/tools/aconfig/aflags/Cargo.toml b/tools/aconfig/aflags/Cargo.toml
index 3350a6cd..6a08da6 100644
--- a/tools/aconfig/aflags/Cargo.toml
+++ b/tools/aconfig/aflags/Cargo.toml
@@ -10,3 +10,4 @@
 protobuf = "3.2.0"
 regex = "1.10.3"
 aconfig_protos = { path = "../aconfig_protos" }
+nix = { version = "0.28.0", features = ["user"] }
diff --git a/tools/aconfig/aflags/src/device_config_source.rs b/tools/aconfig/aflags/src/device_config_source.rs
index 1cea6ed..2589f3d 100644
--- a/tools/aconfig/aflags/src/device_config_source.rs
+++ b/tools/aconfig/aflags/src/device_config_source.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use crate::{Flag, FlagPermission, FlagSource, ValuePickedFrom};
+use crate::{Flag, FlagPermission, FlagSource, FlagValue, ValuePickedFrom};
 use aconfig_protos::ProtoFlagPermission as ProtoPermission;
 use aconfig_protos::ProtoFlagState as ProtoState;
 use aconfig_protos::ProtoParsedFlag;
@@ -40,10 +40,9 @@
     };
 
     let value = match flag.state() {
-        ProtoState::ENABLED => "true",
-        ProtoState::DISABLED => "false",
-    }
-    .to_string();
+        ProtoState::ENABLED => FlagValue::Enabled,
+        ProtoState::DISABLED => FlagValue::Disabled,
+    };
 
     let permission = match flag.permission() {
         ProtoPermission::READ_ONLY => FlagPermission::ReadOnly,
@@ -86,14 +85,16 @@
     Ok(flags.values().cloned().collect())
 }
 
-fn parse_device_config(raw: &str) -> Result<HashMap<String, String>> {
+fn parse_device_config(raw: &str) -> Result<HashMap<String, FlagValue>> {
     let mut flags = HashMap::new();
     let regex = Regex::new(r"(?m)^([[[:alnum:]]_]+/[[[: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 = capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str();
-        flags.insert(key, value.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)
 }
@@ -112,24 +113,24 @@
     Ok(str::from_utf8(&output.stdout)?.to_string())
 }
 
-fn read_device_config_flags() -> Result<HashMap<String, String>> {
+fn read_device_config_flags() -> Result<HashMap<String, FlagValue>> {
     let list_output = read_device_config_output(&["list"])?;
     parse_device_config(&list_output)
 }
 
-fn reconcile(pb_flags: &[Flag], dc_flags: HashMap<String, String>) -> Vec<Flag> {
+fn reconcile(pb_flags: &[Flag], dc_flags: HashMap<String, FlagValue>) -> Vec<Flag> {
     pb_flags
         .iter()
         .map(|f| {
             dc_flags
                 .get(&format!("{}/{}.{}", f.namespace, f.package, f.name))
                 .map(|value| {
-                    if value.eq(&f.value) {
+                    if *value == f.value {
                         Flag { value_picked_from: ValuePickedFrom::Default, ..f.clone() }
                     } else {
                         Flag {
                             value_picked_from: ValuePickedFrom::Server,
-                            value: value.to_string(),
+                            value: *value,
                             ..f.clone()
                         }
                     }
@@ -167,9 +168,9 @@
 namespace_two/android.flag_two=nonsense
 "#;
         let expected = HashMap::from([
-            ("namespace_one/com.foo.bar.flag_one".to_string(), "true".to_string()),
-            ("namespace_one/com.foo.bar.flag_two".to_string(), "false".to_string()),
-            ("namespace_two/android.flag_one".to_string(), "true".to_string()),
+            ("namespace_one/com.foo.bar.flag_one".to_string(), FlagValue::Enabled),
+            ("namespace_one/com.foo.bar.flag_two".to_string(), FlagValue::Disabled),
+            ("namespace_two/android.flag_one".to_string(), FlagValue::Enabled),
         ]);
         let actual = parse_device_config(input).unwrap();
         assert_eq!(expected, actual);
diff --git a/tools/aconfig/aflags/src/main.rs b/tools/aconfig/aflags/src/main.rs
index 7ca70a2..febd567 100644
--- a/tools/aconfig/aflags/src/main.rs
+++ b/tools/aconfig/aflags/src/main.rs
@@ -16,13 +16,13 @@
 
 //! `aflags` is a device binary to read and write aconfig flags.
 
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, ensure, Result};
 use clap::Parser;
 
 mod device_config_source;
 use device_config_source::DeviceConfigSource;
 
-#[derive(Clone)]
+#[derive(Clone, PartialEq)]
 enum FlagPermission {
     ReadOnly,
     ReadWrite,
@@ -52,13 +52,40 @@
     }
 }
 
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum FlagValue {
+    Enabled,
+    Disabled,
+}
+
+impl TryFrom<&str> for FlagValue {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+        match value {
+            "true" | "enabled" => Ok(Self::Enabled),
+            "false" | "disabled" => Ok(Self::Disabled),
+            _ => Err(anyhow!("cannot convert string '{}' to FlagValue", value)),
+        }
+    }
+}
+
+impl ToString for FlagValue {
+    fn to_string(&self) -> String {
+        match &self {
+            Self::Enabled => "enabled".into(),
+            Self::Disabled => "disabled".into(),
+        }
+    }
+}
+
 #[derive(Clone)]
 struct Flag {
     namespace: String,
     name: String,
     package: String,
     container: String,
-    value: String,
+    value: FlagValue,
     permission: FlagPermission,
     value_picked_from: ValuePickedFrom,
 }
@@ -126,7 +153,7 @@
     let full_name = flag.qualified_name();
     let p0 = info.longest_flag_col + 1;
 
-    let val = &flag.value;
+    let val = flag.value.to_string();
     let p1 = info.longest_val_col + 1;
 
     let value_picked_from = flag.value_picked_from.to_string();
@@ -141,16 +168,15 @@
 }
 
 fn set_flag(qualified_name: &str, value: &str) -> Result<()> {
+    ensure!(nix::unistd::Uid::current().is_root(), "must be root to mutate flags");
+
     let flags_binding = DeviceConfigSource::list_flags()?;
     let flag = flags_binding.iter().find(|f| f.qualified_name() == qualified_name).ok_or(
         anyhow!("no aconfig flag '{qualified_name}'. Does the flag have an .aconfig definition?"),
     )?;
 
-    if let FlagPermission::ReadOnly = flag.permission {
-        return Err(anyhow!(
-            "could not write flag '{qualified_name}', it is read-only for the current release configuration.",
-        ));
-    }
+    ensure!(flag.permission == FlagPermission::ReadWrite,
+            format!("could not write flag '{qualified_name}', it is read-only for the current release configuration."));
 
     DeviceConfigSource::override_flag(&flag.namespace, qualified_name, value)?;
 
@@ -161,7 +187,7 @@
     let flags = DeviceConfigSource::list_flags()?;
     let padding_info = PaddingInfo {
         longest_flag_col: flags.iter().map(|f| f.qualified_name().len()).max().unwrap_or(0),
-        longest_val_col: flags.iter().map(|f| f.value.len()).max().unwrap_or(0),
+        longest_val_col: flags.iter().map(|f| f.value.to_string().len()).max().unwrap_or(0),
         longest_value_picked_from_col: flags
             .iter()
             .map(|f| f.value_picked_from.to_string().len())