Add ability to manually write to binary files.

I was updating the format of PackageTableHeader to add an additional
field (and due to that change incremented the file version). This broke
several tests under aconfig_storage_read_api and
aconfig_storage_write_api that were operating on files written in the
old schema. I tried to re-generate them using aconfig create-storage as
explained in aosp/2933375, but was having some trouble. Figure if we can
just update the files directly it will be easier to make updates in the
future anyway. This isn't bypassing logic that's tested - IIUC the tests
cover reading the file correctly (writing is covered in separate tests).

Usage:
$ aconfig-storage print --file=path/to/flag.map --type=flag_map
--format=json > flag_map.json
$ vim flag_map.json // Manually make updates
$ aconfig-storage write-bytes --input-file=flag_map.json
--output-file=path/to/flag.map --type=flag_map

Change-Id: I212bf0b97483740b30130eb121acb895d350da84
Test: manual (adding debug-only tooling) + cargo t
Bug: 316357686
This commit is contained in:
Marybeth Fair
2024-09-12 16:14:11 -04:00
parent 1c0e255327
commit 45b0438cc0
8 changed files with 125 additions and 25 deletions

View File

@@ -14,6 +14,7 @@ rust_defaults {
"libclap",
"libcxx",
"libaconfig_storage_protos",
"libserde",
],
}
@@ -36,7 +37,10 @@ rust_binary_host {
name: "aconfig-storage",
defaults: ["aconfig_storage_file.defaults"],
srcs: ["src/main.rs"],
rustlibs: ["libaconfig_storage_file"],
rustlibs: [
"libaconfig_storage_file",
"libserde_json",
],
}
rust_test_host {

View File

@@ -14,6 +14,8 @@ tempfile = "3.9.0"
thiserror = "1.0.56"
clap = { version = "4.1.8", features = ["derive"] }
cxx = "1.0"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
[[bin]]
name = "aconfig-storage"

View File

@@ -20,10 +20,11 @@
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Flag info header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagInfoHeader {
pub version: u32,
pub container: String,
@@ -89,7 +90,7 @@ impl FlagInfoHeader {
}
/// bit field for flag info
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FlagInfoBit {
HasServerOverride = 1 << 0,
IsReadWrite = 1 << 1,
@@ -97,7 +98,7 @@ pub enum FlagInfoBit {
}
/// Flag info node struct
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub struct FlagInfoNode {
pub attributes: u8,
}
@@ -138,7 +139,7 @@ impl FlagInfoNode {
}
/// Flag info list struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagInfoList {
pub header: FlagInfoHeader,
pub nodes: Vec<FlagInfoNode>,

View File

@@ -23,10 +23,11 @@ use crate::{
};
use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Flag table header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagTableHeader {
pub version: u32,
pub container: String,
@@ -95,7 +96,7 @@ impl FlagTableHeader {
}
/// Flag table node struct
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub struct FlagTableNode {
pub package_id: u32,
pub flag_name: String,
@@ -154,7 +155,7 @@ impl FlagTableNode {
}
}
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagTable {
pub header: FlagTableHeader,
pub buckets: Vec<Option<u32>>,

View File

@@ -20,10 +20,11 @@
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Flag value header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagValueHeader {
pub version: u32,
pub container: String,
@@ -89,7 +90,7 @@ impl FlagValueHeader {
}
/// Flag value list struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct FlagValueList {
pub header: FlagValueHeader,
pub booleans: Vec<bool>,

View File

@@ -41,6 +41,7 @@ pub mod sip_hasher13;
pub mod test_utils;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fs::File;
use std::hash::Hasher;
@@ -107,7 +108,7 @@ impl TryFrom<u8> for StorageFileType {
/// Flag type enum as stored by storage file
/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum StoredFlagType {
ReadWriteBoolean = 0,
ReadOnlyBoolean = 1,

View File

@@ -20,9 +20,29 @@ use aconfig_storage_file::{
list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList,
FlagTable, FlagValueList, PackageTable, StorageFileType,
};
use clap::{builder::ArgAction, Arg, Command};
use serde::Serialize;
use serde_json;
use std::fmt;
use std::fs;
use std::fs::File;
use std::io::Write;
/**
* Usage Examples
*
* Print file:
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map
*
* List flags:
* $ aconfig-storage list --flag-map=path/to/flag.map \
* --flag-val=path/to/flag.val --package-map=path/to/package.map
*
* Write binary file for testing:
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map --format=json > flag_map.json
* $ vim flag_map.json // Manually make updates
* $ aconfig-storage write-bytes --input-file=flag_map.json --output-file=path/to/flag.map --type=flag_map
*/
fn cli() -> Command {
Command::new("aconfig-storage")
.subcommand_required(true)
@@ -34,7 +54,8 @@ fn cli() -> Command {
.long("type")
.required(true)
.value_parser(|s: &str| StorageFileType::try_from(s)),
),
)
.arg(Arg::new("format").long("format").required(false).action(ArgAction::Set)),
)
.subcommand(
Command::new("list")
@@ -50,41 +71,75 @@ fn cli() -> Command {
Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set),
),
)
.subcommand(
Command::new("write-bytes")
// Where to write the output bytes. Suggest to use the StorageFileType names (e.g. flag.map).
.arg(
Arg::new("output-file")
.long("output-file")
.required(true)
.action(ArgAction::Set),
)
// Input file should be json.
.arg(
Arg::new("input-file").long("input-file").required(true).action(ArgAction::Set),
)
.arg(
Arg::new("type")
.long("type")
.required(true)
.value_parser(|s: &str| StorageFileType::try_from(s)),
),
)
}
fn print_storage_file(
file_path: &str,
file_type: &StorageFileType,
as_json: bool,
) -> Result<(), AconfigStorageError> {
let bytes = read_file_to_bytes(file_path)?;
match file_type {
StorageFileType::PackageMap => {
let package_table = PackageTable::from_bytes(&bytes)?;
println!("{:?}", package_table);
println!("{}", to_print_format(package_table, as_json));
}
StorageFileType::FlagMap => {
let flag_table = FlagTable::from_bytes(&bytes)?;
println!("{:?}", flag_table);
println!("{}", to_print_format(flag_table, as_json));
}
StorageFileType::FlagVal => {
let flag_value = FlagValueList::from_bytes(&bytes)?;
println!("{:?}", flag_value);
println!("{}", to_print_format(flag_value, as_json));
}
StorageFileType::FlagInfo => {
let flag_info = FlagInfoList::from_bytes(&bytes)?;
println!("{:?}", flag_info);
println!("{}", to_print_format(flag_info, as_json));
}
}
Ok(())
}
fn to_print_format<T>(file_contents: T, as_json: bool) -> String
where
T: Serialize + fmt::Debug,
{
if as_json {
serde_json::to_string(&file_contents).unwrap()
} else {
format!("{:?}", file_contents)
}
}
fn main() -> Result<(), AconfigStorageError> {
let matches = cli().get_matches();
match matches.subcommand() {
Some(("print", sub_matches)) => {
let file_path = sub_matches.get_one::<String>("file").unwrap();
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
print_storage_file(file_path, file_type)?
let format = sub_matches.get_one::<String>("format");
let as_json: bool = format == Some(&"json".to_string());
print_storage_file(file_path, file_type, as_json)?
}
Some(("list", sub_matches)) => {
let package_map = sub_matches.get_one::<String>("package-map").unwrap();
@@ -96,10 +151,10 @@ fn main() -> Result<(), AconfigStorageError> {
let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?;
for flag in flags.iter() {
println!(
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
);
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
);
}
}
None => {
@@ -113,6 +168,40 @@ fn main() -> Result<(), AconfigStorageError> {
}
}
}
// Converts JSON of the file into raw bytes (as is used on-device).
// Intended to generate/easily update these files for testing.
Some(("write-bytes", sub_matches)) => {
let input_file_path = sub_matches.get_one::<String>("input-file").unwrap();
let input_json = fs::read_to_string(input_file_path).unwrap();
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
let output_bytes: Vec<u8>;
match file_type {
StorageFileType::FlagVal => {
let list: FlagValueList = serde_json::from_str(&input_json).unwrap();
output_bytes = list.into_bytes();
}
StorageFileType::FlagInfo => {
let list: FlagInfoList = serde_json::from_str(&input_json).unwrap();
output_bytes = list.into_bytes();
}
StorageFileType::FlagMap => {
let table: FlagTable = serde_json::from_str(&input_json).unwrap();
output_bytes = table.into_bytes();
}
StorageFileType::PackageMap => {
let table: PackageTable = serde_json::from_str(&input_json).unwrap();
output_bytes = table.into_bytes();
}
}
let output_file_path = sub_matches.get_one::<String>("output-file").unwrap();
let file = File::create(output_file_path);
if file.is_err() {
panic!("can't make file");
}
let _ = file.unwrap().write_all(&output_bytes);
}
_ => unreachable!(),
}
Ok(())

View File

@@ -20,10 +20,11 @@
use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Package table header struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PackageTableHeader {
pub version: u32,
pub container: String,
@@ -92,7 +93,7 @@ impl PackageTableHeader {
}
/// Package table node struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PackageTableNode {
pub package_name: String,
pub package_id: u32,
@@ -151,7 +152,7 @@ impl PackageTableNode {
}
/// Package table struct
#[derive(PartialEq)]
#[derive(PartialEq, Serialize, Deserialize)]
pub struct PackageTable {
pub header: PackageTableHeader,
pub buckets: Vec<Option<u32>>,