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