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