From 8ee1a5b78580c07be3429933ea0a2a5498c4d94f Mon Sep 17 00:00:00 2001 From: Zhi Dou Date: Wed, 7 Aug 2024 18:51:23 +0000 Subject: [PATCH] DO NOT MERGE: manually cherry pick Change-Id: I47b6d783f32b7c260dd806898ed880901b5f784b Test: presubmit Bug: n/a --- tools/aconfig/aconfig/src/codegen/cpp.rs | 4 + tools/aconfig/aconfig/src/codegen/rust.rs | 776 +++++++++++++++++- tools/aconfig/aconfig/src/commands.rs | 16 +- tools/aconfig/aconfig/src/main.rs | 12 +- .../templates/cpp_source_file.template | 175 ++-- tools/aconfig/aconfig/templates/rust.template | 152 +++- .../aconfig_storage_read_api/src/lib.rs | 2 +- tools/aconfig/aflags/Android.bp | 1 + tools/aconfig/aflags/Cargo.toml | 1 + .../aflags/src/aconfig_storage_source.rs | 229 +++--- tools/aconfig/aflags/src/main.rs | 2 +- 11 files changed, 1154 insertions(+), 216 deletions(-) diff --git a/tools/aconfig/aconfig/src/codegen/cpp.rs b/tools/aconfig/aconfig/src/codegen/cpp.rs index e743b2fc59..2c569da8f6 100644 --- a/tools/aconfig/aconfig/src/codegen/cpp.rs +++ b/tools/aconfig/aconfig/src/codegen/cpp.rs @@ -45,6 +45,8 @@ where let header = package.replace('.', "_"); let package_macro = header.to_uppercase(); let cpp_namespace = package.replace('.', "::"); + ensure!(class_elements.len() > 0); + let container = class_elements[0].container.clone(); ensure!(codegen::is_valid_name_ident(&header)); let context = Context { header: &header, @@ -56,6 +58,7 @@ where readwrite_count, is_test_mode: codegen_mode == CodegenMode::Test, class_elements, + container, allow_instrumentation, }; @@ -100,6 +103,7 @@ pub struct Context<'a> { pub readwrite_count: i32, pub is_test_mode: bool, pub class_elements: Vec, + pub container: String, pub allow_instrumentation: bool, } diff --git a/tools/aconfig/aconfig/src/codegen/rust.rs b/tools/aconfig/aconfig/src/codegen/rust.rs index 33c3d37633..1292e0adb5 100644 --- a/tools/aconfig/aconfig/src/codegen/rust.rs +++ b/tools/aconfig/aconfig/src/codegen/rust.rs @@ -20,26 +20,34 @@ use tinytemplate::TinyTemplate; use aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; +use std::collections::HashMap; + use crate::codegen; use crate::codegen::CodegenMode; use crate::commands::OutputFile; pub fn generate_rust_code( package: &str, + flag_ids: HashMap, parsed_flags_iter: I, codegen_mode: CodegenMode, + allow_instrumentation: bool, ) -> Result where I: Iterator, { - let template_flags: Vec = - parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, &pf)).collect(); + let template_flags: Vec = parsed_flags_iter + .map(|pf| TemplateParsedFlag::new(package, flag_ids.clone(), &pf)) + .collect(); let has_readwrite = template_flags.iter().any(|item| item.readwrite); + let container = (template_flags.first().expect("zero template flags").container).to_string(); let context = TemplateContext { package: package.to_string(), template_flags, modules: package.split('.').map(|s| s.to_string()).collect::>(), has_readwrite, + allow_instrumentation, + container, }; let mut template = TinyTemplate::new(); template.add_template( @@ -62,6 +70,8 @@ struct TemplateContext { pub template_flags: Vec, pub modules: Vec, pub has_readwrite: bool, + pub allow_instrumentation: bool, + pub container: String, } #[derive(Serialize)] @@ -69,25 +79,28 @@ struct TemplateParsedFlag { pub readwrite: bool, pub default_value: String, pub name: String, + pub container: String, + pub flag_offset: u16, pub device_config_namespace: String, pub device_config_flag: String, } impl TemplateParsedFlag { #[allow(clippy::nonminimal_bool)] - fn new(package: &str, pf: &ProtoParsedFlag) -> Self { - let template = TemplateParsedFlag { + fn new(package: &str, flag_offsets: HashMap, pf: &ProtoParsedFlag) -> Self { + Self { readwrite: pf.permission() == ProtoFlagPermission::READ_WRITE, default_value: match pf.state() { ProtoFlagState::ENABLED => "true".to_string(), ProtoFlagState::DISABLED => "false".to_string(), }, name: pf.name().to_string(), + container: pf.container().to_string(), + flag_offset: *flag_offsets.get(pf.name()).expect("didnt find package offset :("), device_config_namespace: pf.namespace().to_string(), device_config_flag: codegen::create_device_config_ident(package, pf.name()) .expect("values checked at flag parse time"), - }; - template + } } } @@ -97,6 +110,14 @@ mod tests { const PROD_EXPECTED: &str = r#" //! codegenerated rust flag lib +use aconfig_storage_read_api::{Mmap, AconfigStorageError, StorageFileType, PackageReadContext, get_mapped_storage_file, get_boolean_flag_value, get_package_read_context}; +use std::path::Path; +use std::io::Write; +use log::{log, LevelFilter, Level}; + +static STORAGE_MIGRATION_MARKER_FILE: &str = + "/metadata/aconfig_test_missions/mission_1"; +static MIGRATION_LOG_TAG: &str = "AconfigTestMission1"; /// flag provider pub struct FlagProvider; @@ -226,6 +247,697 @@ pub fn enabled_ro_exported() -> bool { true } +/// query flag enabled_rw +#[inline(always)] +pub fn enabled_rw() -> bool { + PROVIDER.enabled_rw() +} +"#; + + const PROD_INSTRUMENTED_EXPECTED: &str = r#" +//! codegenerated rust flag lib +use aconfig_storage_read_api::{Mmap, AconfigStorageError, StorageFileType, PackageReadContext, get_mapped_storage_file, get_boolean_flag_value, get_package_read_context}; +use std::path::Path; +use std::io::Write; +use log::{log, LevelFilter, Level}; + +static STORAGE_MIGRATION_MARKER_FILE: &str = + "/metadata/aconfig_test_missions/mission_1"; +static MIGRATION_LOG_TAG: &str = "AconfigTestMission1"; + +/// flag provider +pub struct FlagProvider; + +lazy_static::lazy_static! { + + static ref PACKAGE_OFFSET: Result, AconfigStorageError> = unsafe { + get_mapped_storage_file("system", StorageFileType::PackageMap) + .and_then(|package_map| get_package_read_context(&package_map, "com.android.aconfig.test")) + .map(|context| context.map(|c| c.boolean_start_index)) + }; + + static ref FLAG_VAL_MAP: Result = unsafe { + get_mapped_storage_file("system", StorageFileType::FlagVal) + }; + /// flag value cache for disabled_rw + + static ref CACHED_disabled_rw: bool = { + let result = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.aconfig_test", + "com.android.aconfig.test.disabled_rw", + "false") == "true"; + + let use_new_storage_value = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.core_experiments_team_internal", + "com.android.providers.settings.use_new_storage_value", + "false") == "true"; + + if Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + // This will be called multiple times. Subsequent calls after the first are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info)); + + let aconfig_storage_result = FLAG_VAL_MAP + .as_ref() + .map_err(|err| format!("failed to get flag val map: {err}")) + .and_then(|flag_val_map| { + PACKAGE_OFFSET + .as_ref() + .map_err(|err| format!("failed to get package read offset: {err}")) + .and_then(|package_offset| { + match package_offset { + Some(offset) => { + get_boolean_flag_value(&flag_val_map, offset + 1) + .map_err(|err| format!("failed to get flag: {err}")) + }, + None => Err("no context found for package 'com.android.aconfig.test'".to_string()) + } + }) + }); + + match aconfig_storage_result { + Ok(storage_result) if storage_result == result => { + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Ok(storage_result) => { + log!(Level::Error, "AconfigTestMission1: error: mismatch for flag 'disabled_rw'. Legacy storage was {result}, new storage was {storage_result}"); + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: {err}"); + if use_new_storage_value { + panic!("failed to read flag value: {err}"); + } + } + } + } + + result + }; + + /// flag value cache for disabled_rw_exported + + static ref CACHED_disabled_rw_exported: bool = { + let result = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.aconfig_test", + "com.android.aconfig.test.disabled_rw_exported", + "false") == "true"; + + let use_new_storage_value = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.core_experiments_team_internal", + "com.android.providers.settings.use_new_storage_value", + "false") == "true"; + + if Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + // This will be called multiple times. Subsequent calls after the first are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info)); + + let aconfig_storage_result = FLAG_VAL_MAP + .as_ref() + .map_err(|err| format!("failed to get flag val map: {err}")) + .and_then(|flag_val_map| { + PACKAGE_OFFSET + .as_ref() + .map_err(|err| format!("failed to get package read offset: {err}")) + .and_then(|package_offset| { + match package_offset { + Some(offset) => { + get_boolean_flag_value(&flag_val_map, offset + 2) + .map_err(|err| format!("failed to get flag: {err}")) + }, + None => Err("no context found for package 'com.android.aconfig.test'".to_string()) + } + }) + }); + + match aconfig_storage_result { + Ok(storage_result) if storage_result == result => { + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Ok(storage_result) => { + log!(Level::Error, "AconfigTestMission1: error: mismatch for flag 'disabled_rw_exported'. Legacy storage was {result}, new storage was {storage_result}"); + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: {err}"); + if use_new_storage_value { + panic!("failed to read flag value: {err}"); + } + } + } + } + + result + }; + + /// flag value cache for disabled_rw_in_other_namespace + + static ref CACHED_disabled_rw_in_other_namespace: bool = { + let result = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.other_namespace", + "com.android.aconfig.test.disabled_rw_in_other_namespace", + "false") == "true"; + + let use_new_storage_value = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.core_experiments_team_internal", + "com.android.providers.settings.use_new_storage_value", + "false") == "true"; + + if Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + // This will be called multiple times. Subsequent calls after the first are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info)); + + let aconfig_storage_result = FLAG_VAL_MAP + .as_ref() + .map_err(|err| format!("failed to get flag val map: {err}")) + .and_then(|flag_val_map| { + PACKAGE_OFFSET + .as_ref() + .map_err(|err| format!("failed to get package read offset: {err}")) + .and_then(|package_offset| { + match package_offset { + Some(offset) => { + get_boolean_flag_value(&flag_val_map, offset + 3) + .map_err(|err| format!("failed to get flag: {err}")) + }, + None => Err("no context found for package 'com.android.aconfig.test'".to_string()) + } + }) + }); + + match aconfig_storage_result { + Ok(storage_result) if storage_result == result => { + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Ok(storage_result) => { + log!(Level::Error, "AconfigTestMission1: error: mismatch for flag 'disabled_rw_in_other_namespace'. Legacy storage was {result}, new storage was {storage_result}"); + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: {err}"); + if use_new_storage_value { + panic!("failed to read flag value: {err}"); + } + } + } + } + + result + }; + + /// flag value cache for enabled_rw + + static ref CACHED_enabled_rw: bool = { + let result = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.aconfig_test", + "com.android.aconfig.test.enabled_rw", + "true") == "true"; + + let use_new_storage_value = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.core_experiments_team_internal", + "com.android.providers.settings.use_new_storage_value", + "false") == "true"; + + if Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + // This will be called multiple times. Subsequent calls after the first are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info)); + + let aconfig_storage_result = FLAG_VAL_MAP + .as_ref() + .map_err(|err| format!("failed to get flag val map: {err}")) + .and_then(|flag_val_map| { + PACKAGE_OFFSET + .as_ref() + .map_err(|err| format!("failed to get package read offset: {err}")) + .and_then(|package_offset| { + match package_offset { + Some(offset) => { + get_boolean_flag_value(&flag_val_map, offset + 8) + .map_err(|err| format!("failed to get flag: {err}")) + }, + None => Err("no context found for package 'com.android.aconfig.test'".to_string()) + } + }) + }); + + match aconfig_storage_result { + Ok(storage_result) if storage_result == result => { + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Ok(storage_result) => { + log!(Level::Error, "AconfigTestMission1: error: mismatch for flag 'enabled_rw'. Legacy storage was {result}, new storage was {storage_result}"); + if use_new_storage_value { + return storage_result; + } else { + return result; + } + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: {err}"); + if use_new_storage_value { + panic!("failed to read flag value: {err}"); + } + } + } + } + + result + }; + +} + +impl FlagProvider { + + + /// query flag disabled_ro + pub fn disabled_ro(&self) -> bool { + false + } + + /// query flag disabled_rw + pub fn disabled_rw(&self) -> bool { + *CACHED_disabled_rw + } + + /// query flag disabled_rw_exported + pub fn disabled_rw_exported(&self) -> bool { + *CACHED_disabled_rw_exported + } + + /// query flag disabled_rw_in_other_namespace + pub fn disabled_rw_in_other_namespace(&self) -> bool { + *CACHED_disabled_rw_in_other_namespace + } + + /// query flag enabled_fixed_ro + pub fn enabled_fixed_ro(&self) -> bool { + true + } + + /// query flag enabled_fixed_ro_exported + pub fn enabled_fixed_ro_exported(&self) -> bool { + true + } + + /// query flag enabled_ro + pub fn enabled_ro(&self) -> bool { + true + } + + /// query flag enabled_ro_exported + pub fn enabled_ro_exported(&self) -> bool { + true + } + + /// query flag enabled_rw + pub fn enabled_rw(&self) -> bool { + *CACHED_enabled_rw + } + + +} + +/// flag provider +pub static PROVIDER: FlagProvider = FlagProvider; + + +/// query flag disabled_ro +#[inline(always)] +pub fn disabled_ro() -> bool { + + + let result = false; + if !Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + return result; + } + + // This will be called multiple times. Subsequent calls after the first + // are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info), + ); + + unsafe { + let package_map = match get_mapped_storage_file("system", StorageFileType::PackageMap) { + Ok(file) => file, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'disabled_ro': {err}"); + return result; + } + }; + + let package_read_context = match get_package_read_context(&package_map, "com.android.aconfig.test") { + Ok(Some(context)) => context, + Ok(None) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'disabled_ro': did not get context"); + return result; + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'disabled_ro': {err}"); + return result; + } + }; + let flag_val_map = match get_mapped_storage_file("system", StorageFileType::FlagVal) { + Ok(val_map) => val_map, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'disabled_ro': {err}"); + return result; + } + }; + let value = match get_boolean_flag_value(&flag_val_map, 0 + package_read_context.boolean_start_index) { + Ok(val) => val, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'disabled_ro': {err}"); + return result; + } + }; + + if result != value { + log!(Level::Error, "AconfigTestMission1: error: flag mismatch for 'disabled_ro'. Legacy storage was {result}, new storage was {value}"); + } else { + let default_value = false; + } + } + + result + +} + +/// query flag disabled_rw +#[inline(always)] +pub fn disabled_rw() -> bool { + PROVIDER.disabled_rw() +} + +/// query flag disabled_rw_exported +#[inline(always)] +pub fn disabled_rw_exported() -> bool { + PROVIDER.disabled_rw_exported() +} + +/// query flag disabled_rw_in_other_namespace +#[inline(always)] +pub fn disabled_rw_in_other_namespace() -> bool { + PROVIDER.disabled_rw_in_other_namespace() +} + +/// query flag enabled_fixed_ro +#[inline(always)] +pub fn enabled_fixed_ro() -> bool { + + + let result = true; + if !Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + return result; + } + + // This will be called multiple times. Subsequent calls after the first + // are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info), + ); + + unsafe { + let package_map = match get_mapped_storage_file("system", StorageFileType::PackageMap) { + Ok(file) => file, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro': {err}"); + return result; + } + }; + + let package_read_context = match get_package_read_context(&package_map, "com.android.aconfig.test") { + Ok(Some(context)) => context, + Ok(None) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro': did not get context"); + return result; + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro': {err}"); + return result; + } + }; + let flag_val_map = match get_mapped_storage_file("system", StorageFileType::FlagVal) { + Ok(val_map) => val_map, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro': {err}"); + return result; + } + }; + let value = match get_boolean_flag_value(&flag_val_map, 4 + package_read_context.boolean_start_index) { + Ok(val) => val, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro': {err}"); + return result; + } + }; + + if result != value { + log!(Level::Error, "AconfigTestMission1: error: flag mismatch for 'enabled_fixed_ro'. Legacy storage was {result}, new storage was {value}"); + } else { + let default_value = true; + } + } + + result + +} + +/// query flag enabled_fixed_ro_exported +#[inline(always)] +pub fn enabled_fixed_ro_exported() -> bool { + + + let result = true; + if !Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + return result; + } + + // This will be called multiple times. Subsequent calls after the first + // are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info), + ); + + unsafe { + let package_map = match get_mapped_storage_file("system", StorageFileType::PackageMap) { + Ok(file) => file, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro_exported': {err}"); + return result; + } + }; + + let package_read_context = match get_package_read_context(&package_map, "com.android.aconfig.test") { + Ok(Some(context)) => context, + Ok(None) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro_exported': did not get context"); + return result; + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro_exported': {err}"); + return result; + } + }; + let flag_val_map = match get_mapped_storage_file("system", StorageFileType::FlagVal) { + Ok(val_map) => val_map, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro_exported': {err}"); + return result; + } + }; + let value = match get_boolean_flag_value(&flag_val_map, 5 + package_read_context.boolean_start_index) { + Ok(val) => val, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_fixed_ro_exported': {err}"); + return result; + } + }; + + if result != value { + log!(Level::Error, "AconfigTestMission1: error: flag mismatch for 'enabled_fixed_ro_exported'. Legacy storage was {result}, new storage was {value}"); + } else { + let default_value = true; + } + } + + result + +} + +/// query flag enabled_ro +#[inline(always)] +pub fn enabled_ro() -> bool { + + + let result = true; + if !Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + return result; + } + + // This will be called multiple times. Subsequent calls after the first + // are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info), + ); + + unsafe { + let package_map = match get_mapped_storage_file("system", StorageFileType::PackageMap) { + Ok(file) => file, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro': {err}"); + return result; + } + }; + + let package_read_context = match get_package_read_context(&package_map, "com.android.aconfig.test") { + Ok(Some(context)) => context, + Ok(None) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro': did not get context"); + return result; + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro': {err}"); + return result; + } + }; + let flag_val_map = match get_mapped_storage_file("system", StorageFileType::FlagVal) { + Ok(val_map) => val_map, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro': {err}"); + return result; + } + }; + let value = match get_boolean_flag_value(&flag_val_map, 6 + package_read_context.boolean_start_index) { + Ok(val) => val, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro': {err}"); + return result; + } + }; + + if result != value { + log!(Level::Error, "AconfigTestMission1: error: flag mismatch for 'enabled_ro'. Legacy storage was {result}, new storage was {value}"); + } else { + let default_value = true; + } + } + + result + +} + +/// query flag enabled_ro_exported +#[inline(always)] +pub fn enabled_ro_exported() -> bool { + + + let result = true; + if !Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() { + return result; + } + + // This will be called multiple times. Subsequent calls after the first + // are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info), + ); + + unsafe { + let package_map = match get_mapped_storage_file("system", StorageFileType::PackageMap) { + Ok(file) => file, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro_exported': {err}"); + return result; + } + }; + + let package_read_context = match get_package_read_context(&package_map, "com.android.aconfig.test") { + Ok(Some(context)) => context, + Ok(None) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro_exported': did not get context"); + return result; + }, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro_exported': {err}"); + return result; + } + }; + let flag_val_map = match get_mapped_storage_file("system", StorageFileType::FlagVal) { + Ok(val_map) => val_map, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro_exported': {err}"); + return result; + } + }; + let value = match get_boolean_flag_value(&flag_val_map, 7 + package_read_context.boolean_start_index) { + Ok(val) => val, + Err(err) => { + log!(Level::Error, "AconfigTestMission1: error: failed to read flag 'enabled_ro_exported': {err}"); + return result; + } + }; + + if result != value { + log!(Level::Error, "AconfigTestMission1: error: flag mismatch for 'enabled_ro_exported'. Legacy storage was {result}, new storage was {value}"); + } else { + let default_value = true; + } + } + + result + +} + /// query flag enabled_rw #[inline(always)] pub fn enabled_rw() -> bool { @@ -492,6 +1204,14 @@ pub fn reset_flags() { const EXPORTED_EXPECTED: &str = r#" //! codegenerated rust flag lib +use aconfig_storage_read_api::{Mmap, AconfigStorageError, StorageFileType, PackageReadContext, get_mapped_storage_file, get_boolean_flag_value, get_package_read_context}; +use std::path::Path; +use std::io::Write; +use log::{log, LevelFilter, Level}; + +static STORAGE_MIGRATION_MARKER_FILE: &str = + "/metadata/aconfig_test_missions/mission_1"; +static MIGRATION_LOG_TAG: &str = "AconfigTestMission1"; /// flag provider pub struct FlagProvider; @@ -558,6 +1278,14 @@ pub fn enabled_ro_exported() -> bool { const FORCE_READ_ONLY_EXPECTED: &str = r#" //! codegenerated rust flag lib +use aconfig_storage_read_api::{Mmap, AconfigStorageError, StorageFileType, PackageReadContext, get_mapped_storage_file, get_boolean_flag_value, get_package_read_context}; +use std::path::Path; +use std::io::Write; +use log::{log, LevelFilter, Level}; + +static STORAGE_MIGRATION_MARKER_FILE: &str = + "/metadata/aconfig_test_missions/mission_1"; +static MIGRATION_LOG_TAG: &str = "AconfigTestMission1"; /// flag provider pub struct FlagProvider; @@ -633,24 +1361,27 @@ pub fn enabled_rw() -> bool { true } "#; + use crate::commands::assign_flag_ids; - fn test_generate_rust_code(mode: CodegenMode) { + fn test_generate_rust_code(mode: CodegenMode, allow_instrumentation: bool, expected: &str) { let parsed_flags = crate::test::parse_test_flags(); let modified_parsed_flags = crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap(); - let generated = - generate_rust_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode) - .unwrap(); + let flag_ids = + assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap(); + let generated = generate_rust_code( + crate::test::TEST_PACKAGE, + flag_ids, + modified_parsed_flags.into_iter(), + mode, + allow_instrumentation, + ) + .unwrap(); assert_eq!("src/lib.rs", format!("{}", generated.path.display())); assert_eq!( None, crate::test::first_significant_code_diff( - match mode { - CodegenMode::Production => PROD_EXPECTED, - CodegenMode::Test => TEST_EXPECTED, - CodegenMode::Exported => EXPORTED_EXPECTED, - CodegenMode::ForceReadOnly => FORCE_READ_ONLY_EXPECTED, - }, + expected, &String::from_utf8(generated.contents).unwrap() ) ); @@ -658,21 +1389,26 @@ pub fn enabled_rw() -> bool { #[test] fn test_generate_rust_code_for_prod() { - test_generate_rust_code(CodegenMode::Production); + test_generate_rust_code(CodegenMode::Production, false, PROD_EXPECTED); + } + + #[test] + fn test_generate_rust_code_for_prod_instrumented() { + test_generate_rust_code(CodegenMode::Production, true, PROD_INSTRUMENTED_EXPECTED); } #[test] fn test_generate_rust_code_for_test() { - test_generate_rust_code(CodegenMode::Test); + test_generate_rust_code(CodegenMode::Test, false, TEST_EXPECTED); } #[test] fn test_generate_rust_code_for_exported() { - test_generate_rust_code(CodegenMode::Exported); + test_generate_rust_code(CodegenMode::Exported, false, EXPORTED_EXPECTED); } #[test] fn test_generate_rust_code_for_force_read_only() { - test_generate_rust_code(CodegenMode::ForceReadOnly); + test_generate_rust_code(CodegenMode::ForceReadOnly, false, FORCE_READ_ONLY_EXPECTED); } } diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs index 6d1c2f5cfc..59f06627ee 100644 --- a/tools/aconfig/aconfig/src/commands.rs +++ b/tools/aconfig/aconfig/src/commands.rs @@ -238,7 +238,11 @@ pub fn create_cpp_lib( ) } -pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result { +pub fn create_rust_lib( + mut input: Input, + codegen_mode: CodegenMode, + allow_instrumentation: bool, +) -> Result { // // TODO(327420679): Enable export mode for native flag library ensure!( codegen_mode != CodegenMode::Exported, @@ -250,8 +254,14 @@ pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result Command { Command::new("create-rust-lib") .arg(Arg::new("cache").long("cache").required(true)) .arg(Arg::new("out").long("out").required(true)) + .arg( + Arg::new("allow-instrumentation") + .long("allow-instrumentation") + .value_parser(clap::value_parser!(bool)) + .default_value("false"), + ) .arg( Arg::new("mode") .long("mode") @@ -267,8 +273,10 @@ fn main() -> Result<()> { Some(("create-rust-lib", sub_matches)) => { let cache = open_single_file(sub_matches, "cache")?; let mode = get_required_arg::(sub_matches, "mode")?; - let generated_file = - commands::create_rust_lib(cache, *mode).context("failed to create rust lib")?; + let allow_instrumentation = + get_required_arg::(sub_matches, "allow-instrumentation")?; + let generated_file = commands::create_rust_lib(cache, *mode, *allow_instrumentation) + .context("failed to create rust lib")?; let dir = PathBuf::from(get_required_arg::(sub_matches, "out")?); write_output_file_realtive_to_dir(&dir, &generated_file)?; } diff --git a/tools/aconfig/aconfig/templates/cpp_source_file.template b/tools/aconfig/aconfig/templates/cpp_source_file.template index 38dda7df31..b6012e7a0d 100644 --- a/tools/aconfig/aconfig/templates/cpp_source_file.template +++ b/tools/aconfig/aconfig/templates/cpp_source_file.template @@ -1,13 +1,13 @@ #include "{header}.h" {{ if allow_instrumentation }} +{{ if readwrite- }} #include #include "aconfig_storage/aconfig_storage_read_api.hpp" #include - -#define ALOGI(msg, ...) \ - __android_log_print(ANDROID_LOG_INFO, "AconfigTestMission1", (msg), __VA_ARGS__) - +#define LOG_TAG "aconfig_cpp_codegen" +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +{{ -endif }} {{ endif }} {{ if readwrite- }} @@ -66,8 +66,68 @@ namespace {cpp_namespace} \{ class flag_provider : public flag_provider_interface \{ public: - {{ -for item in class_elements }} + {{ if allow_instrumentation- }} + {{ if readwrite- }} + flag_provider() + {{ if readwrite- }} + : cache_({readwrite_count}, -1) + , boolean_start_index_() + {{ -else- }} + : boolean_start_index_() + {{ -endif }} + , flag_value_file_(nullptr) + , read_from_new_storage_(false) + , use_new_storage_value(false) \{ + struct stat buffer; + if (stat("/metadata/aconfig_test_missions/mission_1", &buffer) == 0) \{ + read_from_new_storage_ = true; + } else \{ + return; + } + + auto package_map_file = aconfig_storage::get_mapped_file( + "{container}", + aconfig_storage::StorageFileType::package_map); + if (!package_map_file.ok()) \{ + ALOGI("error: failed to get package map file: %s", package_map_file.error().c_str()); + return; + } + + auto context = aconfig_storage::get_package_read_context( + **package_map_file, "{package}"); + if (!context.ok()) \{ + ALOGI("error: failed to get package read context: %s", context.error().c_str()); + return; + } + + // cache package boolean flag start index + boolean_start_index_ = context->boolean_start_index; + + // unmap package map file and free memory + delete *package_map_file; + + auto flag_value_file = aconfig_storage::get_mapped_file( + "{container}", + aconfig_storage::StorageFileType::flag_val); + if (!flag_value_file.ok()) \{ + ALOGI("error: failed to get flag value file: %s", flag_value_file.error().c_str()); + return; + } + + // cache flag value file + flag_value_file_ = std::unique_ptr( + *flag_value_file); + + use_new_storage_value = server_configurable_flags::GetServerConfigurableFlag( + "aconfig_flags.core_experiments_team_internal", + "com.android.providers.settings.use_new_storage_value", + "false") == "true"; + } + {{ -endif }} + {{ -endif }} + + {{ -for item in class_elements }} virtual bool {item.flag_name}() override \{ {{ -if item.readwrite }} if (cache_[{item.readwrite_idx}] == -1) \{ @@ -76,6 +136,39 @@ namespace {cpp_namespace} \{ "{item.device_config_flag}", "{item.default_value}") == "true"; } + + + {{ if allow_instrumentation- }} + if (read_from_new_storage_) \{ + if (!flag_value_file_) \{ + ALOGI("error: failed to get flag {item.flag_name}: flag value file is null"); + return cache_[{item.readwrite_idx}]; + } + + auto value = aconfig_storage::get_boolean_flag_value( + *flag_value_file_, + boolean_start_index_ + {item.flag_offset}); + + if (!value.ok()) \{ + ALOGI("error: failed to read flag value: %s", value.error().c_str()); + return cache_[{item.readwrite_idx}]; + } + + bool expected_value = cache_[{item.readwrite_idx}]; + if (*value != expected_value) \{ + ALOGI("error: {item.flag_name} value mismatch, new storage value is %s, old storage value is %s", + *value ? "true" : "false", expected_value ? "true" : "false"); + } + + if (use_new_storage_value) \{ + return *value; + } else \{ + return expected_value; + } + } + {{ -endif }} + + return cache_[{item.readwrite_idx}]; {{ -else }} {{ -if item.is_fixed_read_only }} @@ -86,12 +179,21 @@ namespace {cpp_namespace} \{ {{ -endif }} } {{ -endfor }} + {{ if readwrite- }} private: std::vector cache_ = std::vector({readwrite_count}, -1); - {{ -endif }} - }; + {{ if allow_instrumentation- }} + uint32_t boolean_start_index_; + std::unique_ptr flag_value_file_; + + bool read_from_new_storage_; + bool use_new_storage_value; + {{ -endif }} + {{ -endif }} + + }; {{ -endif }} @@ -107,62 +209,6 @@ bool {header}_{item.flag_name}() \{ {{ -if item.readwrite }} return {cpp_namespace}::{item.flag_name}(); {{ -else }} - {{ if allow_instrumentation }} - auto result = - {{ if item.is_fixed_read_only }} - {package_macro}_{item.flag_macro} - {{ else }} - {item.default_value} - {{ endif }}; - - struct stat buffer; - if (stat("/metadata/aconfig_test_missions/mission_1", &buffer) != 0) \{ - return result; - } - - auto package_map_file = aconfig_storage::get_mapped_file( - "{item.container}", - aconfig_storage::StorageFileType::package_map); - if (!package_map_file.ok()) \{ - ALOGI("error: failed to get package map file: %s", package_map_file.error().c_str()); - return result; - } - - auto package_read_context = aconfig_storage::get_package_read_context( - **package_map_file, "{package}"); - if (!package_read_context.ok()) \{ - ALOGI("error: failed to get package read context: %s", package_map_file.error().c_str()); - return result; - } - - delete *package_map_file; - - auto flag_val_map = aconfig_storage::get_mapped_file( - "{item.container}", - aconfig_storage::StorageFileType::flag_val); - if (!flag_val_map.ok()) \{ - ALOGI("error: failed to get flag val map: %s", package_map_file.error().c_str()); - return result; - } - - auto value = aconfig_storage::get_boolean_flag_value( - **flag_val_map, - package_read_context->boolean_start_index + {item.flag_offset}); - if (!value.ok()) \{ - ALOGI("error: failed to get flag val: %s", package_map_file.error().c_str()); - return result; - } - - delete *flag_val_map; - - if (*value != result) \{ - ALOGI("error: new storage value '%d' does not match current value '%d'", *value, result); - } else \{ - ALOGI("success: new storage value was '%d, legacy storage was '%d'", *value, result); - } - - return result; - {{ else }} {{ -if item.is_fixed_read_only }} return {package_macro}_{item.flag_macro}; {{ -else }} @@ -170,7 +216,6 @@ bool {header}_{item.flag_name}() \{ {{ -endif }} {{ -endif }} {{ -endif }} - {{ -endif }} } {{ -if is_test_mode }} @@ -185,5 +230,3 @@ void {header}_reset_flags() \{ {cpp_namespace}::reset_flags(); } {{ -endif }} - - diff --git a/tools/aconfig/aconfig/templates/rust.template b/tools/aconfig/aconfig/templates/rust.template index f9a2829033..77a9984baa 100644 --- a/tools/aconfig/aconfig/templates/rust.template +++ b/tools/aconfig/aconfig/templates/rust.template @@ -1,17 +1,103 @@ //! codegenerated rust flag lib +use aconfig_storage_read_api::\{Mmap, AconfigStorageError, StorageFileType, PackageReadContext, get_mapped_storage_file, get_boolean_flag_value, get_package_read_context}; +use std::path::Path; +use std::io::Write; +use log::\{log, LevelFilter, Level}; + +static STORAGE_MIGRATION_MARKER_FILE: &str = + "/metadata/aconfig_test_missions/mission_1"; +static MIGRATION_LOG_TAG: &str = "AconfigTestMission1"; /// flag provider pub struct FlagProvider; {{ if has_readwrite- }} lazy_static::lazy_static! \{ + {{ if allow_instrumentation }} + static ref PACKAGE_OFFSET: Result, AconfigStorageError> = unsafe \{ + get_mapped_storage_file("{container}", StorageFileType::PackageMap) + .and_then(|package_map| get_package_read_context(&package_map, "{package}")) + .map(|context| context.map(|c| c.boolean_start_index)) + }; + + static ref FLAG_VAL_MAP: Result = unsafe \{ + get_mapped_storage_file("{container}", StorageFileType::FlagVal) + }; + {{ -endif }} + {{ -for flag in template_flags }} {{ -if flag.readwrite }} /// flag value cache for {flag.name} + {{ if allow_instrumentation }} + static ref CACHED_{flag.name}: bool = \{ + let result = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.{flag.device_config_namespace}", + "{flag.device_config_flag}", + "{flag.default_value}") == "true"; + + let use_new_storage_value = flags_rust::GetServerConfigurableFlag( + "aconfig_flags.core_experiments_team_internal", + "com.android.providers.settings.use_new_storage_value", + "false") == "true"; + + if Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() \{ + // This will be called multiple times. Subsequent calls after the first are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info)); + + let aconfig_storage_result = FLAG_VAL_MAP + .as_ref() + .map_err(|err| format!("failed to get flag val map: \{err}")) + .and_then(|flag_val_map| \{ + PACKAGE_OFFSET + .as_ref() + .map_err(|err| format!("failed to get package read offset: \{err}")) + .and_then(|package_offset| \{ + match package_offset \{ + Some(offset) => \{ + get_boolean_flag_value(&flag_val_map, offset + {flag.flag_offset}) + .map_err(|err| format!("failed to get flag: \{err}")) + }, + None => Err("no context found for package '{package}'".to_string()) + } + }) + }); + + match aconfig_storage_result \{ + Ok(storage_result) if storage_result == result => \{ + if use_new_storage_value \{ + return storage_result; + } else \{ + return result; + } + }, + Ok(storage_result) => \{ + log!(Level::Error, "AconfigTestMission1: error: mismatch for flag '{flag.name}'. Legacy storage was \{result}, new storage was \{storage_result}"); + if use_new_storage_value \{ + return storage_result; + } else \{ + return result; + } + }, + Err(err) => \{ + log!(Level::Error, "AconfigTestMission1: error: \{err}"); + if use_new_storage_value \{ + panic!("failed to read flag value: \{err}"); + } + } + } + } + + result + }; + {{ else }} static ref CACHED_{flag.name}: bool = flags_rust::GetServerConfigurableFlag( "aconfig_flags.{flag.device_config_namespace}", "{flag.device_config_flag}", "{flag.default_value}") == "true"; + {{ endif }} {{ -endif }} {{ -endfor }} } @@ -22,11 +108,11 @@ impl FlagProvider \{ {{ for flag in template_flags }} /// query flag {flag.name} pub fn {flag.name}(&self) -> bool \{ - {{ -if flag.readwrite }} + {{ -if flag.readwrite }} *CACHED_{flag.name} - {{ -else }} + {{ -else }} {flag.default_value} - {{ -endif }} + {{ -endif }} } {{ endfor }} @@ -43,7 +129,67 @@ pub fn {flag.name}() -> bool \{ PROVIDER.{flag.name}() {{ -else }} pub fn {flag.name}() -> bool \{ + {{ if not allow_instrumentation }} {flag.default_value} + {{ else }} + + let result = {flag.default_value}; + if !Path::new(STORAGE_MIGRATION_MARKER_FILE).exists() \{ + return result; + } + + // This will be called multiple times. Subsequent calls after the first + // are noops. + logger::init( + logger::Config::default() + .with_tag_on_device(MIGRATION_LOG_TAG) + .with_max_level(LevelFilter::Info), + ); + + unsafe \{ + let package_map = match get_mapped_storage_file("{flag.container}", StorageFileType::PackageMap) \{ + Ok(file) => file, + Err(err) => \{ + log!(Level::Error, "AconfigTestMission1: error: failed to read flag '{flag.name}': \{err}"); + return result; + } + }; + + let package_read_context = match get_package_read_context(&package_map, "{package}") \{ + Ok(Some(context)) => context, + Ok(None) => \{ + log!(Level::Error, "AconfigTestMission1: error: failed to read flag '{flag.name}': did not get context"); + return result; + }, + Err(err) => \{ + log!(Level::Error, "AconfigTestMission1: error: failed to read flag '{flag.name}': \{err}"); + return result; + } + }; + let flag_val_map = match get_mapped_storage_file("{flag.container}", StorageFileType::FlagVal) \{ + Ok(val_map) => val_map, + Err(err) => \{ + log!(Level::Error, "AconfigTestMission1: error: failed to read flag '{flag.name}': \{err}"); + return result; + } + }; + let value = match get_boolean_flag_value(&flag_val_map, {flag.flag_offset} + package_read_context.boolean_start_index) \{ + Ok(val) => val, + Err(err) => \{ + log!(Level::Error, "AconfigTestMission1: error: failed to read flag '{flag.name}': \{err}"); + return result; + } + }; + + if result != value \{ + log!(Level::Error, "AconfigTestMission1: error: flag mismatch for '{flag.name}'. Legacy storage was \{result}, new storage was \{value}"); + } else \{ + let default_value = {flag.default_value}; + } + } + + result + {{ endif }} {{ -endif }} } {{ endfor }} diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs index 61f9e96f84..d76cf3fe4e 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs @@ -53,7 +53,7 @@ use flag_value_query::find_boolean_flag_value; use package_table_query::find_package_read_context; use anyhow::anyhow; -use memmap2::Mmap; +pub use memmap2::Mmap; use std::fs::File; use std::io::Read; diff --git a/tools/aconfig/aflags/Android.bp b/tools/aconfig/aflags/Android.bp index 2a023792b6..c48585aed9 100644 --- a/tools/aconfig/aflags/Android.bp +++ b/tools/aconfig/aflags/Android.bp @@ -11,6 +11,7 @@ rust_defaults { rustlibs: [ "libaconfig_device_paths", "libaconfig_protos", + "libaconfigd_protos", "libaconfig_storage_read_api", "libaconfig_storage_file", "libanyhow", diff --git a/tools/aconfig/aflags/Cargo.toml b/tools/aconfig/aflags/Cargo.toml index eeae295316..7dc343668d 100644 --- a/tools/aconfig/aflags/Cargo.toml +++ b/tools/aconfig/aflags/Cargo.toml @@ -9,6 +9,7 @@ paste = "1.0.11" protobuf = "3.2.0" regex = "1.10.3" aconfig_protos = { path = "../aconfig_protos" } +aconfigd_protos = { version = "0.1.0", path = "../../../../../system/server_configurable_flags/aconfigd"} nix = { version = "0.28.0", features = ["user"] } aconfig_storage_file = { version = "0.1.0", path = "../aconfig_storage_file" } aconfig_storage_read_api = { version = "0.1.0", path = "../aconfig_storage_read_api" } diff --git a/tools/aconfig/aflags/src/aconfig_storage_source.rs b/tools/aconfig/aflags/src/aconfig_storage_source.rs index 0dfb95637e..a2c60128b5 100644 --- a/tools/aconfig/aflags/src/aconfig_storage_source.rs +++ b/tools/aconfig/aflags/src/aconfig_storage_source.rs @@ -1,136 +1,125 @@ -use crate::{Flag, FlagPermission, FlagSource, FlagValue, ValuePickedFrom}; -use anyhow::{anyhow, Result}; - -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; +use crate::{Flag, FlagSource}; +use crate::{FlagPermission, FlagValue, ValuePickedFrom}; +use aconfigd_protos::{ + ProtoFlagQueryReturnMessage, ProtoListStorageMessage, ProtoListStorageMessageMsg, + ProtoStorageRequestMessage, ProtoStorageRequestMessageMsg, ProtoStorageRequestMessages, + ProtoStorageReturnMessage, ProtoStorageReturnMessageMsg, ProtoStorageReturnMessages, +}; +use anyhow::anyhow; +use anyhow::Result; +use protobuf::Message; +use protobuf::SpecialFields; +use std::io::{Read, Write}; +use std::net::Shutdown; +use std::os::unix::net::UnixStream; pub struct AconfigStorageSource {} -use aconfig_storage_file::protos::ProtoStorageFileInfo; -use aconfig_storage_file::protos::ProtoStorageFiles; -use aconfig_storage_file::FlagValueAndInfoSummary; +fn convert(msg: ProtoFlagQueryReturnMessage) -> Result { + let (value, value_picked_from) = match ( + &msg.boot_flag_value, + msg.default_flag_value, + msg.local_flag_value, + msg.has_local_override, + ) { + (_, _, Some(local), Some(has_local)) if has_local => { + (FlagValue::try_from(local.as_str())?, ValuePickedFrom::Local) + } + (Some(boot), Some(default), _, _) => { + let value = FlagValue::try_from(boot.as_str())?; + if *boot == default { + (value, ValuePickedFrom::Default) + } else { + (value, ValuePickedFrom::Server) + } + } + _ => return Err(anyhow!("missing override")), + }; -static STORAGE_INFO_FILE_PATH: &str = "/metadata/aconfig/storage_records.pb"; + let staged_value = match (msg.boot_flag_value, msg.server_flag_value, msg.has_server_override) { + (Some(boot), Some(server), _) if boot == server => None, + (Some(boot), Some(server), Some(has_server)) if boot != server && has_server => { + Some(FlagValue::try_from(server.as_str())?) + } + _ => None, + }; -fn read_default_values(file_info: ProtoStorageFileInfo) -> Result> { - let package_map = - file_info.package_map.ok_or(anyhow!("storage file is missing package map"))?; - let flag_map = file_info.flag_map.ok_or(anyhow!("storage file is missing flag map"))?; - let flag_val = file_info.flag_val.ok_or(anyhow!("storage file is missing flag val"))?; + let permission = match msg.is_readwrite { + Some(is_readwrite) => { + if is_readwrite { + FlagPermission::ReadWrite + } else { + FlagPermission::ReadOnly + } + } + None => return Err(anyhow!("missing permission")), + }; - let mut result = HashMap::new(); - for listed_flag in aconfig_storage_file::list_flags(&package_map, &flag_map, &flag_val)? { - let value = FlagValue::try_from(listed_flag.flag_value.as_str())?; - result.insert(listed_flag.package_name + &listed_flag.flag_name, value); - } - Ok(result) + Ok(Flag { + name: msg.flag_name.ok_or(anyhow!("missing flag name"))?, + package: msg.package_name.ok_or(anyhow!("missing package name"))?, + value, + permission, + value_picked_from, + staged_value, + container: msg.container.ok_or(anyhow!("missing container"))?, + + // TODO: remove once DeviceConfig is not in the CLI. + namespace: "-".to_string(), + }) } -fn read_next_boot_values( - listed_flags: &[FlagValueAndInfoSummary], -) -> Result> { - let mut result = HashMap::new(); - for flag in listed_flags { - result.insert( - flag.package_name.clone() + &flag.flag_name, - FlagValue::try_from(flag.flag_value.as_str())?, - ); +fn read_from_socket() -> Result> { + let messages = ProtoStorageRequestMessages { + msgs: vec![ProtoStorageRequestMessage { + msg: Some(ProtoStorageRequestMessageMsg::ListStorageMessage(ProtoListStorageMessage { + msg: Some(ProtoListStorageMessageMsg::All(true)), + special_fields: SpecialFields::new(), + })), + special_fields: SpecialFields::new(), + }], + special_fields: SpecialFields::new(), + }; + + let mut socket = UnixStream::connect("/dev/socket/aconfigd")?; + + let message_buffer = messages.write_to_bytes()?; + let mut message_length_buffer: [u8; 4] = [0; 4]; + let message_size = &message_buffer.len(); + message_length_buffer[0] = (message_size >> 24) as u8; + message_length_buffer[1] = (message_size >> 16) as u8; + message_length_buffer[2] = (message_size >> 8) as u8; + message_length_buffer[3] = *message_size as u8; + socket.write_all(&message_length_buffer)?; + socket.write_all(&message_buffer)?; + socket.shutdown(Shutdown::Write)?; + + let mut response_length_buffer: [u8; 4] = [0; 4]; + socket.read_exact(&mut response_length_buffer)?; + let response_length = u32::from_be_bytes(response_length_buffer) as usize; + let mut response_buffer = vec![0; response_length]; + socket.read_exact(&mut response_buffer)?; + + let response: ProtoStorageReturnMessages = + protobuf::Message::parse_from_bytes(&response_buffer)?; + + match response.msgs.as_slice() { + [ProtoStorageReturnMessage { + msg: Some(ProtoStorageReturnMessageMsg::ListStorageMessage(list_storage_message)), + .. + }] => Ok(list_storage_message.flags.clone()), + _ => Err(anyhow!("unexpected response from aconfigd")), } - Ok(result) -} - -fn reconcile( - default_values: HashMap, - next_boot_values: HashMap, - flags_current_boot: &[FlagValueAndInfoSummary], - container: &str, -) -> Result> { - let mut result = Vec::new(); - for listed_flag in flags_current_boot { - let default_value = default_values - .get(&(listed_flag.package_name.clone() + &listed_flag.flag_name)) - .copied(); - - let name = listed_flag.flag_name.clone(); - let package = listed_flag.package_name.clone(); - let value = FlagValue::try_from(listed_flag.flag_value.as_str())?; - let container = container.to_string(); - let staged_value = next_boot_values - .get(&(listed_flag.package_name.clone() + &listed_flag.flag_name)) - .filter(|&v| value != *v) - .copied(); - let permission = if listed_flag.is_readwrite { - FlagPermission::ReadWrite - } else { - FlagPermission::ReadOnly - }; - let value_picked_from = if listed_flag.has_local_override { - ValuePickedFrom::Local - } else if Some(value) == default_value { - ValuePickedFrom::Default - } else { - ValuePickedFrom::Server - }; - - result.push(Flag { - name, - package, - value, - container, - staged_value, - permission, - value_picked_from, - - // TODO(b/324436145): delete namespace field once DeviceConfig isn't in CLI. - namespace: "-".to_string(), - }); - } - Ok(result) } impl FlagSource for AconfigStorageSource { fn list_flags() -> Result> { - let mut result = Vec::new(); - - let mut file = File::open(STORAGE_INFO_FILE_PATH)?; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - let storage_file_info: ProtoStorageFiles = protobuf::Message::parse_from_bytes(&bytes)?; - - for file_info in storage_file_info.files { - let default_values = read_default_values(file_info.clone())?; - - let container = - file_info.container.ok_or(anyhow!("storage file is missing container"))?; - let package_map = format!("/metadata/aconfig/maps/{container}.package.map"); - let flag_map = format!("/metadata/aconfig/maps/{container}.flag.map"); - let flag_info = format!("/metadata/aconfig/boot/{container}.info"); - - let flag_val_current_boot = format!("/metadata/aconfig/boot/{container}.val"); - let flag_val_next_boot = format!("/metadata/aconfig/flags/{container}.val"); - - let flags_next_boot = aconfig_storage_file::list_flags_with_info( - &package_map, - &flag_map, - &flag_val_next_boot, - &flag_info, - )?; - let flags_current_boot = aconfig_storage_file::list_flags_with_info( - &package_map, - &flag_map, - &flag_val_current_boot, - &flag_info, - )?; - - let next_boot_values = read_next_boot_values(&flags_next_boot)?; - let processed_flags = - reconcile(default_values, next_boot_values, &flags_current_boot, &container)?; - - result.extend(processed_flags); - } - - Ok(result) + read_from_socket() + .map(|query_messages| { + query_messages.iter().map(|message| convert(message.clone())).collect::>() + })? + .into_iter() + .collect() } fn override_flag(_namespace: &str, _qualified_name: &str, _value: &str) -> Result<()> { diff --git a/tools/aconfig/aflags/src/main.rs b/tools/aconfig/aflags/src/main.rs index 0a5c989ef2..d8912a946e 100644 --- a/tools/aconfig/aflags/src/main.rs +++ b/tools/aconfig/aflags/src/main.rs @@ -289,7 +289,7 @@ fn main() -> Result<()> { let output = match cli.command { Command::List { use_new_storage: true, container } => { list(FlagSourceType::AconfigStorage, container) - .map_err(|_| anyhow!("storage may not be enabled")) + .map_err(|err| anyhow!("storage may not be enabled: {err}")) .map(Some) } Command::List { use_new_storage: false, container } => {