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 }
54 
55 impl std::fmt::Display for ValuePickedFrom {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result56     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57         write!(
58             f,
59             "{}",
60             match &self {
61                 Self::Default => "default",
62                 Self::Server => "server",
63             }
64         )
65     }
66 }
67 
68 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
69 enum FlagValue {
70     Enabled,
71     Disabled,
72 }
73 
74 impl TryFrom<&str> for FlagValue {
75     type Error = anyhow::Error;
76 
try_from(value: &str) -> std::result::Result<Self, Self::Error>77     fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
78         match value {
79             "true" | "enabled" => Ok(Self::Enabled),
80             "false" | "disabled" => Ok(Self::Disabled),
81             _ => Err(anyhow!("cannot convert string '{}' to FlagValue", value)),
82         }
83     }
84 }
85 
86 impl std::fmt::Display for FlagValue {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result87     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88         write!(
89             f,
90             "{}",
91             match &self {
92                 Self::Enabled => "enabled",
93                 Self::Disabled => "disabled",
94             }
95         )
96     }
97 }
98 
99 #[derive(Clone, Debug)]
100 struct Flag {
101     namespace: String,
102     name: String,
103     package: String,
104     container: String,
105     value: FlagValue,
106     staged_value: Option<FlagValue>,
107     permission: FlagPermission,
108     value_picked_from: ValuePickedFrom,
109 }
110 
111 impl Flag {
qualified_name(&self) -> String112     fn qualified_name(&self) -> String {
113         format!("{}.{}", self.package, self.name)
114     }
115 
display_staged_value(&self) -> String116     fn display_staged_value(&self) -> String {
117         match self.staged_value {
118             Some(v) => format!("(->{})", v),
119             None => "-".to_string(),
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) -> Result<()>126     fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>;
127 }
128 
129 enum FlagSourceType {
130     DeviceConfig,
131     AconfigStorage,
132 }
133 
134 const ABOUT_TEXT: &str = "Tool for reading and writing flags.
135 
136 Rows in the table from the `list` command follow this format:
137 
138   package flag_name value provenance permission container
139 
140   * `package`: package set for this flag in its .aconfig definition.
141   * `flag_name`: flag name, also set in definition.
142   * `value`: the value read from the flag.
143   * `staged_value`: the value on next boot:
144     + `-`: same as current value
145     + `(->enabled) flipped to enabled on boot.
146     + `(->disabled) flipped to disabled on boot.
147   * `provenance`: one of:
148     + `default`: the flag value comes from its build-time default.
149     + `server`: the flag value comes from a server override.
150   * `permission`: read-write or read-only.
151   * `container`: the container for the flag, configured in its definition.
152 ";
153 
154 #[derive(Parser, Debug)]
155 #[clap(long_about=ABOUT_TEXT)]
156 struct Cli {
157     #[clap(subcommand)]
158     command: Command,
159 }
160 
161 #[derive(Parser, Debug)]
162 enum Command {
163     /// List all aconfig flags on this device.
164     List {
165         /// Read from the new flag storage.
166         #[clap(long)]
167         use_new_storage: bool,
168 
169         /// Optionally filter by container name.
170         #[clap(short = 'c', long = "container")]
171         container: Option<String>,
172     },
173 
174     /// Enable an aconfig flag on this device, on the next boot.
175     Enable {
176         /// <package>.<flag_name>
177         qualified_name: String,
178     },
179 
180     /// Disable an aconfig flag on this device, on the next boot.
181     Disable {
182         /// <package>.<flag_name>
183         qualified_name: String,
184     },
185 }
186 
187 struct PaddingInfo {
188     longest_flag_col: usize,
189     longest_val_col: usize,
190     longest_staged_val_col: usize,
191     longest_value_picked_from_col: usize,
192     longest_permission_col: usize,
193 }
194 
195 struct Filter {
196     container: Option<String>,
197 }
198 
199 impl Filter {
apply(&self, flags: &[Flag]) -> Vec<Flag>200     fn apply(&self, flags: &[Flag]) -> Vec<Flag> {
201         flags
202             .iter()
203             .filter(|flag| match &self.container {
204                 Some(c) => flag.container == *c,
205                 None => true,
206             })
207             .cloned()
208             .collect()
209     }
210 }
211 
format_flag_row(flag: &Flag, info: &PaddingInfo) -> String212 fn format_flag_row(flag: &Flag, info: &PaddingInfo) -> String {
213     let full_name = flag.qualified_name();
214     let p0 = info.longest_flag_col + 1;
215 
216     let val = flag.value.to_string();
217     let p1 = info.longest_val_col + 1;
218 
219     let staged_val = flag.display_staged_value();
220     let p2 = info.longest_staged_val_col + 1;
221 
222     let value_picked_from = flag.value_picked_from.to_string();
223     let p3 = info.longest_value_picked_from_col + 1;
224 
225     let perm = flag.permission.to_string();
226     let p4 = info.longest_permission_col + 1;
227 
228     let container = &flag.container;
229 
230     format!(
231         "{full_name:p0$}{val:p1$}{staged_val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n"
232     )
233 }
234 
set_flag(qualified_name: &str, value: &str) -> Result<()>235 fn set_flag(qualified_name: &str, value: &str) -> Result<()> {
236     ensure!(nix::unistd::Uid::current().is_root(), "must be root to mutate flags");
237 
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     let flags = (Filter { container }).apply(&flags_unfiltered);
257     let padding_info = PaddingInfo {
258         longest_flag_col: flags.iter().map(|f| f.qualified_name().len()).max().unwrap_or(0),
259         longest_val_col: flags.iter().map(|f| f.value.to_string().len()).max().unwrap_or(0),
260         longest_staged_val_col: flags
261             .iter()
262             .map(|f| f.display_staged_value().len())
263             .max()
264             .unwrap_or(0),
265         longest_value_picked_from_col: flags
266             .iter()
267             .map(|f| f.value_picked_from.to_string().len())
268             .max()
269             .unwrap_or(0),
270         longest_permission_col: flags
271             .iter()
272             .map(|f| f.permission.to_string().len())
273             .max()
274             .unwrap_or(0),
275     };
276 
277     let mut result = String::from("");
278     for flag in flags {
279         let row = format_flag_row(&flag, &padding_info);
280         result.push_str(&row);
281     }
282     Ok(result)
283 }
284 
main()285 fn main() {
286     let cli = Cli::parse();
287     let output = match cli.command {
288         Command::List { use_new_storage: true, container } => {
289             list(FlagSourceType::AconfigStorage, container).map(Some)
290         }
291         Command::List { use_new_storage: false, container } => {
292             list(FlagSourceType::DeviceConfig, container).map(Some)
293         }
294         Command::Enable { qualified_name } => set_flag(&qualified_name, "true").map(|_| None),
295         Command::Disable { qualified_name } => set_flag(&qualified_name, "false").map(|_| None),
296     };
297     match output {
298         Ok(Some(text)) => println!("{text}"),
299         Ok(None) => (),
300         Err(message) => println!("Error: {message}"),
301     }
302 }
303 
304 #[cfg(test)]
305 mod tests {
306     use super::*;
307 
308     #[test]
test_filter_container()309     fn test_filter_container() {
310         let flags = vec![
311             Flag {
312                 namespace: "namespace".to_string(),
313                 name: "test1".to_string(),
314                 package: "package".to_string(),
315                 value: FlagValue::Disabled,
316                 staged_value: None,
317                 permission: FlagPermission::ReadWrite,
318                 value_picked_from: ValuePickedFrom::Default,
319                 container: "system".to_string(),
320             },
321             Flag {
322                 namespace: "namespace".to_string(),
323                 name: "test2".to_string(),
324                 package: "package".to_string(),
325                 value: FlagValue::Disabled,
326                 staged_value: None,
327                 permission: FlagPermission::ReadWrite,
328                 value_picked_from: ValuePickedFrom::Default,
329                 container: "not_system".to_string(),
330             },
331             Flag {
332                 namespace: "namespace".to_string(),
333                 name: "test3".to_string(),
334                 package: "package".to_string(),
335                 value: FlagValue::Disabled,
336                 staged_value: None,
337                 permission: FlagPermission::ReadWrite,
338                 value_picked_from: ValuePickedFrom::Default,
339                 container: "system".to_string(),
340             },
341         ];
342 
343         assert_eq!((Filter { container: Some("system".to_string()) }).apply(&flags).len(), 2);
344     }
345 
346     #[test]
test_filter_no_container()347     fn test_filter_no_container() {
348         let flags = vec![
349             Flag {
350                 namespace: "namespace".to_string(),
351                 name: "test1".to_string(),
352                 package: "package".to_string(),
353                 value: FlagValue::Disabled,
354                 staged_value: None,
355                 permission: FlagPermission::ReadWrite,
356                 value_picked_from: ValuePickedFrom::Default,
357                 container: "system".to_string(),
358             },
359             Flag {
360                 namespace: "namespace".to_string(),
361                 name: "test2".to_string(),
362                 package: "package".to_string(),
363                 value: FlagValue::Disabled,
364                 staged_value: None,
365                 permission: FlagPermission::ReadWrite,
366                 value_picked_from: ValuePickedFrom::Default,
367                 container: "not_system".to_string(),
368             },
369             Flag {
370                 namespace: "namespace".to_string(),
371                 name: "test3".to_string(),
372                 package: "package".to_string(),
373                 value: FlagValue::Disabled,
374                 staged_value: None,
375                 permission: FlagPermission::ReadWrite,
376                 value_picked_from: ValuePickedFrom::Default,
377                 container: "system".to_string(),
378             },
379         ];
380 
381         assert_eq!((Filter { container: None }).apply(&flags).len(), 3);
382     }
383 }
384