xref: /aosp_15_r20/build/make/tools/aconfig/aflags/src/main.rs (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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 //! `aflags` is a device binary to read and write aconfig flags.
18 
19 use anyhow::{anyhow, ensure, Result};
20 use clap::Parser;
21 
22 mod device_config_source;
23 use device_config_source::DeviceConfigSource;
24 
25 mod aconfig_storage_source;
26 use aconfig_storage_source::AconfigStorageSource;
27 
28 mod load_protos;
29 
30 #[derive(Clone, PartialEq, Debug)]
31 enum FlagPermission {
32     ReadOnly,
33     ReadWrite,
34 }
35 
36 impl std::fmt::Display for FlagPermission {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result37     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38         write!(
39             f,
40             "{}",
41             match &self {
42                 Self::ReadOnly => "read-only",
43                 Self::ReadWrite => "read-write",
44             }
45         )
46     }
47 }
48 
49 #[derive(Clone, Debug)]
50 enum ValuePickedFrom {
51     Default,
52     Server,
53     Local,
54 }
55 
56 impl std::fmt::Display for ValuePickedFrom {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result57     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58         write!(
59             f,
60             "{}",
61             match &self {
62                 Self::Default => "default",
63                 Self::Server => "server",
64                 Self::Local => "local",
65             }
66         )
67     }
68 }
69 
70 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
71 enum FlagValue {
72     Enabled,
73     Disabled,
74 }
75 
76 impl TryFrom<&str> for FlagValue {
77     type Error = anyhow::Error;
78 
try_from(value: &str) -> std::result::Result<Self, Self::Error>79     fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
80         match value {
81             "true" | "enabled" => Ok(Self::Enabled),
82             "false" | "disabled" => Ok(Self::Disabled),
83             _ => Err(anyhow!("cannot convert string '{}' to FlagValue", value)),
84         }
85     }
86 }
87 
88 impl std::fmt::Display for FlagValue {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result89     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90         write!(
91             f,
92             "{}",
93             match &self {
94                 Self::Enabled => "enabled",
95                 Self::Disabled => "disabled",
96             }
97         )
98     }
99 }
100 
101 #[derive(Clone, Debug)]
102 struct Flag {
103     namespace: String,
104     name: String,
105     package: String,
106     container: String,
107     value: FlagValue,
108     staged_value: Option<FlagValue>,
109     permission: FlagPermission,
110     value_picked_from: ValuePickedFrom,
111 }
112 
113 impl Flag {
qualified_name(&self) -> String114     fn qualified_name(&self) -> String {
115         format!("{}.{}", self.package, self.name)
116     }
117 
display_staged_value(&self) -> String118     fn display_staged_value(&self) -> String {
119         match (&self.permission, self.staged_value) {
120             (FlagPermission::ReadOnly, _) => "-".to_string(),
121             (FlagPermission::ReadWrite, None) => "-".to_string(),
122             (FlagPermission::ReadWrite, Some(v)) => format!("(->{})", v),
123         }
124     }
125 }
126 
127 trait FlagSource {
list_flags() -> Result<Vec<Flag>>128     fn list_flags() -> Result<Vec<Flag>>;
override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>129     fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>;
130 }
131 
132 enum FlagSourceType {
133     DeviceConfig,
134     AconfigStorage,
135 }
136 
137 const ABOUT_TEXT: &str = "Tool for reading and writing flags.
138 
139 Rows in the table from the `list` command follow this format:
140 
141   package flag_name value provenance permission container
142 
143   * `package`: package set for this flag in its .aconfig definition.
144   * `flag_name`: flag name, also set in definition.
145   * `value`: the value read from the flag.
146   * `staged_value`: the value on next boot:
147     + `-`: same as current value
148     + `(->enabled) flipped to enabled on boot.
149     + `(->disabled) flipped to disabled on boot.
150   * `provenance`: one of:
151     + `default`: the flag value comes from its build-time default.
152     + `server`: the flag value comes from a server override.
153   * `permission`: read-write or read-only.
154   * `container`: the container for the flag, configured in its definition.
155 ";
156 
157 #[derive(Parser, Debug)]
158 #[clap(long_about=ABOUT_TEXT)]
159 struct Cli {
160     #[clap(subcommand)]
161     command: Command,
162 }
163 
164 #[derive(Parser, Debug)]
165 enum Command {
166     /// List all aconfig flags on this device.
167     List {
168         /// Optionally filter by container name.
169         #[clap(short = 'c', long = "container")]
170         container: Option<String>,
171     },
172 
173     /// Enable an aconfig flag on this device, on the next boot.
174     Enable {
175         /// <package>.<flag_name>
176         qualified_name: String,
177     },
178 
179     /// Disable an aconfig flag on this device, on the next boot.
180     Disable {
181         /// <package>.<flag_name>
182         qualified_name: String,
183     },
184 
185     /// Display which flag storage backs aconfig flags.
186     WhichBacking,
187 }
188 
189 struct PaddingInfo {
190     longest_flag_col: usize,
191     longest_val_col: usize,
192     longest_staged_val_col: usize,
193     longest_value_picked_from_col: usize,
194     longest_permission_col: usize,
195 }
196 
197 struct Filter {
198     container: Option<String>,
199 }
200 
201 impl Filter {
apply(&self, flags: &[Flag]) -> Vec<Flag>202     fn apply(&self, flags: &[Flag]) -> Vec<Flag> {
203         flags
204             .iter()
205             .filter(|flag| match &self.container {
206                 Some(c) => flag.container == *c,
207                 None => true,
208             })
209             .cloned()
210             .collect()
211     }
212 }
213 
format_flag_row(flag: &Flag, info: &PaddingInfo) -> String214 fn format_flag_row(flag: &Flag, info: &PaddingInfo) -> String {
215     let full_name = flag.qualified_name();
216     let p0 = info.longest_flag_col + 1;
217 
218     let val = flag.value.to_string();
219     let p1 = info.longest_val_col + 1;
220 
221     let staged_val = flag.display_staged_value();
222     let p2 = info.longest_staged_val_col + 1;
223 
224     let value_picked_from = flag.value_picked_from.to_string();
225     let p3 = info.longest_value_picked_from_col + 1;
226 
227     let perm = flag.permission.to_string();
228     let p4 = info.longest_permission_col + 1;
229 
230     let container = &flag.container;
231 
232     format!(
233         "{full_name:p0$}{val:p1$}{staged_val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n"
234     )
235 }
236 
set_flag(qualified_name: &str, value: &str) -> Result<()>237 fn set_flag(qualified_name: &str, value: &str) -> Result<()> {
238     let flags_binding = DeviceConfigSource::list_flags()?;
239     let flag = flags_binding.iter().find(|f| f.qualified_name() == qualified_name).ok_or(
240         anyhow!("no aconfig flag '{qualified_name}'. Does the flag have an .aconfig definition?"),
241     )?;
242 
243     ensure!(flag.permission == FlagPermission::ReadWrite,
244             format!("could not write flag '{qualified_name}', it is read-only for the current release configuration."));
245 
246     DeviceConfigSource::override_flag(&flag.namespace, qualified_name, value)?;
247 
248     Ok(())
249 }
250 
list(source_type: FlagSourceType, container: Option<String>) -> Result<String>251 fn list(source_type: FlagSourceType, container: Option<String>) -> Result<String> {
252     let flags_unfiltered = match source_type {
253         FlagSourceType::DeviceConfig => DeviceConfigSource::list_flags()?,
254         FlagSourceType::AconfigStorage => AconfigStorageSource::list_flags()?,
255     };
256 
257     if let Some(ref c) = container {
258         ensure!(
259             load_protos::list_containers()?.contains(c),
260             format!("container '{}' not found", &c)
261         );
262     }
263 
264     let flags = (Filter { container }).apply(&flags_unfiltered);
265     let padding_info = PaddingInfo {
266         longest_flag_col: flags.iter().map(|f| f.qualified_name().len()).max().unwrap_or(0),
267         longest_val_col: flags.iter().map(|f| f.value.to_string().len()).max().unwrap_or(0),
268         longest_staged_val_col: flags
269             .iter()
270             .map(|f| f.display_staged_value().len())
271             .max()
272             .unwrap_or(0),
273         longest_value_picked_from_col: flags
274             .iter()
275             .map(|f| f.value_picked_from.to_string().len())
276             .max()
277             .unwrap_or(0),
278         longest_permission_col: flags
279             .iter()
280             .map(|f| f.permission.to_string().len())
281             .max()
282             .unwrap_or(0),
283     };
284 
285     let mut result = String::from("");
286     for flag in flags {
287         let row = format_flag_row(&flag, &padding_info);
288         result.push_str(&row);
289     }
290     Ok(result)
291 }
292 
display_which_backing() -> String293 fn display_which_backing() -> String {
294     if aconfig_flags::auto_generated::enable_only_new_storage() {
295         "aconfig_storage".to_string()
296     } else {
297         "device_config".to_string()
298     }
299 }
300 
main() -> Result<()>301 fn main() -> Result<()> {
302     ensure!(nix::unistd::Uid::current().is_root(), "must be root");
303 
304     let cli = Cli::parse();
305     let output = match cli.command {
306         Command::List { container } => {
307             if aconfig_flags::auto_generated::enable_only_new_storage() {
308                 list(FlagSourceType::AconfigStorage, container)
309                     .map_err(|err| anyhow!("could not list flags: {err}"))
310                     .map(Some)
311             } else {
312                 list(FlagSourceType::DeviceConfig, container).map(Some)
313             }
314         }
315         Command::Enable { qualified_name } => set_flag(&qualified_name, "true").map(|_| None),
316         Command::Disable { qualified_name } => set_flag(&qualified_name, "false").map(|_| None),
317         Command::WhichBacking => Ok(Some(display_which_backing())),
318     };
319     match output {
320         Ok(Some(text)) => println!("{text}"),
321         Ok(None) => (),
322         Err(message) => println!("Error: {message}"),
323     }
324 
325     Ok(())
326 }
327 
328 #[cfg(test)]
329 mod tests {
330     use super::*;
331 
332     #[test]
test_filter_container()333     fn test_filter_container() {
334         let flags = vec![
335             Flag {
336                 namespace: "namespace".to_string(),
337                 name: "test1".to_string(),
338                 package: "package".to_string(),
339                 value: FlagValue::Disabled,
340                 staged_value: None,
341                 permission: FlagPermission::ReadWrite,
342                 value_picked_from: ValuePickedFrom::Default,
343                 container: "system".to_string(),
344             },
345             Flag {
346                 namespace: "namespace".to_string(),
347                 name: "test2".to_string(),
348                 package: "package".to_string(),
349                 value: FlagValue::Disabled,
350                 staged_value: None,
351                 permission: FlagPermission::ReadWrite,
352                 value_picked_from: ValuePickedFrom::Default,
353                 container: "not_system".to_string(),
354             },
355             Flag {
356                 namespace: "namespace".to_string(),
357                 name: "test3".to_string(),
358                 package: "package".to_string(),
359                 value: FlagValue::Disabled,
360                 staged_value: None,
361                 permission: FlagPermission::ReadWrite,
362                 value_picked_from: ValuePickedFrom::Default,
363                 container: "system".to_string(),
364             },
365         ];
366 
367         assert_eq!((Filter { container: Some("system".to_string()) }).apply(&flags).len(), 2);
368     }
369 
370     #[test]
test_filter_no_container()371     fn test_filter_no_container() {
372         let flags = vec![
373             Flag {
374                 namespace: "namespace".to_string(),
375                 name: "test1".to_string(),
376                 package: "package".to_string(),
377                 value: FlagValue::Disabled,
378                 staged_value: None,
379                 permission: FlagPermission::ReadWrite,
380                 value_picked_from: ValuePickedFrom::Default,
381                 container: "system".to_string(),
382             },
383             Flag {
384                 namespace: "namespace".to_string(),
385                 name: "test2".to_string(),
386                 package: "package".to_string(),
387                 value: FlagValue::Disabled,
388                 staged_value: None,
389                 permission: FlagPermission::ReadWrite,
390                 value_picked_from: ValuePickedFrom::Default,
391                 container: "not_system".to_string(),
392             },
393             Flag {
394                 namespace: "namespace".to_string(),
395                 name: "test3".to_string(),
396                 package: "package".to_string(),
397                 value: FlagValue::Disabled,
398                 staged_value: None,
399                 permission: FlagPermission::ReadWrite,
400                 value_picked_from: ValuePickedFrom::Default,
401                 container: "system".to_string(),
402             },
403         ];
404 
405         assert_eq!((Filter { container: None }).apply(&flags).len(), 3);
406     }
407 }
408