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 //! `aconfig-storage` is a debugging tool to parse storage files
18
19 use aconfig_storage_file::{
20 list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList,
21 FlagTable, FlagValueList, PackageTable, StorageFileType,
22 };
23 use clap::{builder::ArgAction, Arg, Command};
24 use serde::Serialize;
25 use serde_json;
26 use std::fmt;
27 use std::fs;
28 use std::fs::File;
29 use std::io::Write;
30
31 /**
32 * Usage Examples
33 *
34 * Print file:
35 * $ aconfig-storage print --file=path/to/flag.map --type=flag_map
36 *
37 * List flags:
38 * $ aconfig-storage list --flag-map=path/to/flag.map \
39 * --flag-val=path/to/flag.val --package-map=path/to/package.map
40 *
41 * Write binary file for testing:
42 * $ aconfig-storage print --file=path/to/flag.map --type=flag_map --format=json > flag_map.json
43 * $ vim flag_map.json // Manually make updates
44 * $ aconfig-storage write-bytes --input-file=flag_map.json --output-file=path/to/flag.map --type=flag_map
45 */
cli() -> Command46 fn cli() -> Command {
47 Command::new("aconfig-storage")
48 .subcommand_required(true)
49 .subcommand(
50 Command::new("print")
51 .arg(Arg::new("file").long("file").required(true).action(ArgAction::Set))
52 .arg(
53 Arg::new("type")
54 .long("type")
55 .required(true)
56 .value_parser(|s: &str| StorageFileType::try_from(s)),
57 )
58 .arg(Arg::new("format").long("format").required(false).action(ArgAction::Set)),
59 )
60 .subcommand(
61 Command::new("list")
62 .arg(
63 Arg::new("package-map")
64 .long("package-map")
65 .required(true)
66 .action(ArgAction::Set),
67 )
68 .arg(Arg::new("flag-map").long("flag-map").required(true).action(ArgAction::Set))
69 .arg(Arg::new("flag-val").long("flag-val").required(true).action(ArgAction::Set))
70 .arg(
71 Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set),
72 ),
73 )
74 .subcommand(
75 Command::new("write-bytes")
76 // Where to write the output bytes. Suggest to use the StorageFileType names (e.g. flag.map).
77 .arg(
78 Arg::new("output-file")
79 .long("output-file")
80 .required(true)
81 .action(ArgAction::Set),
82 )
83 // Input file should be json.
84 .arg(
85 Arg::new("input-file").long("input-file").required(true).action(ArgAction::Set),
86 )
87 .arg(
88 Arg::new("type")
89 .long("type")
90 .required(true)
91 .value_parser(|s: &str| StorageFileType::try_from(s)),
92 ),
93 )
94 }
95
print_storage_file( file_path: &str, file_type: &StorageFileType, as_json: bool, ) -> Result<(), AconfigStorageError>96 fn print_storage_file(
97 file_path: &str,
98 file_type: &StorageFileType,
99 as_json: bool,
100 ) -> Result<(), AconfigStorageError> {
101 let bytes = read_file_to_bytes(file_path)?;
102 match file_type {
103 StorageFileType::PackageMap => {
104 let package_table = PackageTable::from_bytes(&bytes)?;
105 println!("{}", to_print_format(package_table, as_json));
106 }
107 StorageFileType::FlagMap => {
108 let flag_table = FlagTable::from_bytes(&bytes)?;
109 println!("{}", to_print_format(flag_table, as_json));
110 }
111 StorageFileType::FlagVal => {
112 let flag_value = FlagValueList::from_bytes(&bytes)?;
113 println!("{}", to_print_format(flag_value, as_json));
114 }
115 StorageFileType::FlagInfo => {
116 let flag_info = FlagInfoList::from_bytes(&bytes)?;
117 println!("{}", to_print_format(flag_info, as_json));
118 }
119 }
120 Ok(())
121 }
122
to_print_format<T>(file_contents: T, as_json: bool) -> String where T: Serialize + fmt::Debug,123 fn to_print_format<T>(file_contents: T, as_json: bool) -> String
124 where
125 T: Serialize + fmt::Debug,
126 {
127 if as_json {
128 serde_json::to_string(&file_contents).unwrap()
129 } else {
130 format!("{:?}", file_contents)
131 }
132 }
133
main() -> Result<(), AconfigStorageError>134 fn main() -> Result<(), AconfigStorageError> {
135 let matches = cli().get_matches();
136 match matches.subcommand() {
137 Some(("print", sub_matches)) => {
138 let file_path = sub_matches.get_one::<String>("file").unwrap();
139 let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
140 let format = sub_matches.get_one::<String>("format");
141 let as_json: bool = format == Some(&"json".to_string());
142 print_storage_file(file_path, file_type, as_json)?
143 }
144 Some(("list", sub_matches)) => {
145 let package_map = sub_matches.get_one::<String>("package-map").unwrap();
146 let flag_map = sub_matches.get_one::<String>("flag-map").unwrap();
147 let flag_val = sub_matches.get_one::<String>("flag-val").unwrap();
148 let flag_info = sub_matches.get_one::<String>("flag-info");
149 match flag_info {
150 Some(info_file) => {
151 let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?;
152 for flag in flags.iter() {
153 println!(
154 "{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
155 flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
156 flag.is_readwrite, flag.has_server_override, flag.has_local_override,
157 );
158 }
159 }
160 None => {
161 let flags = list_flags(package_map, flag_map, flag_val)?;
162 for flag in flags.iter() {
163 println!(
164 "{} {} {} {:?}",
165 flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
166 );
167 }
168 }
169 }
170 }
171 // Converts JSON of the file into raw bytes (as is used on-device).
172 // Intended to generate/easily update these files for testing.
173 Some(("write-bytes", sub_matches)) => {
174 let input_file_path = sub_matches.get_one::<String>("input-file").unwrap();
175 let input_json = fs::read_to_string(input_file_path).unwrap();
176
177 let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
178 let output_bytes: Vec<u8>;
179 match file_type {
180 StorageFileType::FlagVal => {
181 let list: FlagValueList = serde_json::from_str(&input_json).unwrap();
182 output_bytes = list.into_bytes();
183 }
184 StorageFileType::FlagInfo => {
185 let list: FlagInfoList = serde_json::from_str(&input_json).unwrap();
186 output_bytes = list.into_bytes();
187 }
188 StorageFileType::FlagMap => {
189 let table: FlagTable = serde_json::from_str(&input_json).unwrap();
190 output_bytes = table.into_bytes();
191 }
192 StorageFileType::PackageMap => {
193 let table: PackageTable = serde_json::from_str(&input_json).unwrap();
194 output_bytes = table.into_bytes();
195 }
196 }
197
198 let output_file_path = sub_matches.get_one::<String>("output-file").unwrap();
199 let file = File::create(output_file_path);
200 if file.is_err() {
201 panic!("can't make file");
202 }
203 let _ = file.unwrap().write_all(&output_bytes);
204 }
205 _ => unreachable!(),
206 }
207 Ok(())
208 }
209