blob: 037bbd0d78315c554da2bfcf4448b3a97eddca27 [file] [log] [blame]
Ted Bauer4dbf58a2024-02-08 18:46:52 +00001/*
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
Ted Bauera98448f2024-03-06 14:01:19 -050019use anyhow::{anyhow, ensure, Result};
Ted Bauer4dbf58a2024-02-08 18:46:52 +000020use clap::Parser;
21
22mod device_config_source;
23use device_config_source::DeviceConfigSource;
24
Ted Bauera98448f2024-03-06 14:01:19 -050025#[derive(Clone, PartialEq)]
Ted Bauer4dbf58a2024-02-08 18:46:52 +000026enum FlagPermission {
27 ReadOnly,
28 ReadWrite,
29}
30
31impl ToString for FlagPermission {
32 fn to_string(&self) -> String {
33 match &self {
34 Self::ReadOnly => "read-only".into(),
35 Self::ReadWrite => "read-write".into(),
36 }
37 }
38}
39
40#[derive(Clone)]
41enum ValuePickedFrom {
42 Default,
43 Server,
44}
45
46impl ToString for ValuePickedFrom {
47 fn to_string(&self) -> String {
48 match &self {
49 Self::Default => "default".into(),
50 Self::Server => "server".into(),
51 }
52 }
53}
54
55#[derive(Clone)]
56struct Flag {
57 namespace: String,
58 name: String,
59 package: String,
60 container: String,
61 value: String,
62 permission: FlagPermission,
63 value_picked_from: ValuePickedFrom,
64}
65
Ted Bauer84883bd2024-03-04 22:45:29 +000066impl Flag {
67 fn qualified_name(&self) -> String {
68 format!("{}.{}", self.package, self.name)
69 }
70}
71
Ted Bauer4dbf58a2024-02-08 18:46:52 +000072trait FlagSource {
73 fn list_flags() -> Result<Vec<Flag>>;
Ted Bauer84883bd2024-03-04 22:45:29 +000074 fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>;
Ted Bauer4dbf58a2024-02-08 18:46:52 +000075}
76
77const ABOUT_TEXT: &str = "Tool for reading and writing flags.
78
79Rows in the table from the `list` command follow this format:
80
81 package flag_name value provenance permission container
82
83 * `package`: package set for this flag in its .aconfig definition.
84 * `flag_name`: flag name, also set in definition.
85 * `value`: the value read from the flag.
86 * `provenance`: one of:
87 + `default`: the flag value comes from its build-time default.
88 + `server`: the flag value comes from a server override.
89 * `permission`: read-write or read-only.
90 * `container`: the container for the flag, configured in its definition.
91";
92
93#[derive(Parser, Debug)]
94#[clap(long_about=ABOUT_TEXT)]
95struct Cli {
96 #[clap(subcommand)]
97 command: Command,
98}
99
100#[derive(Parser, Debug)]
101enum Command {
102 /// List all aconfig flags on this device.
103 List,
Ted Bauer84883bd2024-03-04 22:45:29 +0000104
105 /// Enable an aconfig flag on this device, on the next boot.
106 Enable {
107 /// <package>.<flag_name>
108 qualified_name: String,
109 },
110
111 /// Disable an aconfig flag on this device, on the next boot.
112 Disable {
113 /// <package>.<flag_name>
114 qualified_name: String,
115 },
Ted Bauer4dbf58a2024-02-08 18:46:52 +0000116}
117
118struct PaddingInfo {
119 longest_package_col: usize,
120 longest_name_col: usize,
121 longest_val_col: usize,
122 longest_value_picked_from_col: usize,
123 longest_permission_col: usize,
124}
125
126fn format_flag_row(flag: &Flag, info: &PaddingInfo) -> String {
127 let pkg = &flag.package;
128 let p0 = info.longest_package_col + 1;
129
130 let name = &flag.name;
131 let p1 = info.longest_name_col + 1;
132
133 let val = flag.value.to_string();
134 let p2 = info.longest_val_col + 1;
135
136 let value_picked_from = flag.value_picked_from.to_string();
137 let p3 = info.longest_value_picked_from_col + 1;
138
139 let perm = flag.permission.to_string();
140 let p4 = info.longest_permission_col + 1;
141
142 let container = &flag.container;
143
144 format!("{pkg:p0$}{name:p1$}{val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n")
145}
146
Ted Bauer84883bd2024-03-04 22:45:29 +0000147fn set_flag(qualified_name: &str, value: &str) -> Result<()> {
Ted Bauera98448f2024-03-06 14:01:19 -0500148 ensure!(nix::unistd::Uid::current().is_root(), "must be root to mutate flags");
149
Ted Bauer84883bd2024-03-04 22:45:29 +0000150 let flags_binding = DeviceConfigSource::list_flags()?;
151 let flag = flags_binding.iter().find(|f| f.qualified_name() == qualified_name).ok_or(
152 anyhow!("no aconfig flag '{qualified_name}'. Does the flag have an .aconfig definition?"),
153 )?;
154
Ted Bauera98448f2024-03-06 14:01:19 -0500155 ensure!(flag.permission == FlagPermission::ReadWrite,
156 format!("could not write flag '{qualified_name}', it is read-only for the current release configuration."));
Ted Bauer84883bd2024-03-04 22:45:29 +0000157
158 DeviceConfigSource::override_flag(&flag.namespace, qualified_name, value)?;
159
160 Ok(())
161}
162
Ted Bauer4dbf58a2024-02-08 18:46:52 +0000163fn list() -> Result<String> {
164 let flags = DeviceConfigSource::list_flags()?;
165 let padding_info = PaddingInfo {
166 longest_package_col: flags.iter().map(|f| f.package.len()).max().unwrap_or(0),
167 longest_name_col: flags.iter().map(|f| f.name.len()).max().unwrap_or(0),
168 longest_val_col: flags.iter().map(|f| f.value.to_string().len()).max().unwrap_or(0),
169 longest_value_picked_from_col: flags
170 .iter()
171 .map(|f| f.value_picked_from.to_string().len())
172 .max()
173 .unwrap_or(0),
174 longest_permission_col: flags
175 .iter()
176 .map(|f| f.permission.to_string().len())
177 .max()
178 .unwrap_or(0),
179 };
180
181 let mut result = String::from("");
182 for flag in flags {
183 let row = format_flag_row(&flag, &padding_info);
184 result.push_str(&row);
185 }
186 Ok(result)
187}
188
189fn main() {
190 let cli = Cli::parse();
191 let output = match cli.command {
Ted Bauer84883bd2024-03-04 22:45:29 +0000192 Command::List => list().map(Some),
193 Command::Enable { qualified_name } => set_flag(&qualified_name, "true").map(|_| None),
194 Command::Disable { qualified_name } => set_flag(&qualified_name, "false").map(|_| None),
Ted Bauer4dbf58a2024-02-08 18:46:52 +0000195 };
196 match output {
Ted Bauer84883bd2024-03-04 22:45:29 +0000197 Ok(Some(text)) => println!("{text}"),
198 Ok(None) => (),
199 Err(message) => println!("Error: {message}"),
Ted Bauer4dbf58a2024-02-08 18:46:52 +0000200 }
201}