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