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