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