1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 use crate::load_protos;
18 use crate::{Flag, FlagSource, FlagValue, ValuePickedFrom};
19 
20 use anyhow::{anyhow, bail, Result};
21 use regex::Regex;
22 use std::collections::HashMap;
23 use std::process::Command;
24 use std::str;
25 
26 pub struct DeviceConfigSource {}
27 
parse_device_config(raw: &str) -> Result<HashMap<String, FlagValue>>28 fn parse_device_config(raw: &str) -> Result<HashMap<String, FlagValue>> {
29     let mut flags = HashMap::new();
30     let regex = Regex::new(r"(?m)^([[[:alnum:]]_]+/[[[:alnum:]]_\.]+)=(true|false)$")?;
31     for capture in regex.captures_iter(raw) {
32         let key =
33             capture.get(1).ok_or(anyhow!("invalid device_config output"))?.as_str().to_string();
34         let value = FlagValue::try_from(
35             capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str(),
36         )?;
37         flags.insert(key, value);
38     }
39     Ok(flags)
40 }
41 
read_device_config_output(command: &[&str]) -> Result<String>42 fn read_device_config_output(command: &[&str]) -> Result<String> {
43     let output = Command::new("/system/bin/device_config").args(command).output()?;
44     if !output.status.success() {
45         let reason = match output.status.code() {
46             Some(code) => {
47                 let output = str::from_utf8(&output.stdout)?;
48                 if !output.is_empty() {
49                     format!("exit code {code}, output was {output}")
50                 } else {
51                     format!("exit code {code}")
52                 }
53             }
54             None => "terminated by signal".to_string(),
55         };
56         bail!("failed to access flag storage: {}", reason);
57     }
58     Ok(str::from_utf8(&output.stdout)?.to_string())
59 }
60 
read_device_config_flags() -> Result<HashMap<String, FlagValue>>61 fn read_device_config_flags() -> Result<HashMap<String, FlagValue>> {
62     let list_output = read_device_config_output(&["list"])?;
63     parse_device_config(&list_output)
64 }
65 
66 /// Parse the list of newline-separated staged flags.
67 ///
68 /// The output is a newline-sepaarated list of entries which follow this format:
69 ///   `namespace*flagname=value`
70 ///
71 /// The resulting map maps from `namespace/flagname` to `value`, if a staged flag exists for
72 /// `namespace/flagname`.
parse_staged_flags(raw: &str) -> Result<HashMap<String, FlagValue>>73 fn parse_staged_flags(raw: &str) -> Result<HashMap<String, FlagValue>> {
74     let mut flags = HashMap::new();
75     for line in raw.split('\n') {
76         match (line.find('*'), line.find('=')) {
77             (Some(star_index), Some(equal_index)) => {
78                 let namespace = &line[..star_index];
79                 let flag = &line[star_index + 1..equal_index];
80                 if let Ok(value) = FlagValue::try_from(&line[equal_index + 1..]) {
81                     flags.insert(namespace.to_owned() + "/" + flag, value);
82                 }
83             }
84             _ => continue,
85         };
86     }
87     Ok(flags)
88 }
89 
read_staged_flags() -> Result<HashMap<String, FlagValue>>90 fn read_staged_flags() -> Result<HashMap<String, FlagValue>> {
91     let staged_flags_output = read_device_config_output(&["list", "staged"])?;
92     parse_staged_flags(&staged_flags_output)
93 }
94 
reconcile( pb_flags: &[Flag], dc_flags: HashMap<String, FlagValue>, staged_flags: HashMap<String, FlagValue>, ) -> Vec<Flag>95 fn reconcile(
96     pb_flags: &[Flag],
97     dc_flags: HashMap<String, FlagValue>,
98     staged_flags: HashMap<String, FlagValue>,
99 ) -> Vec<Flag> {
100     pb_flags
101         .iter()
102         .map(|f| {
103             let server_override = dc_flags.get(&format!("{}/{}", f.namespace, f.qualified_name()));
104             let (value_picked_from, selected_value) = match server_override {
105                 Some(value) if *value != f.value => (ValuePickedFrom::Server, *value),
106                 _ => (ValuePickedFrom::Default, f.value),
107             };
108             Flag { value_picked_from, value: selected_value, ..f.clone() }
109         })
110         .map(|f| {
111             let staged_value = staged_flags
112                 .get(&format!("{}/{}", f.namespace, f.qualified_name()))
113                 .map(|value| if *value != f.value { Some(*value) } else { None })
114                 .unwrap_or(None);
115             Flag { staged_value, ..f }
116         })
117         .collect()
118 }
119 
120 impl FlagSource for DeviceConfigSource {
list_flags() -> Result<Vec<Flag>>121     fn list_flags() -> Result<Vec<Flag>> {
122         let pb_flags = load_protos::load()?;
123         let dc_flags = read_device_config_flags()?;
124         let staged_flags = read_staged_flags()?;
125 
126         let flags = reconcile(&pb_flags, dc_flags, staged_flags);
127         Ok(flags)
128     }
129 
override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>130     fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()> {
131         read_device_config_output(&["put", namespace, qualified_name, value]).map(|_| ())
132     }
133 }
134 
135 #[cfg(test)]
136 mod tests {
137     use super::*;
138 
139     #[test]
test_parse_device_config()140     fn test_parse_device_config() {
141         let input = r#"
142 namespace_one/com.foo.bar.flag_one=true
143 namespace_one/com.foo.bar.flag_two=false
144 random_noise;
145 namespace_two/android.flag_one=true
146 namespace_two/android.flag_two=nonsense
147 "#;
148         let expected = HashMap::from([
149             ("namespace_one/com.foo.bar.flag_one".to_string(), FlagValue::Enabled),
150             ("namespace_one/com.foo.bar.flag_two".to_string(), FlagValue::Disabled),
151             ("namespace_two/android.flag_one".to_string(), FlagValue::Enabled),
152         ]);
153         let actual = parse_device_config(input).unwrap();
154         assert_eq!(expected, actual);
155     }
156 }
157