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