Merge "Remove fs_get_stats." into main
diff --git a/cogsetup.sh b/cogsetup.sh
index 6439af0..44538f2 100644
--- a/cogsetup.sh
+++ b/cogsetup.sh
@@ -21,18 +21,21 @@
OUT_DIR="out"
fi
- if [[ -L "${OUT_DIR}" ]]; then
+ # getoutdir ensures paths are absolute. envsetup could be called from a
+ # directory other than the root of the source tree
+ local outdir=$(getoutdir)
+ if [[ -L "${outdir}" ]]; then
return
fi
- if [ -d "${OUT_DIR}" ]; then
- echo -e "\tOutput directory ${OUT_DIR} cannot be present in a Cog workspace."
- echo -e "\tDelete \"${OUT_DIR}\" or create a symlink from \"${OUT_DIR}\" to a directory outside your workspace."
+ if [ -d "${outdir}" ]; then
+ echo -e "\tOutput directory ${outdir} cannot be present in a Cog workspace."
+ echo -e "\tDelete \"${outdir}\" or create a symlink from \"${outdir}\" to a directory outside your workspace."
return 1
fi
DEFAULT_OUTPUT_DIR="${HOME}/.cog/android-build-out"
mkdir -p ${DEFAULT_OUTPUT_DIR}
- ln -s ${DEFAULT_OUTPUT_DIR} `pwd`/out
+ ln -s ${DEFAULT_OUTPUT_DIR} ${outdir}
}
# This function sets up the build environment to be appropriate for Cog.
@@ -63,4 +66,4 @@
echo -e "\e[01;31mERROR:\e[0m This script must be run from a Cog workspace."
fi
-_setup_cog_env
\ No newline at end of file
+_setup_cog_env
diff --git a/core/Makefile b/core/Makefile
index 0215fda..93f88c0 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -465,9 +465,7 @@
)
$(if $(1),\
cp $$(PRIVATE_MODULES) $$(PRIVATE_MODULE_DIR)/; \
- for MODULE in $$(PRIVATE_LOAD_MODULES); do \
- basename $$$$MODULE >> $$(PRIVATE_LOAD_FILE); \
- done; \
+ if [ -n "$$(PRIVATE_LOAD_MODULES)" ]; then basename -a $$(PRIVATE_LOAD_MODULES); fi > $$(PRIVATE_LOAD_FILE); \
)
# The ln -sf + find -delete sequence is to remove any modules in
# PRIVATE_EXTRA_MODULES which have same basename as MODULES in PRIVATE_MODULES
@@ -5063,9 +5061,12 @@
check_vintf_all_deps :=
# -----------------------------------------------------------------
-# Activate vendor APEXes for checkvintf
+# Activate APEXes for checkvintf
apex_dirs := \
+ $(TARGET_OUT)/apex/% \
+ $(TARGET_OUT_PRODUCT)/apex/% \
+ $(TARGET_OUT_SYSTEM_EXT)/apex/% \
$(TARGET_OUT_VENDOR)/apex/% \
apex_files := $(sort $(filter $(apex_dirs), $(INTERNAL_ALLIMAGES_FILES)))
@@ -5081,7 +5082,10 @@
@echo $(PRIVATE_APEX_FILES) > /dev/null
@rm -rf $(APEX_OUT)
@mkdir -p $(APEX_OUT)
- $< --vendor_path $(TARGET_OUT_VENDOR) \
+ $< --system_path $(TARGET_OUT) \
+ --system_ext_path $(TARGET_OUT_SYSTEM_EXT) \
+ --product_path $(TARGET_OUT_PRODUCT) \
+ --vendor_path $(TARGET_OUT_VENDOR) \
--apex_path $(APEX_OUT)
apex_files :=
@@ -5089,14 +5093,14 @@
# The build system only writes VINTF metadata to */etc/vintf paths. Legacy paths aren't needed here
# because they are only used for prebuilt images.
-# APEX files in /vendor/apex can have VINTF fragments as well.
+# APEX files in /$partition/apex can have VINTF fragments as well.
check_vintf_common_srcs_patterns := \
$(TARGET_OUT)/etc/vintf/% \
$(TARGET_OUT_VENDOR)/etc/vintf/% \
$(TARGET_OUT_ODM)/etc/vintf/% \
$(TARGET_OUT_PRODUCT)/etc/vintf/% \
$(TARGET_OUT_SYSTEM_EXT)/etc/vintf/% \
- $(TARGET_OUT_VENDOR)/apex/% \
+ $(apex_dirs)
check_vintf_common_srcs := $(sort $(filter $(check_vintf_common_srcs_patterns),$(INTERNAL_ALLIMAGES_FILES)))
check_vintf_common_srcs_patterns :=
@@ -5114,15 +5118,17 @@
# -- Check system and system_ext manifests / matrices including fragments (excluding other framework manifests / matrices, e.g. product);
ifdef BUILDING_SYSTEM_IMAGE
check_vintf_system_deps := $(filter $(TARGET_OUT)/etc/vintf/% \
- $(TARGET_OUT_SYSTEM_EXT)/etc/vintf/%, \
+ $(TARGET_OUT_SYSTEM_EXT)/etc/vintf/% \
+ $(TARGET_OUT)/apex/% \
+ $(TARGET_OUT_SYSTEM_EXT)/apex/%, \
$(check_vintf_common_srcs))
ifneq ($(check_vintf_system_deps),)
check_vintf_has_system := true
check_vintf_system_log := $(intermediates)/check_vintf_system.log
check_vintf_all_deps += $(check_vintf_system_log)
-$(check_vintf_system_log): $(HOST_OUT_EXECUTABLES)/checkvintf $(check_vintf_system_deps)
- @( $< --check-one --dirmap /system:$(TARGET_OUT) > $@ 2>&1 ) || ( cat $@ && exit 1 )
+$(check_vintf_system_log): $(HOST_OUT_EXECUTABLES)/checkvintf $(check_vintf_system_deps) $(APEX_INFO_FILE)
+ @( $< --check-one --dirmap /system:$(TARGET_OUT) --dirmap /apex:$(APEX_OUT) > $@ 2>&1 ) || ( cat $@ && exit 1 )
$(call declare-1p-target,$(check_vintf_system_log))
check_vintf_system_log :=
@@ -5131,10 +5137,11 @@
vintffm_log := $(intermediates)/vintffm.log
endif
check_vintf_all_deps += $(vintffm_log)
-$(vintffm_log): $(HOST_OUT_EXECUTABLES)/vintffm $(check_vintf_system_deps)
+$(vintffm_log): $(HOST_OUT_EXECUTABLES)/vintffm $(check_vintf_system_deps) $(APEX_INFO_FILE)
@( $< --check --dirmap /system:$(TARGET_OUT) \
--dirmap /system_ext:$(TARGET_OUT_SYSTEM_EXT) \
--dirmap /product:$(TARGET_OUT_PRODUCT) \
+ --dirmap /apex:$(APEX_OUT) \
$(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
$(call declare-1p-target,$(vintffm_log))
@@ -5924,6 +5931,9 @@
ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
$(hide) echo "allow_non_ab=true" >> $@
endif
+ifeq ($(BOARD_NON_AB_OTA_DISABLE_COMPRESSION),true)
+ $(hide) echo "board_non_ab_ota_disable_compression=true" >> $@
+endif
ifdef BOARD_PREBUILT_DTBOIMAGE
$(hide) echo "has_dtbo=true" >> $@
ifeq ($(BOARD_AVB_ENABLE),true)
@@ -6084,7 +6094,7 @@
# $1: root directory
# $2: add prefix
define fs_config
-(cd $(1); find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,$(2),' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) -R "$(2)"
+(cd $(1); find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,$(2),' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -R "$(2)"
endef
define filter-out-missing-vendor
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 9f43a3e..ac586c5 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -37,7 +37,12 @@
# Default behavior for the tree wrt building modules or using prebuilts. This
# can always be overridden by setting the environment variable
# MODULE_BUILD_FROM_SOURCE.
-BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := $(RELEASE_DEFAULT_MODULE_BUILD_FROM_SOURCE)
+# TODO(b/301454934): The value from build flag is set to empty when use `False`
+# The condition below can be removed after the issue get sorted.
+ifeq (,$(BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE))
+ BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := false
+endif
ifneq ($(SANITIZE_TARGET)$(EMMA_INSTRUMENT_FRAMEWORK),)
# Always use sources when building the framework with Java coverage or
@@ -46,6 +51,18 @@
BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
endif
+ifneq ($(CLANG_COVERAGE)$(NATIVE_COVERAGE_PATHS),)
+ # Always use sources when building with clang coverage and native coverage.
+ # It is possible that there are certain situations when building with coverage
+ # would work with prebuilts, e.g. when the coverage is not being applied to
+ # modules for which we provide prebuilts. Unfortunately, determining that
+ # would require embedding knowledge of which coverage paths affect which
+ # modules here. That would duplicate a lot of information, add yet another
+ # location module authors have to update and complicate the logic here.
+ # For nowe we will just always build from sources when doing coverage builds.
+ BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
# ART does not provide linux_bionic variants needed for products that
# set HOST_CROSS_OS=linux_bionic.
ifeq (linux_bionic,${HOST_CROSS_OS})
@@ -117,6 +134,7 @@
rkpd \
uwb \
wifi \
+ mediaprovider \
$(foreach m, $(INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES),\
$(if $(call soong_config_get,$(m)_module,source_build),,\
@@ -144,8 +162,8 @@
SYSTEMUI_OPTIMIZE_JAVA ?= true
$(call add_soong_config_var,ANDROID,SYSTEMUI_OPTIMIZE_JAVA)
-# Disable Compose in SystemUI by default.
-SYSTEMUI_USE_COMPOSE ?= false
+# Enable Compose in SystemUI by default.
+SYSTEMUI_USE_COMPOSE ?= true
$(call add_soong_config_var,ANDROID,SYSTEMUI_USE_COMPOSE)
ifdef PRODUCT_AVF_ENABLED
@@ -164,6 +182,8 @@
$(call add_soong_config_var_value,ANDROID,release_binder_death_recipient_weak_from_jni,$(RELEASE_BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI))
+$(call add_soong_config_var_value,ANDROID,release_selinux_data_data_ignore,$(RELEASE_SELINUX_DATA_DATA_IGNORE))
+
# Enable system_server optimizations by default unless explicitly set or if
# there may be dependent runtime jars.
# TODO(b/240588226): Remove the off-by-default exceptions after handling
diff --git a/core/art_config.mk b/core/art_config.mk
index 54bfd6b..47b4bcf 100644
--- a/core/art_config.mk
+++ b/core/art_config.mk
@@ -27,8 +27,11 @@
# soong variables indicate whether the prebuilt is enabled:
# - $(m)_module/source_build for art and TOGGLEABLE_PREBUILT_MODULES
# - ANDROID/module_build_from_source for other mainline modules
+# Note that RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST is the list of module names
+# and library names of jars that need to be removed. We have to keep separated list per
+# release config due to possibility of different prebuilt content.
APEX_BOOT_JARS_EXCLUDED :=
-$(foreach pair, $(PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY),\
+$(foreach pair, $(RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST),\
$(eval m := $(subst com.android.,,$(call word-colon,1,$(pair)))) \
$(if $(call soong_config_get,$(m)_module,source_build), \
$(if $(filter true,$(call soong_config_get,$(m)_module,source_build)),, \
diff --git a/core/binary.mk b/core/binary.mk
index 6dab49c..b17ab00 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -481,6 +481,34 @@
my_cflags += $(CLANG_EXTERNAL_CFLAGS)
endif
+# Extra cflags for projects under hardware/ directory.
+# This should match the definition of `thirdPartyDirPrefixExceptions`
+# in build/soong/android/paths.go.
+# Get the second element of LOCAL_PATH
+ifneq ($(filter hardware/%,$(LOCAL_PATH)),)
+ my_subdir := $(word 2,$(subst /,$(space),$(LOCAL_PATH)))
+ must_compile_hardware_subdirs := \
+ hardware/google/% \
+ hardware/interfaces/% \
+ hardware/libhardware/% \
+ hardware/libhardware_legacy/% \
+ hardware/ril/%
+ ifeq ($(filter $(must_compile_hardware_subdirs),$(my_subdir)),)
+ my_cflags += $(CLANG_EXTERNAL_CFLAGS)
+ endif
+endif
+
+# Extra cflags for projects under vendor/ directory.
+# This should match the definition of `thirdPartyDirPrefixExceptions`
+# in build/soong/android/paths.go.
+ifneq ($(filter vendor/%,$(LOCAL_PATH)),)
+ my_subdir := $(word 2,$(subst /,$(space),$(LOCAL_PATH)))
+ # Do not add the flags for any subdir that contains the string "google".
+ ifneq ($(findstring google,$(my_subdir)),)
+ my_cflags += $(CLANG_EXTERNAL_CFLAGS)
+ endif
+endif
+
# arch-specific static libraries go first so that generic ones can depend on them
my_static_libraries := $(LOCAL_STATIC_LIBRARIES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)) $(LOCAL_STATIC_LIBRARIES_$(my_32_64_bit_suffix)) $(my_static_libraries)
my_whole_static_libraries := $(LOCAL_WHOLE_STATIC_LIBRARIES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)) $(LOCAL_WHOLE_STATIC_LIBRARIES_$(my_32_64_bit_suffix)) $(my_whole_static_libraries)
diff --git a/core/board_config.mk b/core/board_config.mk
index 8c23f93..25e0643 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -287,6 +287,9 @@
include $(BUILD_SYSTEM)/board_config_wifi.mk
+# Set up soong config for "soong_config_value_variable".
+-include vendor/google/build/soong/soong_config_namespace/camera.mk
+
# Default *_CPU_VARIANT_RUNTIME to CPU_VARIANT if unspecified.
TARGET_CPU_VARIANT_RUNTIME := $(or $(TARGET_CPU_VARIANT_RUNTIME),$(TARGET_CPU_VARIANT))
TARGET_2ND_CPU_VARIANT_RUNTIME := $(or $(TARGET_2ND_CPU_VARIANT_RUNTIME),$(TARGET_2ND_CPU_VARIANT))
@@ -946,6 +949,11 @@
endif
endif
+# For Non A/B full OTA, disable brotli compression.
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
+ BOARD_NON_AB_OTA_DISABLE_COMPRESSION := true
+endif
+
# Quick check for building generic OTA packages. Currently it only supports A/B OTAs.
ifeq ($(PRODUCT_BUILD_GENERIC_OTA_PACKAGE),true)
ifneq ($(AB_OTA_UPDATER),true)
diff --git a/core/config.mk b/core/config.mk
index 1c4a10f..c9f752d 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -885,6 +885,7 @@
32.0 \
33.0 \
34.0 \
+ 202404 \
)
.KATI_READONLY := \
@@ -1273,7 +1274,7 @@
endif
ifeq (true,$(FULL_SYSTEM_OPTIMIZE_JAVA))
-ifeq (,$(SYSTEM_OPTIMIZE_JAVA))
+ifeq (false,$(SYSTEM_OPTIMIZE_JAVA))
$(error SYSTEM_OPTIMIZE_JAVA must be enabled when FULL_SYSTEM_OPTIMIZE_JAVA is enabled)
endif
endif
diff --git a/core/main.mk b/core/main.mk
index b4ca2a4..c1cafc0 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -284,6 +284,11 @@
ro.product.first_api_level=$(PRODUCT_SHIPPING_API_LEVEL)
endif
+ifdef PRODUCT_SHIPPING_VENDOR_API_LEVEL
+ADDITIONAL_VENDOR_PROPERTIES += \
+ ro.vendor.api_level=$(PRODUCT_SHIPPING_VENDOR_API_LEVEL)
+endif
+
ifneq ($(TARGET_BUILD_VARIANT),user)
ifdef PRODUCT_SET_DEBUGFS_RESTRICTIONS
ADDITIONAL_VENDOR_PROPERTIES += \
diff --git a/core/packaging/flags.mk b/core/packaging/flags.mk
index 62ef3df..500efdd 100644
--- a/core/packaging/flags.mk
+++ b/core/packaging/flags.mk
@@ -154,9 +154,9 @@
ifeq ($(RELEASE_CREATE_ACONFIG_STORAGE_FILE),true)
$(foreach partition, $(_FLAG_PARTITIONS), \
- $(eval aconfig_storage_package_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/package.map) \
- $(eval aconfig_storage_flag_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.map) \
- $(eval aconfig_storage_flag_val.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.val) \
+ $(eval aconfig_storage_package_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/aconfig/package.map) \
+ $(eval aconfig_storage_flag_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/aconfig/flag.map) \
+ $(eval aconfig_storage_flag_val.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/aconfig/flag.val) \
$(eval $(call generate-partition-aconfig-storage-file, \
$(TARGET_OUT_FLAGS)/$(partition)/package.map, \
$(TARGET_OUT_FLAGS)/$(partition)/flag.map, \
diff --git a/core/product.mk b/core/product.mk
index 60cab47..d64dde2 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -230,6 +230,9 @@
# The first API level this product shipped with
_product_single_value_vars += PRODUCT_SHIPPING_API_LEVEL
+# The first vendor API level this product shipped with
+_product_single_value_vars += PRODUCT_SHIPPING_VENDOR_API_LEVEL
+
_product_list_vars += VENDOR_PRODUCT_RESTRICT_VENDOR_FILES
_product_list_vars += VENDOR_EXCEPTION_MODULES
_product_list_vars += VENDOR_EXCEPTION_PATHS
diff --git a/core/product_config.mk b/core/product_config.mk
index 500735e..4525423 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -606,6 +606,15 @@
endif
endef
+ifndef PRODUCT_VIRTUAL_AB_COW_VERSION
+ PRODUCT_VIRTUAL_AB_COW_VERSION := 2
+ ifdef PRODUCT_SHIPPING_API_LEVEL
+ ifeq (true,$(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),34))
+ PRODUCT_VIRTUAL_AB_COW_VERSION := 3
+ endif
+ endif
+endif
+
# Copy and check the value of each PRODUCT_BUILD_*_IMAGE variable
$(foreach image, \
PVMFW \
diff --git a/core/release_config.mk b/core/release_config.mk
index 8d19bc7..a6152bd 100644
--- a/core/release_config.mk
+++ b/core/release_config.mk
@@ -41,7 +41,9 @@
# which has OWNERS control. If it isn't let others define their own.
# TODO: Remove wildcard for build/release one when all branch manifests
# have updated.
-config_map_files := $(wildcard build/release/release_config_map.mk) \
+config_map_files := $(wildcard build/trunk_release/release_config_map.mk) \
+ $(wildcard build/release/release_config_map.mk) \
+ $(wildcard vendor/google_shared/build/release/release_config_map.mk) \
$(if $(wildcard vendor/google/release/release_config_map.mk), \
vendor/google/release/release_config_map.mk, \
$(sort \
diff --git a/core/tasks/cts.mk b/core/tasks/cts.mk
index 91cb2c9..b9f0988 100644
--- a/core/tasks/cts.mk
+++ b/core/tasks/cts.mk
@@ -144,30 +144,30 @@
$(call generate-coverage-report-cts,"CTS System API Coverage Report - XML",\
$(PRIVATE_TEST_CASES),xml)
-$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(cts_verifier_apk)
+$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(verifier-dir), $(c))
$(cts-verifier-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-verifier-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-verifier-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
$(cts-verifier-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-verifier-coverage-report) : $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+$(cts-verifier-coverage-report) : $(cts_verifier_apk) $(verifier-zip) $(cts_api_coverage_dependencies) | $(ACP)
$(call generate-coverage-report-cts,"CTS Verifier API Coverage Report",\
$(PRIVATE_TEST_CASES),html)
-$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
+$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts) $(verifier-dir), $(c))
$(cts-combined-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-combined-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-combined-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
$(cts-combined-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-combined-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+$(cts-combined-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(verifier-zip) $(cts_api_coverage_dependencies) | $(ACP)
$(call generate-coverage-report-cts,"CTS Combined API Coverage Report",\
$(PRIVATE_TEST_CASES),html)
-$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
+$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts) $(verifier-dir), $(c))
$(cts-combined-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-combined-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-combined-xml-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
$(cts-combined-xml-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-combined-xml-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+$(cts-combined-xml-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(verifier-zip) $(cts_api_coverage_dependencies) | $(ACP)
$(call generate-coverage-report-cts,"CTS Combined API Coverage Report - XML",\
$(PRIVATE_TEST_CASES),xml)
@@ -236,3 +236,8 @@
cts_api_coverage_exe :=
cts_verifier_apk :=
android_cts_zip :=
+cts-dir :=
+verifier-dir-name :=
+verifier-dir :=
+verifier-zip-name :=
+verifier-zip :=
diff --git a/core/tasks/meta-lic.mk b/core/tasks/meta-lic.mk
new file mode 100644
index 0000000..0079714
--- /dev/null
+++ b/core/tasks/meta-lic.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Declare license metadata for non-module files released with products.
+
+# Moved here from frameworks/av/media/Android.mk
+$(eval $(call declare-1p-copy-files,frameworks/av/media/libeffects,audio_effects.conf))
+$(eval $(call declare-1p-copy-files,frameworks/av/media/libeffects,audio_effects.xml))
+$(eval $(call declare-1p-copy-files,frameworks/av/media/libstagefright,))
+
+# Moved here from frameworks/av/services/Android.mk
+$(eval $(call declare-1p-copy-files,frameworks/av/services/audiopolicy,))
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index b8aeb38..a2afd49 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -94,15 +94,16 @@
ExtShared \
flags_health_check \
framework-graphics \
+ framework-location \
framework-minus-apex \
framework-minus-apex-install-dependencies \
- framework-res \
framework-sysconfig.xml \
fsck.erofs \
fsck_msdos \
fsverity-release-cert-der \
fs_config_files_system \
fs_config_dirs_system \
+ gpu_counter_producer \
group_system \
gsid \
gsi_tool \
@@ -238,10 +239,12 @@
perfetto \
ping \
ping6 \
+ pintool \
platform.xml \
pm \
preinstalled-packages-asl-files.xml \
preinstalled-packages-platform.xml \
+ preinstalled-packages-strict-signature.xml \
printflags \
privapp-permissions-platform.xml \
prng_seeder \
@@ -262,6 +265,7 @@
services \
settings \
SettingsProvider \
+ sfdo \
sgdisk \
Shell \
shell_and_utilities_system \
@@ -284,7 +288,6 @@
uncrypt \
usbd \
vdc \
- viewcompiler \
voip-common \
vold \
watchdogd \
@@ -415,7 +418,6 @@
unwind_info \
unwind_reg_info \
unwind_symbols \
- viewcompiler \
tzdata_host \
tzdata_host_tzdata_apex \
tzlookup.xml_host_tzdata_apex \
@@ -440,6 +442,7 @@
adevice_fingerprint \
arping \
dmuserd \
+ evemu-record \
idlcli \
init-debug.rc \
iotop \
@@ -492,5 +495,8 @@
$(call inherit-product, $(SRC_TARGET_DIR)/product/runtime_libart.mk)
+# Use the configured release of sqlite
+$(call soong_config_set, libsqlite3, release_package_libsqlite3, $(RELEASE_PACKAGE_LIBSQLITE3))
+
# Use "image" APEXes always.
$(call inherit-product,$(SRC_TARGET_DIR)/product/updatable_apex.mk)
diff --git a/target/product/base_vendor.mk b/target/product/base_vendor.mk
index a0c5929..ec3de75 100644
--- a/target/product/base_vendor.mk
+++ b/target/product/base_vendor.mk
@@ -51,7 +51,6 @@
dumpsys_vendor \
fs_config_files_nonsystem \
fs_config_dirs_nonsystem \
- gpu_counter_producer \
gralloc.default \
group_odm \
group_vendor \
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index dca9baa..4a968d7 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -50,6 +50,7 @@
PRODUCT_BOOT_JARS += \
framework-minus-apex \
framework-graphics \
+ framework-location \
ext \
telephony-common \
voip-common \
@@ -104,13 +105,6 @@
com.android.nfcservices:framework-nfc
endif
-# TODO(b/308174306): Adjust this after multiple prebuilts version is supported.
-# APEX boot jars that are not in prebuilt apexes.
-# Keep the list sorted by module names and then library names.
-PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY := \
- com.android.mediaprovider:framework-pdf \
- com.android.mediaprovider:framework-pdf-v \
-
# List of system_server classpath jars delivered via apex.
# Keep the list sorted by module names and then library names.
# Note: For modules available in Q, DO NOT add new entries here.
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 80aecb7..f771916 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -24,7 +24,7 @@
VNDK-SP: android.hardware.common-V2-ndk.so
VNDK-SP: android.hardware.common.fmq-V1-ndk.so
VNDK-SP: android.hardware.graphics.allocator-V2-ndk.so
-VNDK-SP: android.hardware.graphics.common-V4-ndk.so
+VNDK-SP: android.hardware.graphics.common-V5-ndk.so
VNDK-SP: android.hardware.graphics.common@1.0.so
VNDK-SP: android.hardware.graphics.common@1.1.so
VNDK-SP: android.hardware.graphics.common@1.2.so
@@ -60,8 +60,8 @@
VNDK-SP: libutilscallstack.so
VNDK-SP: libz.so
VNDK-core: android.frameworks.cameraservice.common-V1-ndk.so
-VNDK-core: android.frameworks.cameraservice.device-V1-ndk.so
-VNDK-core: android.frameworks.cameraservice.service-V1-ndk.so
+VNDK-core: android.frameworks.cameraservice.device-V2-ndk.so
+VNDK-core: android.frameworks.cameraservice.service-V2-ndk.so
VNDK-core: android.hardware.audio.common@2.0.so
VNDK-core: android.hardware.configstore-utils.so
VNDK-core: android.hardware.configstore@1.0.so
@@ -138,6 +138,7 @@
VNDK-core: libxml2.so
VNDK-core: libyuv.so
VNDK-core: libziparchive.so
+VNDK-core: server_configurable_flags.so
VNDK-private: libblas.so
VNDK-private: libcompiler_rt.so
VNDK-private: libft2.so
@@ -208,3 +209,4 @@
VNDK-product: libyuv.so
VNDK-product: libz.so
VNDK-product: libziparchive.so
+VNDK-product: server_configurable_flags.so
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index a581324..2e37366 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -31,11 +31,7 @@
PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
system/etc/init/config \
system/product/% \
- system/system_ext/% \
- system/lib/vndk-29 \
- system/lib/vndk-sp-29 \
- system/lib64/vndk-29 \
- system/lib64/vndk-sp-29
+ system/system_ext/%
# GSI should always support up-to-date platform features.
# Keep this value at the latest API level to ensure latest build system
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index b5292d2..bf9aa41 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -74,6 +74,7 @@
VpnDialogs \
vr \
+PRODUCT_PACKAGES += $(RELEASE_PACKAGE_VIRTUAL_CAMERA)
PRODUCT_SYSTEM_SERVER_APPS += \
FusedLocation \
diff --git a/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml b/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml
index 1295e1c..d3e2808 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml
@@ -27,5 +27,6 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
</config>
diff --git a/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml b/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml
index e2482e1..ef8056f 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml
@@ -24,6 +24,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
</config>
diff --git a/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml b/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml
index 54add22..536c35b 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml
@@ -29,6 +29,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Camera (Camera2) -->
@@ -42,6 +43,7 @@
<install-in-user-type package="com.android.deskclock">
<install-in user-type="FULL" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Contacts -->
@@ -56,6 +58,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Search (QuickSearchBox) TODO(b/258055479) -->
@@ -64,6 +67,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- WallpaperCropper -->
diff --git a/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml b/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml
index cc1c135..67a2a01 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml
@@ -24,6 +24,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
</config>
diff --git a/target/product/virtual_ab_ota/android_t_baseline.mk b/target/product/virtual_ab_ota/android_t_baseline.mk
index af0f7a9..418aaa4 100644
--- a/target/product/virtual_ab_ota/android_t_baseline.mk
+++ b/target/product/virtual_ab_ota/android_t_baseline.mk
@@ -20,5 +20,3 @@
#
# All U+ launching devices should instead use vabc_features.mk.
$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/vabc_features.mk)
-
-PRODUCT_VIRTUAL_AB_COW_VERSION ?= 2
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 95f1215..6bd0d06 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -4,6 +4,8 @@
"aconfig",
"aconfig_protos",
"aconfig_storage_file",
+ "aconfig_storage_read_api",
+ "aconfig_storage_write_api",
"aflags",
"printflags"
]
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index e42b5d3..2f1694b 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -20,10 +20,11 @@
// aconfig C++ integration tests (test mode auto-generated code)
"name": "aconfig.test.cpp.test_mode"
},
- {
- // aconfig C++ integration tests (exported mode auto-generated code)
- "name": "aconfig.test.cpp.exported_mode"
- },
+ // TODO(327420679): Enable export mode for native flag library
+ // {
+ // // aconfig C++ integration tests (exported mode auto-generated code)
+ // "name": "aconfig.test.cpp.exported_mode"
+ // },
{
// aconfig Rust integration tests (production mode auto-generated code)
"name": "aconfig.prod_mode.test.rust"
@@ -32,10 +33,11 @@
// aconfig Rust integration tests (test mode auto-generated code)
"name": "aconfig.test_mode.test.rust"
},
- {
- // aconfig Rust integration tests (exported mode auto-generated code)
- "name": "aconfig.exported_mode.test.rust"
- },
+ // TODO(327420679): Enable export mode for native flag library
+ // {
+ // // aconfig Rust integration tests (exported mode auto-generated code)
+ // "name": "aconfig.exported_mode.test.rust"
+ // },
{
// printflags unit tests
"name": "printflags.test"
@@ -68,12 +70,24 @@
],
"postsubmit": [
{
+ // aconfig_storage_write_api unit tests
+ "name": "aconfig_storage_write_api.test"
+ },
+ {
+ // aconfig_storage_read_api unit tests
+ "name": "aconfig_storage_read_api.test"
+ },
+ {
+ // aconfig_storage write api rust integration tests
+ "name": "aconfig_storage_write_api.test.rust"
+ },
+ {
// aconfig_storage read api rust integration tests
- "name": "aconfig_storage.test.rust"
+ "name": "aconfig_storage_read_api.test.rust"
},
{
// aconfig_storage read api cpp integration tests
- "name": "aconfig_storage.test.cpp"
+ "name": "aconfig_storage_read_api.test.cpp"
},
{
// aflags CLI unit tests
diff --git a/tools/aconfig/aconfig/Android.bp b/tools/aconfig/aconfig/Android.bp
index 164bfe7..00a6fee 100644
--- a/tools/aconfig/aconfig/Android.bp
+++ b/tools/aconfig/aconfig/Android.bp
@@ -47,6 +47,7 @@
aconfig_declarations {
name: "aconfig.test.exported.flags",
package: "com.android.aconfig.test.exported",
+ exportable: true,
container: "system",
srcs: ["tests/test_exported.aconfig"],
}
@@ -143,12 +144,6 @@
}
cc_aconfig_library {
- name: "aconfig_test_cpp_library_exported_variant",
- aconfig_declarations: "aconfig.test.flags",
- mode: "exported",
-}
-
-cc_aconfig_library {
name: "aconfig_test_cpp_library_force_read_only_variant",
aconfig_declarations: "aconfig.test.flags",
mode: "force-read-only",
@@ -184,6 +179,14 @@
test_suites: ["general-tests"],
}
+// TODO(327420679): Enable export mode for native flag library
+/*
+cc_aconfig_library {
+ name: "aconfig_test_cpp_library_exported_variant",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "exported",
+}
+
cc_test {
name: "aconfig.test.cpp.exported_mode",
srcs: [
@@ -198,6 +201,7 @@
],
test_suites: ["general-tests"],
}
+*/
cc_test {
name: "aconfig.test.cpp.force_read_only_mode",
@@ -249,6 +253,8 @@
test_suites: ["general-tests"],
}
+// TODO(327420679): Enable export mode for native flag library
+/*
rust_aconfig_library {
name: "libaconfig_test_rust_library_with_exported_mode",
crate_name: "aconfig_test_rust_library",
@@ -266,6 +272,7 @@
],
test_suites: ["general-tests"],
}
+*/
rust_aconfig_library {
name: "libaconfig_test_rust_library_with_force_read_only_mode",
diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs
index a18f9a8..fab2fa3 100644
--- a/tools/aconfig/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/aconfig/src/codegen/java.rs
@@ -351,6 +351,10 @@
}
return false;
}
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ private boolean isOptimizationEnabled() {
+ return false;
+ }
private boolean getValue(String flagName) {
Boolean value = this.mFlagMap.get(flagName);
if (value == null) {
@@ -358,10 +362,6 @@
}
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() {
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RO, false),
@@ -558,8 +558,6 @@
let expect_flags_content = r#"
package com.android.aconfig.test;
- // TODO(b/303773055): Remove the annotation after access issue is resolved.
- import android.compat.annotation.UnsupportedAppUsage;
/** @hide */
public final class Flags {
/** @hide */
@@ -569,15 +567,12 @@
/** @hide */
public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
- @UnsupportedAppUsage
public static boolean disabledRwExported() {
return FEATURE_FLAGS.disabledRwExported();
}
- @UnsupportedAppUsage
public static boolean enabledFixedRoExported() {
return FEATURE_FLAGS.enabledFixedRoExported();
}
- @UnsupportedAppUsage
public static boolean enabledRoExported() {
return FEATURE_FLAGS.enabledRoExported();
}
@@ -587,23 +582,16 @@
let expect_feature_flags_content = r#"
package com.android.aconfig.test;
- // TODO(b/303773055): Remove the annotation after access issue is resolved.
- import android.compat.annotation.UnsupportedAppUsage;
/** @hide */
public interface FeatureFlags {
- @UnsupportedAppUsage
boolean disabledRwExported();
- @UnsupportedAppUsage
boolean enabledFixedRoExported();
- @UnsupportedAppUsage
boolean enabledRoExported();
}
"#;
let expect_feature_flags_impl_content = r#"
package com.android.aconfig.test;
- // TODO(b/303773055): Remove the annotation after access issue is resolved.
- import android.compat.annotation.UnsupportedAppUsage;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
/** @hide */
@@ -637,7 +625,6 @@
}
@Override
- @UnsupportedAppUsage
public boolean disabledRwExported() {
if (!aconfig_test_is_cached) {
load_overrides_aconfig_test();
@@ -646,7 +633,6 @@
}
@Override
- @UnsupportedAppUsage
public boolean enabledFixedRoExported() {
if (!aconfig_test_is_cached) {
load_overrides_aconfig_test();
@@ -655,7 +641,6 @@
}
@Override
- @UnsupportedAppUsage
public boolean enabledRoExported() {
if (!aconfig_test_is_cached) {
load_overrides_aconfig_test();
@@ -666,8 +651,6 @@
let expect_fake_feature_flags_impl_content = r#"
package com.android.aconfig.test;
- // TODO(b/303773055): Remove the annotation after access issue is resolved.
- import android.compat.annotation.UnsupportedAppUsage;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -679,17 +662,14 @@
resetAll();
}
@Override
- @UnsupportedAppUsage
public boolean disabledRwExported() {
return getValue(Flags.FLAG_DISABLED_RW_EXPORTED);
}
@Override
- @UnsupportedAppUsage
public boolean enabledFixedRoExported() {
return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED);
}
@Override
- @UnsupportedAppUsage
public boolean enabledRoExported() {
return getValue(Flags.FLAG_ENABLED_RO_EXPORTED);
}
@@ -704,13 +684,6 @@
entry.setValue(null);
}
}
- public boolean isFlagReadOnlyOptimized(String flagName) {
- if (mReadOnlyFlagsSet.contains(flagName) &&
- isOptimizationEnabled()) {
- return true;
- }
- return false;
- }
private boolean getValue(String flagName) {
Boolean value = this.mFlagMap.get(flagName);
if (value == null) {
@@ -718,10 +691,6 @@
}
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() {
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
@@ -1065,6 +1034,10 @@
}
return false;
}
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ private boolean isOptimizationEnabled() {
+ return false;
+ }
private boolean getValue(String flagName) {
Boolean value = this.mFlagMap.get(flagName);
if (value == null) {
@@ -1072,10 +1045,6 @@
}
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() {
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RO, false),
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index 59f349b..98dde44 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -31,7 +31,7 @@
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
ProtoParsedFlags, ProtoTracepoint,
};
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
pub struct Input {
pub source: String,
@@ -203,6 +203,11 @@
}
pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
+ // TODO(327420679): Enable export mode for native flag library
+ ensure!(
+ codegen_mode != CodegenMode::Exported,
+ "Exported mode for generated c/c++ flag library is disabled"
+ );
let parsed_flags = input.try_parse_flags()?;
let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
let Some(package) = find_unique_package(&modified_parsed_flags) else {
@@ -214,6 +219,11 @@
}
pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> {
+ // // TODO(327420679): Enable export mode for native flag library
+ ensure!(
+ codegen_mode != CodegenMode::Exported,
+ "Exported mode for generated rust flag library is disabled"
+ );
let parsed_flags = input.try_parse_flags()?;
let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
let Some(package) = find_unique_package(&modified_parsed_flags) else {
@@ -227,7 +237,7 @@
pub fn create_storage(
caches: Vec<Input>,
container: &str,
- file: &StorageFileSelection,
+ file: &StorageFileType,
) -> Result<Vec<u8>> {
let parsed_flags_vec: Vec<ProtoParsedFlags> = caches
.into_iter()
diff --git a/tools/aconfig/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs
index 5a4f23c..69f5458 100644
--- a/tools/aconfig/aconfig/src/main.rs
+++ b/tools/aconfig/aconfig/src/main.rs
@@ -29,7 +29,7 @@
mod dump;
mod storage;
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
use codegen::CodegenMode;
use dump::DumpFormat;
@@ -138,7 +138,7 @@
.arg(
Arg::new("file")
.long("file")
- .value_parser(|s: &str| StorageFileSelection::try_from(s)),
+ .value_parser(|s: &str| StorageFileType::try_from(s)),
)
.arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
.arg(Arg::new("out").long("out").required(true)),
@@ -285,7 +285,7 @@
write_output_to_file_or_stdout(path, &output)?;
}
Some(("create-storage", sub_matches)) => {
- let file = get_required_arg::<StorageFileSelection>(sub_matches, "file")
+ let file = get_required_arg::<StorageFileType>(sub_matches, "file")
.context("Invalid storage file selection")?;
let cache = open_zero_or_more_files(sub_matches, "cache")?;
let container = get_required_arg::<String>(sub_matches, "container")?;
diff --git a/tools/aconfig/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs
index 1381e89..b861c1f 100644
--- a/tools/aconfig/aconfig/src/storage/flag_table.rs
+++ b/tools/aconfig/aconfig/src/storage/flag_table.rs
@@ -17,7 +17,7 @@
use crate::commands::assign_flag_ids;
use crate::storage::FlagPackage;
use aconfig_storage_file::{
- get_table_size, FlagTable, FlagTableHeader, FlagTableNode, FILE_VERSION,
+ get_table_size, FlagTable, FlagTableHeader, FlagTableNode, FILE_VERSION, StorageFileType
};
use anyhow::{anyhow, Result};
@@ -25,6 +25,7 @@
FlagTableHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::FlagMap as u8,
file_size: 0,
num_flags,
bucket_offset: 0,
@@ -168,31 +169,32 @@
let expected_header = FlagTableHeader {
version: FILE_VERSION,
container: String::from("system"),
- file_size: 320,
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
+ bucket_offset: 31,
+ node_offset: 99,
};
assert_eq!(header, &expected_header);
let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
let expected_bucket: Vec<Option<u32>> = vec![
- Some(98),
- Some(124),
+ Some(99),
+ Some(125),
None,
None,
None,
- Some(177),
+ Some(178),
None,
- Some(203),
+ Some(204),
None,
- Some(261),
+ Some(262),
None,
None,
None,
None,
None,
- Some(293),
+ Some(294),
None,
];
assert_eq!(buckets, &expected_bucket);
@@ -201,10 +203,10 @@
assert_eq!(nodes.len(), 8);
assert_eq!(nodes[0], new_expected_node(0, "enabled_ro", 1, 1, None));
- assert_eq!(nodes[1], new_expected_node(0, "enabled_rw", 1, 2, Some(150)));
+ assert_eq!(nodes[1], new_expected_node(0, "enabled_rw", 1, 2, Some(151)));
assert_eq!(nodes[2], new_expected_node(1, "disabled_ro", 1, 0, None));
assert_eq!(nodes[3], new_expected_node(2, "enabled_ro", 1, 1, None));
- assert_eq!(nodes[4], new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(235)));
+ assert_eq!(nodes[4], new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(236)));
assert_eq!(nodes[5], new_expected_node(1, "enabled_ro", 1, 2, None));
assert_eq!(nodes[6], new_expected_node(2, "enabled_fixed_ro", 1, 0, None));
assert_eq!(nodes[7], new_expected_node(0, "disabled_rw", 1, 0, None));
diff --git a/tools/aconfig/aconfig/src/storage/flag_value.rs b/tools/aconfig/aconfig/src/storage/flag_value.rs
index 0d4b5b4..e40bbc1 100644
--- a/tools/aconfig/aconfig/src/storage/flag_value.rs
+++ b/tools/aconfig/aconfig/src/storage/flag_value.rs
@@ -17,13 +17,14 @@
use crate::commands::assign_flag_ids;
use crate::storage::FlagPackage;
use aconfig_protos::ProtoFlagState;
-use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION};
+use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION, StorageFileType};
use anyhow::{anyhow, Result};
fn new_header(container: &str, num_flags: u32) -> FlagValueHeader {
FlagValueHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::FlagVal as u8,
file_size: 0,
num_flags,
boolean_value_offset: 0,
@@ -79,9 +80,10 @@
let expected_header = FlagValueHeader {
version: FILE_VERSION,
container: String::from("system"),
- file_size: 34,
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
num_flags: 8,
- boolean_value_offset: 26,
+ boolean_value_offset: 27,
};
assert_eq!(header, &expected_header);
diff --git a/tools/aconfig/aconfig/src/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs
index 29eb9c8..c818d79 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -26,7 +26,7 @@
package_table::create_package_table,
};
use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
pub struct FlagPackage<'a> {
pub package_name: &'a str,
@@ -87,7 +87,7 @@
pub fn generate_storage_file<'a, I>(
container: &str,
parsed_flags_vec_iter: I,
- file: &StorageFileSelection,
+ file: &StorageFileType,
) -> Result<Vec<u8>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
@@ -95,15 +95,15 @@
let packages = group_flags_by_package(parsed_flags_vec_iter);
match file {
- StorageFileSelection::PackageMap => {
+ StorageFileType::PackageMap => {
let package_table = create_package_table(container, &packages)?;
Ok(package_table.as_bytes())
}
- StorageFileSelection::FlagMap => {
+ StorageFileType::FlagMap => {
let flag_table = create_flag_table(container, &packages)?;
Ok(flag_table.as_bytes())
}
- StorageFileSelection::FlagVal => {
+ StorageFileType::FlagVal => {
let flag_value = create_flag_value(container, &packages)?;
Ok(flag_value.as_bytes())
}
diff --git a/tools/aconfig/aconfig/src/storage/package_table.rs b/tools/aconfig/aconfig/src/storage/package_table.rs
index 4c08129..bc2da4d 100644
--- a/tools/aconfig/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/aconfig/src/storage/package_table.rs
@@ -17,7 +17,7 @@
use anyhow::Result;
use aconfig_storage_file::{
- get_table_size, PackageTable, PackageTableHeader, PackageTableNode, FILE_VERSION,
+ get_table_size, PackageTable, PackageTableHeader, PackageTableNode, FILE_VERSION, StorageFileType
};
use crate::storage::FlagPackage;
@@ -26,6 +26,7 @@
PackageTableHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::PackageMap as u8,
file_size: 0,
num_packages,
bucket_offset: 0,
@@ -123,15 +124,16 @@
let expected_header = PackageTableHeader {
version: FILE_VERSION,
container: String::from("system"),
- file_size: 208,
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
num_packages: 3,
- bucket_offset: 30,
- node_offset: 58,
+ bucket_offset: 31,
+ node_offset: 59,
};
assert_eq!(header, &expected_header);
let buckets: &Vec<Option<u32>> = &package_table.as_ref().unwrap().buckets;
- let expected: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+ let expected: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
assert_eq!(buckets, &expected);
let nodes: &Vec<PackageTableNode> = &package_table.as_ref().unwrap().nodes;
@@ -147,7 +149,7 @@
package_name: String::from("com.android.aconfig.storage.test_1"),
package_id: 0,
boolean_offset: 0,
- next_offset: Some(158),
+ next_offset: Some(159),
};
assert_eq!(nodes[1], second_node_expected);
let third_node_expected = PackageTableNode {
diff --git a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template
index 28dddd8..177e711 100644
--- a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template
+++ b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template
@@ -1,7 +1,8 @@
package {package_name};
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-
+{{ -endif }}
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -16,7 +17,7 @@
{{ for item in flag_elements}}
@Override
- @UnsupportedAppUsage
+{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }}
public boolean {item.method_name}() \{
return getValue(Flags.FLAG_{item.flag_name_constant_suffix});
}
@@ -33,7 +34,7 @@
entry.setValue(null);
}
}
-
+{{ if not library_exported }}
public boolean isFlagReadOnlyOptimized(String flagName) \{
if (mReadOnlyFlagsSet.contains(flagName) &&
isOptimizationEnabled()) \{
@@ -42,6 +43,11 @@
return false;
}
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ private boolean isOptimizationEnabled() \{
+ return false;
+ }
+{{ -endif }}
private boolean getValue(String flagName) \{
Boolean value = this.mFlagMap.get(flagName);
if (value == null) \{
@@ -50,10 +56,6 @@
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() \{
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
diff --git a/tools/aconfig/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/aconfig/templates/FeatureFlags.java.template
index 5e67b13..13edcb4 100644
--- a/tools/aconfig/aconfig/templates/FeatureFlags.java.template
+++ b/tools/aconfig/aconfig/templates/FeatureFlags.java.template
@@ -1,23 +1,21 @@
package {package_name};
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-
+{{ -endif }}
/** @hide */
public interface FeatureFlags \{
{{ for item in flag_elements }}
-{{ -if library_exported }}
- @UnsupportedAppUsage
- boolean {item.method_name}();
-{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
{{ -else }}
@com.android.aconfig.annotations.AssumeFalseForR8
{{ -endif- }}
-{{ endif }}
+{{ -endif }}
+{{ -if not library_exported }}
@UnsupportedAppUsage
+{{ -endif }}
boolean {item.method_name}();
-{{ endif }}
{{ -endfor }}
-}
+}
\ No newline at end of file
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
index 28baa41..12b2fc1 100644
--- a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,6 +1,8 @@
package {package_name};
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
+{{ -endif }}
{{ -if not is_test_mode }}
{{ -if runtime_lookup_required }}
import android.provider.DeviceConfig;
@@ -14,12 +16,8 @@
{{ -endfor- }}
{{ for flag in flag_elements }}
-{{ -if library_exported }}
- private static boolean {flag.method_name} = false;
-{{ -else }}
{{- if flag.is_read_write }}
private static boolean {flag.method_name} = {flag.default_value};
-{{- endif- }}
{{ -endif }}
{{ -endfor }}
{{ for namespace_with_flags in namespace_flags }}
@@ -27,15 +25,10 @@
try \{
Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}");
{{ -for flag in namespace_with_flags.flags }}
-{{ -if library_exported }}
- {flag.method_name} =
- properties.getBoolean("{flag.device_config_flag}", false);
-{{ -else }}
{{ -if flag.is_read_write }}
{flag.method_name} =
properties.getBoolean("{flag.device_config_flag}", {flag.default_value});
{{ -endif }}
-{{ -endif }}
{{ -endfor }}
} catch (NullPointerException e) \{
throw new RuntimeException(
@@ -53,14 +46,10 @@
{{ -endif }}{#- end of runtime_lookup_required #}
{{ -for flag in flag_elements }}
@Override
+{{ -if not library_exported }}
@UnsupportedAppUsage
+{{ -endif }}
public boolean {flag.method_name}() \{
-{{ -if library_exported }}
- if (!{flag.device_config_namespace}_is_cached) \{
- load_overrides_{flag.device_config_namespace}();
- }
- return {flag.method_name};
-{{ -else }}
{{ -if flag.is_read_write }}
if (!{flag.device_config_namespace}_is_cached) \{
load_overrides_{flag.device_config_namespace}();
@@ -68,7 +57,6 @@
return {flag.method_name};
{{ -else }}
return {flag.default_value};
-{{ -endif- }}
{{ -endif }}
}
{{ endfor }}
@@ -79,7 +67,9 @@
public final class FeatureFlagsImpl implements FeatureFlags \{
{{ for flag in flag_elements }}
@Override
+{{ -if not library_exported }}
@UnsupportedAppUsage
+{{ -endif }}
public boolean {flag.method_name}() \{
throw new UnsupportedOperationException(
"Method is not implemented.");
diff --git a/tools/aconfig/aconfig/templates/Flags.java.template b/tools/aconfig/aconfig/templates/Flags.java.template
index 34b8189..e105991 100644
--- a/tools/aconfig/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/aconfig/templates/Flags.java.template
@@ -1,8 +1,8 @@
package {package_name};
-
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-
+{{ -endif }}
/** @hide */
public final class Flags \{
{{ -for item in flag_elements}}
@@ -10,12 +10,6 @@
public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
{{- endfor }}
{{ -for item in flag_elements}}
-{{ if library_exported }}
- @UnsupportedAppUsage
- public static boolean {item.method_name}() \{
- return FEATURE_FLAGS.{item.method_name}();
- }
-{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
@@ -23,11 +17,12 @@
@com.android.aconfig.annotations.AssumeFalseForR8
{{ -endif }}
{{ -endif }}
+{{ -if not library_exported }}
@UnsupportedAppUsage
+{{ -endif }}
public static boolean {item.method_name}() \{
return FEATURE_FLAGS.{item.method_name}();
}
-{{ -endif }}
{{ -endfor }}
{{ -if is_test_mode }}
public static void setFeatureFlags(FeatureFlags featureFlags) \{
diff --git a/tools/aconfig/aconfig_protos/protos/aconfig.proto b/tools/aconfig/aconfig_protos/protos/aconfig.proto
index 8833722..9d1b8cb 100644
--- a/tools/aconfig/aconfig_protos/protos/aconfig.proto
+++ b/tools/aconfig/aconfig_protos/protos/aconfig.proto
@@ -22,16 +22,38 @@
// This protobuf file defines messages used to represent and manage flags in the "aconfig" system
// The following format requirements apply across various message fields:
-// # name: a lowercase string in snake_case format, no consecutive underscores, and no leading digit
-// For example adjust_rate is a valid name, while AdjustRate, adjust__rate, and
-// 2adjust_rate are invalid
//
-// # namespace: a lowercase string in snake_case format, no consecutive underscores, and no leading
-// digit. For example android_bar_system
+// # name: name of the flag
//
-// # package: lowercase strings in snake_case format, delimited by dots, no consecutive underscores
-// and no leading digit in each string. For example com.android.mypackage is a valid name
-// while com.android.myPackage, com.android.1mypackage are invalid
+// format: a lowercase string in snake_case format, no consecutive underscores, and no leading
+// digit. For example adjust_rate is a valid name, while AdjustRate, adjust__rate, and
+// adjust_rate are invalid
+//
+// # namespace: namespace the flag belongs to
+//
+// format: a lowercase string in snake_case format, no consecutive underscores, and no leading
+// digit. For example android_bar_system
+//
+// # package: package to which the flag belongs
+//
+// format: lowercase strings in snake_case format, delimited by dots, no consecutive underscores
+// and no leading digit in each string. For example com.android.mypackage is a valid name
+// while com.android.myPackage, com.android.1mypackage are invalid
+//
+// # container: container as software built in its entirety using the same build environment and
+// always installed as a single unit
+//
+// For example the following are all separate containers:
+// * the system partition
+// * the vendor partition
+// * apexes: each APEX is its own container
+// * APKs: for APKs which are released independently via Play, each APK is its own container.
+// If an APK is released as part of a Mainline module, or as part of the system partition
+// via OTA, then they are part of the apex or the system partition container
+//
+// format: lowercase strings in snake_case format, delimited by dots if multiple, no consecutive
+// underscores or leading digits in each string. The recommended container values are the
+// partition names or the module names
// messages used in both aconfig input and output
@@ -98,6 +120,7 @@
repeated flag_declaration flag = 2;
// Container the flag belongs to (optional)
+ // See # container for format detail
optional string container = 3;
};
@@ -160,6 +183,7 @@
optional bool is_exported = 10;
// Container the flag belongs to (optional)
+ // See # container for format detail
optional string container = 11;
// Additional information about the flag, including its purpose and form factors (optional)
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index 8922ba4..2a606bf 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -6,16 +6,13 @@
name: "aconfig_storage_file.defaults",
edition: "2021",
lints: "none",
- srcs: ["src/lib.rs"],
rustlibs: [
"libanyhow",
- "libaconfig_storage_protos",
- "libonce_cell",
- "libprotobuf",
- "libtempfile",
- "libmemmap2",
- "libcxx",
"libthiserror",
+ "libtempfile",
+ "libprotobuf",
+ "libclap",
+ "libaconfig_storage_protos",
],
}
@@ -24,38 +21,21 @@
crate_name: "aconfig_storage_file",
host_supported: true,
defaults: ["aconfig_storage_file.defaults"],
+ srcs: ["src/lib.rs"],
}
-genrule {
- name: "ro.package.map",
- out: ["tests/tmp.ro.package.map"],
- srcs: ["tests/package.map"],
- cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
-genrule {
- name: "ro.flag.map",
- out: ["tests/tmp.ro.flag.map"],
- srcs: ["tests/flag.map"],
- cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
-genrule {
- name: "ro.flag.val",
- out: ["tests/tmp.ro.flag.val"],
- srcs: ["tests/flag.val"],
- cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
+rust_binary_host {
+ name: "aconfig-storage",
+ defaults: ["aconfig_storage_file.defaults"],
+ srcs: ["src/main.rs"],
+ rustlibs: ["libaconfig_storage_file"],
}
rust_test_host {
name: "aconfig_storage_file.test",
test_suites: ["general-tests"],
defaults: ["aconfig_storage_file.defaults"],
- data: [
- "tests/package.map",
- "tests/flag.map",
- "tests/flag.val",
- ],
+ srcs: ["src/lib.rs"],
}
rust_protobuf {
@@ -79,38 +59,3 @@
],
host_supported: true,
}
-
-genrule {
- name: "libcxx_aconfig_storage_bridge_code",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) > $(out)",
- srcs: ["src/lib.rs"],
- out: ["aconfig_storage/lib.rs.cc"],
-}
-
-genrule {
- name: "libcxx_aconfig_storage_bridge_header",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) --header > $(out)",
- srcs: ["src/lib.rs"],
- out: ["aconfig_storage/lib.rs.h"],
-}
-
-rust_ffi_static {
- name: "libaconfig_storage_cxx_bridge",
- crate_name: "aconfig_storage_cxx_bridge",
- host_supported: true,
- defaults: ["aconfig_storage_file.defaults"],
-}
-
-cc_library_static {
- name: "libaconfig_storage_cc",
- srcs: ["aconfig_storage.cpp"],
- generated_headers: [
- "cxx-bridge-header",
- "libcxx_aconfig_storage_bridge_header"
- ],
- generated_sources: ["libcxx_aconfig_storage_bridge_code"],
- whole_static_libs: ["libaconfig_storage_cxx_bridge"],
- export_include_dirs: ["include"],
-}
diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml
index c4e2670..641f481 100644
--- a/tools/aconfig/aconfig_storage_file/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_file/Cargo.toml
@@ -9,12 +9,14 @@
[dependencies]
anyhow = "1.0.69"
-memmap2 = "0.8.0"
protobuf = "3.2.0"
-once_cell = "1.19.0"
tempfile = "3.9.0"
-cxx = "1.0"
thiserror = "1.0.56"
+clap = { version = "4.1.8", features = ["derive"] }
+
+[[bin]]
+name = "aconfig-storage"
+path = "src/main.rs"
[build-dependencies]
protobuf-codegen = "3.2.0"
diff --git a/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp b/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp
deleted file mode 100644
index ac64093..0000000
--- a/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-#include "aconfig_storage/aconfig_storage.hpp"
-
-#include "rust/cxx.h"
-#include "aconfig_storage/lib.rs.h"
-
-namespace aconfig_storage {
-
-/// Get package offset
-PackageOffsetQuery get_package_offset(
- std::string const& container,
- std::string const& package) {
- auto offset_cxx = get_package_offset_cxx(
- rust::Str(container.c_str()),
- rust::Str(package.c_str()));
- auto offset = PackageOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.package_exists = offset_cxx.package_exists;
- offset.package_id = offset_cxx.package_id;
- offset.boolean_offset = offset_cxx.boolean_offset;
- return offset;
-}
-
-/// Get flag offset
-FlagOffsetQuery get_flag_offset(
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name) {
- auto offset_cxx = get_flag_offset_cxx(
- rust::Str(container.c_str()),
- package_id,
- rust::Str(flag_name.c_str()));
- auto offset = FlagOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.flag_exists = offset_cxx.flag_exists;
- offset.flag_offset = offset_cxx.flag_offset;
- return offset;
-}
-
-/// Get boolean flag value
-BooleanFlagValueQuery get_boolean_flag_value(
- std::string const& container,
- uint32_t offset) {
- auto value_cxx = get_boolean_flag_value_cxx(
- rust::Str(container.c_str()),
- offset);
- auto value = BooleanFlagValueQuery();
- value.query_success = value_cxx.query_success;
- value.error_message = std::string(value_cxx.error_message.c_str());
- value.flag_value = value_cxx.flag_value;
- return value;
-}
-
-namespace test_only_api {
-PackageOffsetQuery get_package_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- std::string const& package) {
- auto offset_cxx = get_package_offset_cxx_impl(
- rust::Str(pb_file.c_str()),
- rust::Str(container.c_str()),
- rust::Str(package.c_str()));
- auto offset = PackageOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.package_exists = offset_cxx.package_exists;
- offset.package_id = offset_cxx.package_id;
- offset.boolean_offset = offset_cxx.boolean_offset;
- return offset;
-}
-
-FlagOffsetQuery get_flag_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name) {
- auto offset_cxx = get_flag_offset_cxx_impl(
- rust::Str(pb_file.c_str()),
- rust::Str(container.c_str()),
- package_id,
- rust::Str(flag_name.c_str()));
- auto offset = FlagOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.flag_exists = offset_cxx.flag_exists;
- offset.flag_offset = offset_cxx.flag_offset;
- return offset;
-}
-
-BooleanFlagValueQuery get_boolean_flag_value_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t offset) {
- auto value_cxx = get_boolean_flag_value_cxx_impl(
- rust::Str(pb_file.c_str()),
- rust::Str(container.c_str()),
- offset);
- auto value = BooleanFlagValueQuery();
- value.query_success = value_cxx.query_success;
- value.error_message = std::string(value_cxx.error_message.c_str());
- value.flag_value = value_cxx.flag_value;
- return value;
-}
-} // namespace test_only_api
-} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_file/build.rs b/tools/aconfig/aconfig_storage_file/build.rs
index 894b71c..1feeb60 100644
--- a/tools/aconfig/aconfig_storage_file/build.rs
+++ b/tools/aconfig/aconfig_storage_file/build.rs
@@ -14,7 +14,4 @@
.inputs(proto_files)
.cargo_out_dir("aconfig_storage_protos")
.run_from_script();
-
- let _ = cxx_build::bridge("src/lib.rs");
- println!("cargo:rerun-if-changed=src/lib.rs");
}
diff --git a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp b/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp
deleted file mode 100644
index 636fb7e..0000000
--- a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <string>
-
-namespace aconfig_storage {
-
-/// Package offset query result
-struct PackageOffsetQuery {
- bool query_success;
- std::string error_message;
- bool package_exists;
- uint32_t package_id;
- uint32_t boolean_offset;
-};
-
-/// Flag offset query result
-struct FlagOffsetQuery {
- bool query_success;
- std::string error_message;
- bool flag_exists;
- uint16_t flag_offset;
-};
-
-/// Boolean flag value query result
-struct BooleanFlagValueQuery {
- bool query_success;
- std::string error_message;
- bool flag_value;
-};
-
-/// Get package offset
-/// \input container: the flag container name
-/// \input package: the flag package name
-/// \returns a PackageOffsetQuery
-PackageOffsetQuery get_package_offset(
- std::string const& container,
- std::string const& package);
-
-/// Get flag offset
-/// \input container: the flag container name
-/// \input package_id: the flag package id obtained from package offset query
-/// \input flag_name: flag name
-/// \returns a FlagOffsetQuery
-FlagOffsetQuery get_flag_offset(
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name);
-
-/// Get boolean flag value
-/// \input container: the flag container name
-/// \input offset: the boolean flag value byte offset in the file
-/// \returns a BooleanFlagValueQuery
-BooleanFlagValueQuery get_boolean_flag_value(
- std::string const& container,
- uint32_t offset);
-
-/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE, TEST ONLY
-namespace test_only_api {
-PackageOffsetQuery get_package_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- std::string const& package);
-
-FlagOffsetQuery get_flag_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name);
-
-BooleanFlagValueQuery get_boolean_flag_value_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t offset);
-} // namespace test_only_api
-} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
index 108804e..f9b3158 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
@@ -17,22 +17,46 @@
//! flag table module defines the flag table file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError::{self, BytesParseFail, HigherStorageFileVersion};
-use crate::{get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes};
+use crate::{
+ get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes,
+ read_u8_from_bytes,
+};
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
-pub type FlagOffset = u16;
+use std::fmt;
/// Flag table header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagTableHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_flags: u32,
pub bucket_offset: u32,
pub node_offset: u32,
}
+/// Implement debug print trait for header
+impl fmt::Debug for FlagTableHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Flags: {}, Bucket Offset:{}, Node Offset: {}",
+ self.num_flags, self.bucket_offset, self.node_offset
+ )?;
+ Ok(())
+ }
+}
+
impl FlagTableHeader {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -41,6 +65,7 @@
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_type.to_le_bytes());
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_flags.to_le_bytes());
result.extend_from_slice(&self.bucket_offset.to_le_bytes());
@@ -51,19 +76,26 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let table = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_flags: read_u32_from_bytes(bytes, &mut head)?,
bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
node_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if table.file_type != StorageFileType::FlagMap as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag map"
+ )));
+ }
+ Ok(table)
}
}
/// Flag table node struct
-#[derive(PartialEq, Debug, Clone)]
+#[derive(PartialEq, Clone)]
pub struct FlagTableNode {
pub package_id: u32,
pub flag_name: String,
@@ -72,6 +104,18 @@
pub next_offset: Option<u32>,
}
+/// Implement debug print trait for node
+impl fmt::Debug for FlagTableNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Package Id: {}, Flag: {}, Type: {}, Offset: {}, Next: {:?}",
+ self.package_id, self.flag_name, self.flag_type, self.flag_id, self.next_offset
+ )?;
+ Ok(())
+ }
+}
+
impl FlagTableNode {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -109,13 +153,28 @@
}
}
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagTable {
pub header: FlagTableHeader,
pub buckets: Vec<Option<u32>>,
pub nodes: Vec<FlagTableNode>,
}
+/// Implement debug print trait for flag table
+impl fmt::Debug for FlagTable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Buckets:")?;
+ writeln!(f, "{:?}", self.buckets)?;
+ writeln!(f, "Nodes:")?;
+ for node in self.nodes.iter() {
+ write!(f, "{:?}", node)?;
+ }
+ Ok(())
+ }
+}
+
/// Flag table struct
impl FlagTable {
/// Serialize to bytes
@@ -147,108 +206,19 @@
Ok(node)
})
.collect::<Result<Vec<_>, AconfigStorageError>>()
- .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse flag table: {}", errmsg)))?;
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!("fail to parse flag table: {}", errmsg))
+ })?;
let table = Self { header, buckets, nodes };
Ok(table)
}
}
-/// Query flag within package offset
-pub fn find_flag_offset(
- buf: &[u8],
- package_id: u32,
- flag: &str,
-) -> Result<Option<FlagOffset>, AconfigStorageError> {
- let interpreted_header = FlagTableHeader::from_bytes(buf)?;
- if interpreted_header.version > crate::FILE_VERSION {
- return Err(HigherStorageFileVersion(anyhow!(
- "Cannot read storage file with a higher version of {} with lib version {}",
- interpreted_header.version,
- crate::FILE_VERSION
- )));
- }
-
- let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
- let bucket_index = FlagTableNode::find_bucket_index(package_id, flag, num_buckets);
-
- let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
- let mut flag_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
- if flag_node_offset < interpreted_header.node_offset as usize
- || flag_node_offset >= interpreted_header.file_size as usize
- {
- return Ok(None);
- }
-
- loop {
- let interpreted_node = FlagTableNode::from_bytes(&buf[flag_node_offset..])?;
- if interpreted_node.package_id == package_id && interpreted_node.flag_name == flag {
- return Ok(Some(interpreted_node.flag_id));
- }
- match interpreted_node.next_offset {
- Some(offset) => flag_node_offset = offset as usize,
- None => return Ok(None),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
-
- impl FlagTableNode {
- // create test baseline, syntactic sugar
- fn new_expected(
- package_id: u32,
- flag_name: &str,
- flag_type: u16,
- flag_id: u16,
- next_offset: Option<u32>,
- ) -> Self {
- Self { package_id, flag_name: flag_name.to_string(), flag_type, flag_id, next_offset }
- }
- }
-
- pub fn create_test_flag_table() -> FlagTable {
- let header = FlagTableHeader {
- version: crate::FILE_VERSION,
- container: String::from("system"),
- file_size: 320,
- num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
- };
- let buckets: Vec<Option<u32>> = vec![
- Some(98),
- Some(124),
- None,
- None,
- None,
- Some(177),
- None,
- Some(203),
- None,
- Some(261),
- None,
- None,
- None,
- None,
- None,
- Some(293),
- None,
- ];
- let nodes = vec![
- FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
- FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150)),
- FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
- FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
- FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235)),
- FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
- FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None),
- FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None),
- ];
- FlagTable { header, buckets, nodes }
- }
+ use crate::test_utils::create_test_flag_table;
#[test]
// this test point locks down the table serialization
@@ -266,56 +236,33 @@
assert_eq!(node, &reinterpreted_node);
}
- let reinterpreted_table = FlagTable::from_bytes(&flag_table.as_bytes());
+ let flag_table_bytes = flag_table.as_bytes();
+ let reinterpreted_table = FlagTable::from_bytes(&flag_table_bytes);
assert!(reinterpreted_table.is_ok());
assert_eq!(&flag_table, &reinterpreted_table.unwrap());
+ assert_eq!(flag_table_bytes.len() as u32, header.file_size);
}
#[test]
- // this test point locks down table query
- fn test_flag_query() {
- let flag_table = create_test_flag_table().as_bytes();
- let baseline = vec![
- (0, "enabled_ro", 1u16),
- (0, "enabled_rw", 2u16),
- (1, "disabled_ro", 0u16),
- (2, "enabled_ro", 1u16),
- (1, "enabled_fixed_ro", 1u16),
- (1, "enabled_ro", 2u16),
- (2, "enabled_fixed_ro", 0u16),
- (0, "disabled_rw", 0u16),
- ];
- for (package_id, flag_name, expected_offset) in baseline.into_iter() {
- let flag_offset =
- find_flag_offset(&flag_table[..], package_id, flag_name).unwrap().unwrap();
- assert_eq!(flag_offset, expected_offset);
- }
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let flag_table = create_test_flag_table();
+ let bytes = &flag_table.as_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1234)
}
#[test]
- // this test point locks down table query of a non exist flag
- fn test_not_existed_flag_query() {
- let flag_table = create_test_flag_table().as_bytes();
- let flag_offset = find_flag_offset(&flag_table[..], 1, "disabled_fixed_ro").unwrap();
- assert_eq!(flag_offset, None);
- let flag_offset = find_flag_offset(&flag_table[..], 2, "disabled_rw").unwrap();
- assert_eq!(flag_offset, None);
- }
-
- #[test]
- // this test point locks down query error when file has a higher version
- fn test_higher_version_storage_file() {
- let mut table = create_test_flag_table();
- table.header.version = crate::FILE_VERSION + 1;
- let flag_table = table.as_bytes();
- let error = find_flag_offset(&flag_table[..], 0, "enabled_ro").unwrap_err();
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_table = create_test_flag_table();
+ flag_table.header.file_type = 123u8;
+ let error = FlagTable::from_bytes(&flag_table.as_bytes()).unwrap_err();
assert_eq!(
format!("{:?}", error),
- format!(
- "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
- crate::FILE_VERSION + 1,
- crate::FILE_VERSION
- )
+ format!("BytesParseFail(binary file is not a flag map)")
);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
index 0a6a37f..c9d09a1 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
@@ -17,20 +17,42 @@
//! flag value module defines the flag value file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError::{self, HigherStorageFileVersion, InvalidStorageFileOffset};
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
+use std::fmt;
/// Flag value header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagValueHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_flags: u32,
pub boolean_value_offset: u32,
}
+/// Implement debug print trait for header
+impl fmt::Debug for FlagValueHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Flags: {}, Value Offset:{}",
+ self.num_flags, self.boolean_value_offset
+ )?;
+ Ok(())
+ }
+}
+
impl FlagValueHeader {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -39,6 +61,7 @@
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_type.to_le_bytes());
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_flags.to_le_bytes());
result.extend_from_slice(&self.boolean_value_offset.to_le_bytes());
@@ -48,23 +71,41 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let list = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_flags: read_u32_from_bytes(bytes, &mut head)?,
boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if list.file_type != StorageFileType::FlagVal as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag value file"
+ )));
+ }
+ Ok(list)
}
}
/// Flag value list struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagValueList {
pub header: FlagValueHeader,
pub booleans: Vec<bool>,
}
+/// Implement debug print trait for flag value
+impl fmt::Debug for FlagValueList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Values:")?;
+ writeln!(f, "{:?}", self.booleans)?;
+ Ok(())
+ }
+}
+
impl FlagValueList {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -87,46 +128,10 @@
}
}
-/// Query flag value
-pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, AconfigStorageError> {
- let interpreted_header = FlagValueHeader::from_bytes(buf)?;
- if interpreted_header.version > crate::FILE_VERSION {
- return Err(HigherStorageFileVersion(anyhow!(
- "Cannot read storage file with a higher version of {} with lib version {}",
- interpreted_header.version,
- crate::FILE_VERSION
- )));
- }
-
- let mut head = (interpreted_header.boolean_value_offset + flag_offset) as usize;
-
- // TODO: right now, there is only boolean flags, with more flag value types added
- // later, the end of boolean flag value section should be updated (b/322826265).
- if head >= interpreted_header.file_size as usize {
- return Err(InvalidStorageFileOffset(anyhow!(
- "Flag value offset goes beyond the end of the file."
- )));
- }
-
- let val = read_u8_from_bytes(buf, &mut head)?;
- Ok(val == 1)
-}
-
#[cfg(test)]
mod tests {
use super::*;
-
- pub fn create_test_flag_value_list() -> FlagValueList {
- let header = FlagValueHeader {
- version: crate::FILE_VERSION,
- container: String::from("system"),
- file_size: 34,
- num_flags: 8,
- boolean_value_offset: 26,
- };
- let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
- FlagValueList { header, booleans }
- }
+ use crate::test_utils::create_test_flag_value_list;
#[test]
// this test point locks down the value list serialization
@@ -138,47 +143,33 @@
assert!(reinterpreted_header.is_ok());
assert_eq!(header, &reinterpreted_header.unwrap());
- let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
+ let flag_value_bytes = flag_value_list.as_bytes();
+ let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_bytes);
assert!(reinterpreted_value_list.is_ok());
assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
+ assert_eq!(flag_value_bytes.len() as u32, header.file_size);
}
#[test]
- // this test point locks down flag value query
- fn test_flag_value_query() {
- let flag_value_list = create_test_flag_value_list().as_bytes();
- let baseline: Vec<bool> = vec![false, true, false, false, true, true, false, true];
- for (offset, expected_value) in baseline.into_iter().enumerate() {
- let flag_value = find_boolean_flag_value(&flag_value_list[..], offset as u32).unwrap();
- assert_eq!(flag_value, expected_value);
- }
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let flag_value_list = create_test_flag_value_list();
+ let bytes = &flag_value_list.as_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1234)
}
#[test]
- // this test point locks down query beyond the end of boolean section
- fn test_boolean_out_of_range() {
- let flag_value_list = create_test_flag_value_list().as_bytes();
- let error = find_boolean_flag_value(&flag_value_list[..], 8).unwrap_err();
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_value_list = create_test_flag_value_list();
+ flag_value_list.header.file_type = 123u8;
+ let error = FlagValueList::from_bytes(&flag_value_list.as_bytes()).unwrap_err();
assert_eq!(
format!("{:?}", error),
- "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
- );
- }
-
- #[test]
- // this test point locks down query error when file has a higher version
- fn test_higher_version_storage_file() {
- let mut value_list = create_test_flag_value_list();
- value_list.header.version = crate::FILE_VERSION + 1;
- let flag_value = value_list.as_bytes();
- let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
- crate::FILE_VERSION + 1,
- crate::FILE_VERSION
- )
+ format!("BytesParseFail(binary file is not a flag value file)")
);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index 84e0e90..24b16a1 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -34,7 +34,6 @@
pub mod flag_table;
pub mod flag_value;
-pub mod mapped_file;
pub mod package_table;
pub mod protos;
@@ -43,12 +42,13 @@
use anyhow::anyhow;
use std::collections::hash_map::DefaultHasher;
+use std::fs::File;
use std::hash::{Hash, Hasher};
+use std::io::Read;
-pub use crate::flag_table::{FlagOffset, FlagTable, FlagTableHeader, FlagTableNode};
+pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
pub use crate::flag_value::{FlagValueHeader, FlagValueList};
-pub use crate::package_table::{PackageOffset, PackageTable, PackageTableHeader, PackageTableNode};
-pub use crate::protos::ProtoStorageFiles;
+pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit};
@@ -62,18 +62,15 @@
402653189, 805306457, 1610612741,
];
-/// Storage file location pb file
-pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/storage_files.pb";
-
/// Storage file type enum
#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum StorageFileSelection {
- PackageMap,
- FlagMap,
- FlagVal,
+pub enum StorageFileType {
+ PackageMap = 0,
+ FlagMap = 1,
+ FlagVal = 2,
}
-impl TryFrom<&str> for StorageFileSelection {
+impl TryFrom<&str> for StorageFileType {
type Error = anyhow::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
@@ -81,7 +78,22 @@
"package_map" => Ok(Self::PackageMap),
"flag_map" => Ok(Self::FlagMap),
"flag_val" => Ok(Self::FlagVal),
- _ => Err(anyhow!("Invalid storage file to create")),
+ _ => Err(anyhow!(
+ "Invalid storage file type, valid types are package_map|flag_map|flag_val"
+ )),
+ }
+ }
+}
+
+impl TryFrom<u8> for StorageFileType {
+ type Error = anyhow::Error;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ x if x == Self::PackageMap as u8 => Ok(Self::PackageMap),
+ x if x == Self::FlagMap as u8 => Ok(Self::FlagMap),
+ x if x == Self::FlagVal as u8 => Ok(Self::FlagVal),
+ _ => Err(anyhow!("Invalid storage file type")),
}
}
}
@@ -104,7 +116,7 @@
}
/// Read and parse bytes as u8
-pub(crate) fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
+pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
let val =
u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg))
@@ -127,10 +139,7 @@
}
/// Read and parse bytes as u32
-pub(crate) fn read_u32_from_bytes(
- buf: &[u8],
- head: &mut usize,
-) -> Result<u32, AconfigStorageError> {
+pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> {
let val =
u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
@@ -167,6 +176,12 @@
#[error("fail to map storage file")]
MapFileFail(#[source] anyhow::Error),
+ #[error("fail to get mapped file")]
+ ObtainMappedFileFail(#[source] anyhow::Error),
+
+ #[error("fail to flush mapped storage file")]
+ MapFlushFail(#[source] anyhow::Error),
+
#[error("number of items in hash table exceed limit")]
HashTableSizeLimit(#[source] anyhow::Error),
@@ -178,393 +193,88 @@
#[error("invalid storage file byte offset")]
InvalidStorageFileOffset(#[source] anyhow::Error),
+
+ #[error("failed to create file")]
+ FileCreationFail(#[source] anyhow::Error),
}
-/// Get package start offset implementation
-pub fn get_package_offset_impl(
- pb_file: &str,
- container: &str,
- package: &str,
-) -> Result<Option<PackageOffset>, AconfigStorageError> {
- let mapped_file =
- crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::PackageMap)?;
- crate::package_table::find_package_offset(&mapped_file, package)
+/// Read in storage file as bytes
+pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError> {
+ let mut file = File::open(file_path).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!(
+ "Failed to read 4 bytes from file {}: {}",
+ file_path,
+ errmsg
+ ))
+ })?;
+ Ok(buffer)
}
-/// Get flag offset implementation
-pub fn get_flag_offset_impl(
- pb_file: &str,
- container: &str,
- package_id: u32,
- flag: &str,
-) -> Result<Option<FlagOffset>, AconfigStorageError> {
- let mapped_file =
- crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::FlagMap)?;
- crate::flag_table::find_flag_offset(&mapped_file, package_id, flag)
-}
+/// List flag values from storage files
+pub fn list_flags(
+ package_map: &str,
+ flag_map: &str,
+ flag_val: &str,
+) -> Result<Vec<(String, bool)>, AconfigStorageError> {
+ let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
+ let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
+ let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
-/// Get boolean flag value implementation
-pub fn get_boolean_flag_value_impl(
- pb_file: &str,
- container: &str,
- offset: u32,
-) -> Result<bool, AconfigStorageError> {
- let mapped_file =
- crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::FlagVal)?;
- crate::flag_value::find_boolean_flag_value(&mapped_file, offset)
-}
-
-/// Get package start offset for flags given the container and package name.
-///
-/// This function would map the corresponding package map file if has not been mapped yet,
-/// and then look for the target package in this mapped file.
-///
-/// If a package is found, it returns Ok(Some(PackageOffset))
-/// If a package is not found, it returns Ok(None)
-/// If errors out such as no such package map file is found, it returns an Err(errmsg)
-pub fn get_package_offset(
- container: &str,
- package: &str,
-) -> Result<Option<PackageOffset>, AconfigStorageError> {
- get_package_offset_impl(STORAGE_LOCATION_FILE, container, package)
-}
-
-/// Get flag offset within a package given the container name, package id and flag name.
-///
-/// This function would map the corresponding flag map file if has not been mapped yet,
-/// and then look for the target flag in this mapped file.
-///
-/// If a flag is found, it returns Ok(Some(u16))
-/// If a flag is not found, it returns Ok(None)
-/// If errors out such as no such flag map file is found, it returns an Err(errmsg)
-pub fn get_flag_offset(
- container: &str,
- package_id: u32,
- flag: &str,
-) -> Result<Option<FlagOffset>, AconfigStorageError> {
- get_flag_offset_impl(STORAGE_LOCATION_FILE, container, package_id, flag)
-}
-
-/// Get the boolean flag value given the container name and flag global offset
-///
-/// This function would map the corresponding flag value file if has not been mapped yet,
-/// and then look for the target flag value at the specified offset.
-///
-/// If flag value file is successfully mapped and the provide offset is valid, it returns
-/// the boolean flag value, otherwise it returns the error message.
-pub fn get_boolean_flag_value(container: &str, offset: u32) -> Result<bool, AconfigStorageError> {
- get_boolean_flag_value_impl(STORAGE_LOCATION_FILE, container, offset)
-}
-
-#[cxx::bridge]
-mod ffi {
- // Package table query return for cc interlop
- pub struct PackageOffsetQueryCXX {
- pub query_success: bool,
- pub error_message: String,
- pub package_exists: bool,
- pub package_id: u32,
- pub boolean_offset: u32,
+ let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
+ for node in package_table.nodes.iter() {
+ package_info[node.package_id as usize] = (&node.package_name, node.boolean_offset);
}
- // Flag table query return for cc interlop
- pub struct FlagOffsetQueryCXX {
- pub query_success: bool,
- pub error_message: String,
- pub flag_exists: bool,
- pub flag_offset: u16,
+ let mut flags = Vec::new();
+ for node in flag_table.nodes.iter() {
+ let (package_name, package_offset) = package_info[node.package_id as usize];
+ let full_flag_name = String::from(package_name) + "/" + &node.flag_name;
+ let flag_offset = package_offset + node.flag_id as u32;
+ let flag_value = flag_value_list.booleans[flag_offset as usize];
+ flags.push((full_flag_name, flag_value));
}
- // Flag value query return for cc interlop
- pub struct BooleanFlagValueQueryCXX {
- pub query_success: bool,
- pub error_message: String,
- pub flag_value: bool,
- }
-
- // Rust export to c++
- extern "Rust" {
- pub fn get_package_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package: &str,
- ) -> PackageOffsetQueryCXX;
-
- pub fn get_flag_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package_id: u32,
- flag: &str,
- ) -> FlagOffsetQueryCXX;
-
- pub fn get_boolean_flag_value_cxx_impl(
- pb_file: &str,
- container: &str,
- offset: u32,
- ) -> BooleanFlagValueQueryCXX;
-
- pub fn get_package_offset_cxx(container: &str, package: &str) -> PackageOffsetQueryCXX;
-
- pub fn get_flag_offset_cxx(
- container: &str,
- package_id: u32,
- flag: &str,
- ) -> FlagOffsetQueryCXX;
-
- pub fn get_boolean_flag_value_cxx(container: &str, offset: u32)
- -> BooleanFlagValueQueryCXX;
- }
-}
-
-/// Get package start offset impl cc interlop
-pub fn get_package_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package: &str,
-) -> ffi::PackageOffsetQueryCXX {
- ffi::PackageOffsetQueryCXX::new(get_package_offset_impl(pb_file, container, package))
-}
-
-/// Get flag start offset impl cc interlop
-pub fn get_flag_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package_id: u32,
- flag: &str,
-) -> ffi::FlagOffsetQueryCXX {
- ffi::FlagOffsetQueryCXX::new(get_flag_offset_impl(pb_file, container, package_id, flag))
-}
-
-/// Get boolean flag value impl cc interlop
-pub fn get_boolean_flag_value_cxx_impl(
- pb_file: &str,
- container: &str,
- offset: u32,
-) -> ffi::BooleanFlagValueQueryCXX {
- ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value_impl(pb_file, container, offset))
-}
-
-/// Get package start offset cc interlop
-pub fn get_package_offset_cxx(container: &str, package: &str) -> ffi::PackageOffsetQueryCXX {
- ffi::PackageOffsetQueryCXX::new(get_package_offset(container, package))
-}
-
-/// Get flag start offset cc interlop
-pub fn get_flag_offset_cxx(
- container: &str,
- package_id: u32,
- flag: &str,
-) -> ffi::FlagOffsetQueryCXX {
- ffi::FlagOffsetQueryCXX::new(get_flag_offset(container, package_id, flag))
-}
-
-/// Get boolean flag value cc interlop
-pub fn get_boolean_flag_value_cxx(container: &str, offset: u32) -> ffi::BooleanFlagValueQueryCXX {
- ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value(container, offset))
-}
-
-impl ffi::PackageOffsetQueryCXX {
- pub(crate) fn new(offset_result: Result<Option<PackageOffset>, AconfigStorageError>) -> Self {
- match offset_result {
- Ok(offset_opt) => match offset_opt {
- Some(offset) => Self {
- query_success: true,
- error_message: String::from(""),
- package_exists: true,
- package_id: offset.package_id,
- boolean_offset: offset.boolean_offset,
- },
- None => Self {
- query_success: true,
- error_message: String::from(""),
- package_exists: false,
- package_id: 0,
- boolean_offset: 0,
- },
- },
- Err(errmsg) => Self {
- query_success: false,
- error_message: format!("{:?}", errmsg),
- package_exists: false,
- package_id: 0,
- boolean_offset: 0,
- },
- }
- }
-}
-
-impl ffi::FlagOffsetQueryCXX {
- pub(crate) fn new(offset_result: Result<Option<FlagOffset>, AconfigStorageError>) -> Self {
- match offset_result {
- Ok(offset_opt) => match offset_opt {
- Some(offset) => Self {
- query_success: true,
- error_message: String::from(""),
- flag_exists: true,
- flag_offset: offset,
- },
- None => Self {
- query_success: true,
- error_message: String::from(""),
- flag_exists: false,
- flag_offset: 0,
- },
- },
- Err(errmsg) => Self {
- query_success: false,
- error_message: format!("{:?}", errmsg),
- flag_exists: false,
- flag_offset: 0,
- },
- }
- }
-}
-
-impl ffi::BooleanFlagValueQueryCXX {
- pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
- match value_result {
- Ok(value) => {
- Self { query_success: true, error_message: String::from(""), flag_value: value }
- }
- Err(errmsg) => Self {
- query_success: false,
- error_message: format!("{:?}", errmsg),
- flag_value: false,
- },
- }
- }
+ flags.sort_by(|v1, v2| v1.0.cmp(&v2.0));
+ Ok(flags)
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::test_utils::{write_storage_text_to_temp_file, TestStorageFileSet};
-
- fn create_test_storage_files(read_only: bool) -> TestStorageFileSet {
- TestStorageFileSet::new(
- "./tests/package.map",
- "./tests/flag.map",
- "./tests/flag.val",
- read_only,
- )
- .unwrap()
- }
+ use crate::test_utils::{
+ create_test_flag_table, create_test_flag_value_list, create_test_package_table,
+ write_bytes_to_temp_file,
+ };
#[test]
- // this test point locks down flag package offset query
- fn test_package_offset_query() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
+ // this test point locks down the flag list api
+ fn test_list_flag() {
+ let package_table =
+ write_bytes_to_temp_file(&create_test_package_table().as_bytes()).unwrap();
+ let flag_table = write_bytes_to_temp_file(&create_test_flag_table().as_bytes()).unwrap();
+ let flag_value_list =
+ write_bytes_to_temp_file(&create_test_flag_value_list().as_bytes()).unwrap();
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_1",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
- assert_eq!(package_offset, expected_package_offset);
+ let package_table_path = package_table.path().display().to_string();
+ let flag_table_path = flag_table.path().display().to_string();
+ let flag_value_list_path = flag_value_list.path().display().to_string();
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_2",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_4",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
- assert_eq!(package_offset, expected_package_offset);
- }
-
- #[test]
- // this test point locks down flag offset query
- fn test_flag_offset_query() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let baseline = vec![
- (0, "enabled_ro", 1u16),
- (0, "enabled_rw", 2u16),
- (1, "disabled_ro", 0u16),
- (2, "enabled_ro", 1u16),
- (1, "enabled_fixed_ro", 1u16),
- (1, "enabled_ro", 2u16),
- (2, "enabled_fixed_ro", 0u16),
- (0, "disabled_rw", 0u16),
+ let flags =
+ list_flags(&package_table_path, &flag_table_path, &flag_value_list_path).unwrap();
+ let expected = [
+ (String::from("com.android.aconfig.storage.test_1/disabled_rw"), false),
+ (String::from("com.android.aconfig.storage.test_1/enabled_ro"), true),
+ (String::from("com.android.aconfig.storage.test_1/enabled_rw"), false),
+ (String::from("com.android.aconfig.storage.test_2/disabled_ro"), false),
+ (String::from("com.android.aconfig.storage.test_2/enabled_fixed_ro"), true),
+ (String::from("com.android.aconfig.storage.test_2/enabled_ro"), true),
+ (String::from("com.android.aconfig.storage.test_4/enabled_fixed_ro"), false),
+ (String::from("com.android.aconfig.storage.test_4/enabled_ro"), true),
];
- for (package_id, flag_name, expected_offset) in baseline.into_iter() {
- let flag_offset =
- get_flag_offset_impl(&file_full_path, "system", package_id, flag_name)
- .unwrap()
- .unwrap();
- assert_eq!(flag_offset, expected_offset);
- }
- }
-
- #[test]
- // this test point locks down flag offset query
- fn test_flag_value_query() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let baseline: Vec<bool> = vec![false; 8];
- for (offset, expected_value) in baseline.into_iter().enumerate() {
- let flag_value =
- get_boolean_flag_value_impl(&file_full_path, "system", offset as u32).unwrap();
- assert_eq!(flag_value, expected_value);
- }
+ assert_eq!(flags, expected);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs
new file mode 100644
index 0000000..293d018
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/main.rs
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! `aconfig-storage` is a debugging tool to parse storage files
+
+use aconfig_storage_file::{
+ list_flags, read_file_to_bytes, AconfigStorageError, FlagTable, FlagValueList, PackageTable,
+ StorageFileType,
+};
+
+use clap::{builder::ArgAction, Arg, Command};
+
+fn cli() -> Command {
+ Command::new("aconfig-storage")
+ .subcommand_required(true)
+ .subcommand(
+ Command::new("print")
+ .arg(Arg::new("file").long("file").required(true).action(ArgAction::Set))
+ .arg(
+ Arg::new("type")
+ .long("type")
+ .required(true)
+ .value_parser(|s: &str| StorageFileType::try_from(s)),
+ ),
+ )
+ .subcommand(
+ Command::new("list")
+ .arg(
+ Arg::new("package-map")
+ .long("package-map")
+ .required(true)
+ .action(ArgAction::Set),
+ )
+ .arg(Arg::new("flag-map").long("flag-map").required(true).action(ArgAction::Set))
+ .arg(Arg::new("flag-val").long("flag-val").required(true).action(ArgAction::Set)),
+ )
+}
+
+fn print_storage_file(
+ file_path: &str,
+ file_type: &StorageFileType,
+) -> 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);
+ }
+ StorageFileType::FlagMap => {
+ let flag_table = FlagTable::from_bytes(&bytes)?;
+ println!("{:?}", flag_table);
+ }
+ StorageFileType::FlagVal => {
+ let flag_value = FlagValueList::from_bytes(&bytes)?;
+ println!("{:?}", flag_value);
+ }
+ }
+ Ok(())
+}
+
+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)?
+ }
+ Some(("list", sub_matches)) => {
+ let package_map = sub_matches.get_one::<String>("package-map").unwrap();
+ let flag_map = sub_matches.get_one::<String>("flag-map").unwrap();
+ let flag_val = sub_matches.get_one::<String>("flag-val").unwrap();
+ let flags = list_flags(package_map, flag_map, flag_val)?;
+ for flag in flags.iter() {
+ println!("{}: {}", flag.0, flag.1);
+ }
+ }
+ _ => unreachable!(),
+ }
+ Ok(())
+}
diff --git a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs b/tools/aconfig/aconfig_storage_file/src/mapped_file.rs
deleted file mode 100644
index d8f2570..0000000
--- a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-use std::collections::HashMap;
-use std::fs::File;
-use std::io::{BufReader, Read};
-use std::sync::{Arc, Mutex};
-
-use anyhow::anyhow;
-use memmap2::Mmap;
-use once_cell::sync::Lazy;
-
-use crate::protos::{
- storage_files::try_from_binary_proto, ProtoStorageFileInfo, ProtoStorageFiles,
-};
-use crate::AconfigStorageError::{
- self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
-};
-use crate::StorageFileSelection;
-
-/// Cache for already mapped files
-static ALL_MAPPED_FILES: Lazy<Mutex<HashMap<String, MappedStorageFileSet>>> = Lazy::new(|| {
- let mapped_files = HashMap::new();
- Mutex::new(mapped_files)
-});
-
-/// Mapped storage files for a particular container
-#[derive(Debug)]
-struct MappedStorageFileSet {
- package_map: Arc<Mmap>,
- flag_map: Arc<Mmap>,
- flag_val: Arc<Mmap>,
-}
-
-/// Find where storage files are stored for a particular container
-fn find_container_storage_location(
- location_pb_file: &str,
- container: &str,
-) -> Result<ProtoStorageFileInfo, AconfigStorageError> {
- let file = File::open(location_pb_file).map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to open file {}: {}", location_pb_file, errmsg))
- })?;
- let mut reader = BufReader::new(file);
- let mut bytes = Vec::new();
- reader.read_to_end(&mut bytes).map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to read file {}: {}", location_pb_file, errmsg))
- })?;
- let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes).map_err(|errmsg| {
- ProtobufParseFail(anyhow!(
- "Failed to parse storage location pb file {}: {}",
- location_pb_file,
- errmsg
- ))
- })?;
- for location_info in storage_locations.files.iter() {
- if location_info.container() == container {
- return Ok(location_info.clone());
- }
- }
- Err(StorageFileNotFound(anyhow!("Storage file does not exist for {}", container)))
-}
-
-/// Verify the file is read only and then map it
-fn verify_read_only_and_map(file_path: &str) -> Result<Mmap, AconfigStorageError> {
- let file = File::open(file_path)
- .map_err(|errmsg| FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)))?;
- let metadata = file.metadata().map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to find metadata for {}: {}", file_path, errmsg))
- })?;
-
- // ensure storage file is read only
- if !metadata.permissions().readonly() {
- return Err(MapFileFail(anyhow!("fail to map non read only storage file {}", file_path)));
- }
-
- // SAFETY:
- //
- // Mmap constructors are unsafe as it would have undefined behaviors if the file
- // is modified after mapped (https://docs.rs/memmap2/latest/memmap2/struct.Mmap.html).
- //
- // We either have to make this api unsafe or ensure that the file will not be modified
- // which means it is read only. Here in the code, we check explicitly that the file
- // being mapped must only have read permission, otherwise, error out, thus making sure
- // it is safe.
- //
- // We should remove this restriction if we need to support mmap non read only file in
- // the future (by making this api unsafe). But for now, all flags are boot stable, so
- // the boot flag file copy should be readonly.
- unsafe {
- let mapped_file = Mmap::map(&file).map_err(|errmsg| {
- MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
- })?;
- Ok(mapped_file)
- }
-}
-
-/// Map all storage files for a particular container
-fn map_container_storage_files(
- location_pb_file: &str,
- container: &str,
-) -> Result<MappedStorageFileSet, AconfigStorageError> {
- let files_location = find_container_storage_location(location_pb_file, container)?;
- let package_map = Arc::new(verify_read_only_and_map(files_location.package_map())?);
- let flag_map = Arc::new(verify_read_only_and_map(files_location.flag_map())?);
- let flag_val = Arc::new(verify_read_only_and_map(files_location.flag_val())?);
- Ok(MappedStorageFileSet { package_map, flag_map, flag_val })
-}
-
-/// Get a mapped storage file given the container and file type
-pub(crate) fn get_mapped_file(
- location_pb_file: &str,
- container: &str,
- file_selection: StorageFileSelection,
-) -> Result<Arc<Mmap>, AconfigStorageError> {
- let mut all_mapped_files = ALL_MAPPED_FILES.lock().unwrap();
- match all_mapped_files.get(container) {
- Some(mapped_files) => Ok(match file_selection {
- StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
- StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
- StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
- }),
- None => {
- let mapped_files = map_container_storage_files(location_pb_file, container)?;
- let file_ptr = match file_selection {
- StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
- StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
- StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
- };
- all_mapped_files.insert(container.to_string(), mapped_files);
- Ok(file_ptr)
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::test_utils::{write_storage_text_to_temp_file, TestStorageFileSet};
-
- #[test]
- fn test_find_storage_file_location() {
- let text_proto = r#"
-files {
- version: 0
- container: "system"
- package_map: "/system/etc/package.map"
- flag_map: "/system/etc/flag.map"
- flag_val: "/metadata/aconfig/system.val"
- timestamp: 12345
-}
-files {
- version: 1
- container: "product"
- package_map: "/product/etc/package.map"
- flag_map: "/product/etc/flag.map"
- flag_val: "/metadata/aconfig/product.val"
- timestamp: 54321
-}
-"#;
- let file = write_storage_text_to_temp_file(text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let file_info = find_container_storage_location(&file_full_path, "system").unwrap();
- assert_eq!(file_info.version(), 0);
- assert_eq!(file_info.container(), "system");
- assert_eq!(file_info.package_map(), "/system/etc/package.map");
- assert_eq!(file_info.flag_map(), "/system/etc/flag.map");
- assert_eq!(file_info.flag_val(), "/metadata/aconfig/system.val");
- assert_eq!(file_info.timestamp(), 12345);
-
- let file_info = find_container_storage_location(&file_full_path, "product").unwrap();
- assert_eq!(file_info.version(), 1);
- assert_eq!(file_info.container(), "product");
- assert_eq!(file_info.package_map(), "/product/etc/package.map");
- assert_eq!(file_info.flag_map(), "/product/etc/flag.map");
- assert_eq!(file_info.flag_val(), "/metadata/aconfig/product.val");
- assert_eq!(file_info.timestamp(), 54321);
-
- let err = find_container_storage_location(&file_full_path, "vendor").unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
- }
-
- fn map_and_verify(
- location_pb_file: &str,
- file_selection: StorageFileSelection,
- actual_file: &str,
- ) {
- let mut opened_file = File::open(actual_file).unwrap();
- let mut content = Vec::new();
- opened_file.read_to_end(&mut content).unwrap();
-
- let mmaped_file = get_mapped_file(location_pb_file, "system", file_selection).unwrap();
- assert_eq!(mmaped_file[..], content[..]);
- }
-
- fn create_test_storage_files(read_only: bool) -> TestStorageFileSet {
- TestStorageFileSet::new(
- "./tests/package.map",
- "./tests/flag.map",
- "./tests/flag.val",
- read_only,
- )
- .unwrap()
- }
-
- #[test]
- fn test_mapped_file_contents() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- map_and_verify(
- &file_full_path,
- StorageFileSelection::PackageMap,
- &ro_files.package_map.name,
- );
- map_and_verify(&file_full_path, StorageFileSelection::FlagMap, &ro_files.flag_map.name);
- map_and_verify(&file_full_path, StorageFileSelection::FlagVal, &ro_files.flag_val.name);
- }
-
- #[test]
- fn test_map_non_read_only_file() {
- let ro_files = create_test_storage_files(true);
- let rw_files = create_test_storage_files(false);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- rw_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let error = map_container_storage_files(&file_full_path, "system").unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read only storage file {})",
- rw_files.package_map.name
- )
- );
-
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, rw_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let error = map_container_storage_files(&file_full_path, "system").unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read only storage file {})",
- rw_files.flag_map.name
- )
- );
-
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, rw_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let error = map_container_storage_files(&file_full_path, "system").unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read only storage file {})",
- rw_files.flag_val.name
- )
- );
- }
-}
diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs
index 7308d7b..7cb60eb 100644
--- a/tools/aconfig/aconfig_storage_file/src/package_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs
@@ -17,21 +17,43 @@
//! package table module defines the package table file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError::{self, BytesParseFail, HigherStorageFileVersion};
-use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes};
+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 std::fmt;
/// Package table header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct PackageTableHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_packages: u32,
pub bucket_offset: u32,
pub node_offset: u32,
}
+/// Implement debug print trait for header
+impl fmt::Debug for PackageTableHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Packages: {}, Bucket Offset:{}, Node Offset: {}",
+ self.num_packages, self.bucket_offset, self.node_offset
+ )?;
+ Ok(())
+ }
+}
+
impl PackageTableHeader {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -40,6 +62,7 @@
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_type.to_le_bytes());
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_packages.to_le_bytes());
result.extend_from_slice(&self.bucket_offset.to_le_bytes());
@@ -50,19 +73,26 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let table = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_packages: read_u32_from_bytes(bytes, &mut head)?,
bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
node_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if table.file_type != StorageFileType::PackageMap as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a package map"
+ )));
+ }
+ Ok(table)
}
}
/// Package table node struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct PackageTableNode {
pub package_name: String,
pub package_id: u32,
@@ -72,6 +102,18 @@
pub next_offset: Option<u32>,
}
+/// Implement debug print trait for node
+impl fmt::Debug for PackageTableNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Package: {}, Id: {}, Offset: {}, Next: {:?}",
+ self.package_name, self.package_id, self.boolean_offset, self.next_offset
+ )?;
+ Ok(())
+ }
+}
+
impl PackageTableNode {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -109,13 +151,28 @@
}
/// Package table struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct PackageTable {
pub header: PackageTableHeader,
pub buckets: Vec<Option<u32>>,
pub nodes: Vec<PackageTableNode>,
}
+/// Implement debug print trait for package table
+impl fmt::Debug for PackageTable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Buckets:")?;
+ writeln!(f, "{:?}", self.buckets)?;
+ writeln!(f, "Nodes:")?;
+ for node in self.nodes.iter() {
+ write!(f, "{:?}", node)?;
+ }
+ Ok(())
+ }
+}
+
impl PackageTable {
/// Serialize to bytes
pub fn as_bytes(&self) -> Vec<u8> {
@@ -146,95 +203,22 @@
Ok(node)
})
.collect::<Result<Vec<_>, AconfigStorageError>>()
- .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse package table: {}", errmsg)))?;
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!(
+ "fail to parse package table: {}",
+ errmsg
+ ))
+ })?;
let table = Self { header, buckets, nodes };
Ok(table)
}
}
-/// Package table query return
-#[derive(PartialEq, Debug)]
-pub struct PackageOffset {
- pub package_id: u32,
- pub boolean_offset: u32,
-}
-
-/// Query package id and start offset
-pub fn find_package_offset(
- buf: &[u8],
- package: &str,
-) -> Result<Option<PackageOffset>, AconfigStorageError> {
- let interpreted_header = PackageTableHeader::from_bytes(buf)?;
- if interpreted_header.version > crate::FILE_VERSION {
- return Err(HigherStorageFileVersion(anyhow!(
- "Cannot read storage file with a higher version of {} with lib version {}",
- interpreted_header.version,
- crate::FILE_VERSION
- )));
- }
-
- let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
- let bucket_index = PackageTableNode::find_bucket_index(package, num_buckets);
-
- let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
- let mut package_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
- if package_node_offset < interpreted_header.node_offset as usize
- || package_node_offset >= interpreted_header.file_size as usize
- {
- return Ok(None);
- }
-
- loop {
- let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?;
- if interpreted_node.package_name == package {
- return Ok(Some(PackageOffset {
- package_id: interpreted_node.package_id,
- boolean_offset: interpreted_node.boolean_offset,
- }));
- }
- match interpreted_node.next_offset {
- Some(offset) => package_node_offset = offset as usize,
- None => return Ok(None),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
-
- pub fn create_test_package_table() -> PackageTable {
- let header = PackageTableHeader {
- version: crate::FILE_VERSION,
- container: String::from("system"),
- file_size: 208,
- num_packages: 3,
- bucket_offset: 30,
- node_offset: 58,
- };
- let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
- let first_node = PackageTableNode {
- package_name: String::from("com.android.aconfig.storage.test_2"),
- package_id: 1,
- boolean_offset: 3,
- next_offset: None,
- };
- let second_node = PackageTableNode {
- package_name: String::from("com.android.aconfig.storage.test_1"),
- package_id: 0,
- boolean_offset: 0,
- next_offset: Some(158),
- };
- let third_node = PackageTableNode {
- package_name: String::from("com.android.aconfig.storage.test_4"),
- package_id: 2,
- boolean_offset: 6,
- next_offset: None,
- };
- let nodes = vec![first_node, second_node, third_node];
- PackageTable { header, buckets, nodes }
- }
+ use crate::test_utils::create_test_package_table;
#[test]
// this test point locks down the table serialization
@@ -251,64 +235,33 @@
assert_eq!(node, &reinterpreted_node);
}
- let reinterpreted_table = PackageTable::from_bytes(&package_table.as_bytes());
+ let package_table_bytes = package_table.as_bytes();
+ let reinterpreted_table = PackageTable::from_bytes(&package_table_bytes);
assert!(reinterpreted_table.is_ok());
assert_eq!(&package_table, &reinterpreted_table.unwrap());
+ assert_eq!(package_table_bytes.len() as u32, header.file_size);
}
#[test]
- // this test point locks down table query
- fn test_package_query() {
- let package_table = create_test_package_table().as_bytes();
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1")
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
- assert_eq!(package_offset, expected_package_offset);
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_2")
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
- assert_eq!(package_offset, expected_package_offset);
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_4")
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
- assert_eq!(package_offset, expected_package_offset);
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let package_table = create_test_package_table();
+ let bytes = &package_table.as_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1234)
}
#[test]
- // this test point locks down table query of a non exist package
- fn test_not_existed_package_query() {
- // this will land at an empty bucket
- let package_table = create_test_package_table().as_bytes();
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_3").unwrap();
- assert_eq!(package_offset, None);
- // this will land at the end of a linked list
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_5").unwrap();
- assert_eq!(package_offset, None);
- }
-
- #[test]
- // this test point locks down query error when file has a higher version
- fn test_higher_version_storage_file() {
- let mut table = create_test_package_table();
- table.header.version = crate::FILE_VERSION + 1;
- let package_table = table.as_bytes();
- let error = find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1")
- .unwrap_err();
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut package_table = create_test_package_table();
+ package_table.header.file_type = 123u8;
+ let error = PackageTable::from_bytes(&package_table.as_bytes()).unwrap_err();
assert_eq!(
format!("{:?}", error),
- format!(
- "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
- crate::FILE_VERSION + 1,
- crate::FILE_VERSION
- )
+ format!("BytesParseFail(binary file is not a package map)")
);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/protos.rs b/tools/aconfig/aconfig_storage_file/src/protos.rs
index 37df3e1..8b86205 100644
--- a/tools/aconfig/aconfig_storage_file/src/protos.rs
+++ b/tools/aconfig/aconfig_storage_file/src/protos.rs
@@ -49,8 +49,11 @@
pub use auto_generated::*;
use anyhow::Result;
+use protobuf::Message;
+use std::io::Write;
+use tempfile::NamedTempFile;
-pub mod storage_files {
+pub mod storage_record_pb {
use super::*;
use anyhow::ensure;
@@ -80,15 +83,28 @@
}
Ok(())
}
+
+ pub fn get_binary_proto_from_text_proto(text_proto: &str) -> Result<Vec<u8>> {
+ let storage_files: ProtoStorageFiles = protobuf::text_format::parse_from_str(text_proto)?;
+ let mut binary_proto = Vec::new();
+ storage_files.write_to_vec(&mut binary_proto)?;
+ Ok(binary_proto)
+ }
+
+ pub fn write_proto_to_temp_file(text_proto: &str) -> Result<NamedTempFile> {
+ let bytes = get_binary_proto_from_text_proto(text_proto).unwrap();
+ let mut file = NamedTempFile::new()?;
+ let _ = file.write_all(&bytes);
+ Ok(file)
+ }
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::test_utils::get_binary_storage_proto_bytes;
#[test]
- fn test_parse_storage_files() {
+ fn test_parse_storage_record_pb() {
let text_proto = r#"
files {
version: 0
@@ -107,8 +123,9 @@
timestamp: 54321
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let storage_files = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let storage_files = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap();
assert_eq!(storage_files.files.len(), 2);
let system_file = &storage_files.files[0];
assert_eq!(system_file.version(), 0);
@@ -127,7 +144,7 @@
}
#[test]
- fn test_parse_invalid_storage_files() {
+ fn test_parse_invalid_storage_record_pb() {
let text_proto = r#"
files {
version: 0
@@ -138,8 +155,9 @@
timestamp: 12345
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let err = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
assert_eq!(
format!("{:?}", err),
"invalid storage file record: missing package map file for container system"
@@ -155,8 +173,9 @@
timestamp: 12345
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let err = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
assert_eq!(
format!("{:?}", err),
"invalid storage file record: missing flag map file for container system"
@@ -172,8 +191,9 @@
timestamp: 12345
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let err = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
assert_eq!(
format!("{:?}", err),
"invalid storage file record: missing flag val file for container system"
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
index 7905d51..586bb4c 100644
--- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,79 +14,120 @@
* limitations under the License.
*/
-use crate::protos::ProtoStorageFiles;
-use anyhow::Result;
-use protobuf::Message;
-use std::fs;
+use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
+use crate::flag_value::{FlagValueHeader, FlagValueList};
+use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
+use crate::{AconfigStorageError, StorageFileType};
+
+use anyhow::anyhow;
use std::io::Write;
use tempfile::NamedTempFile;
-pub(crate) fn get_binary_storage_proto_bytes(text_proto: &str) -> Result<Vec<u8>> {
- let storage_files: ProtoStorageFiles = protobuf::text_format::parse_from_str(text_proto)?;
- let mut binary_proto = Vec::new();
- storage_files.write_to_vec(&mut binary_proto)?;
- Ok(binary_proto)
+pub(crate) fn create_test_package_table() -> PackageTable {
+ let header = PackageTableHeader {
+ version: 1234,
+ container: String::from("system"),
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
+ num_packages: 3,
+ bucket_offset: 31,
+ node_offset: 59,
+ };
+ let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
+ let first_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_2"),
+ package_id: 1,
+ boolean_offset: 3,
+ next_offset: None,
+ };
+ let second_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_1"),
+ package_id: 0,
+ boolean_offset: 0,
+ next_offset: Some(159),
+ };
+ let third_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_4"),
+ package_id: 2,
+ boolean_offset: 6,
+ next_offset: None,
+ };
+ let nodes = vec![first_node, second_node, third_node];
+ PackageTable { header, buckets, nodes }
}
-pub(crate) fn write_storage_text_to_temp_file(text_proto: &str) -> Result<NamedTempFile> {
- let bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let mut file = NamedTempFile::new()?;
+impl FlagTableNode {
+ // create test baseline, syntactic sugar
+ fn new_expected(
+ package_id: u32,
+ flag_name: &str,
+ flag_type: u16,
+ flag_id: u16,
+ next_offset: Option<u32>,
+ ) -> Self {
+ Self { package_id, flag_name: flag_name.to_string(), flag_type, flag_id, next_offset }
+ }
+}
+
+pub(crate) fn create_test_flag_table() -> FlagTable {
+ let header = FlagTableHeader {
+ version: 1234,
+ container: String::from("system"),
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
+ num_flags: 8,
+ bucket_offset: 31,
+ node_offset: 99,
+ };
+ let buckets: Vec<Option<u32>> = vec![
+ Some(99),
+ Some(125),
+ None,
+ None,
+ None,
+ Some(178),
+ None,
+ Some(204),
+ None,
+ Some(262),
+ None,
+ None,
+ None,
+ None,
+ None,
+ Some(294),
+ None,
+ ];
+ let nodes = vec![
+ FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
+ FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(151)),
+ FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
+ FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
+ FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(236)),
+ FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
+ FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None),
+ FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None),
+ ];
+ FlagTable { header, buckets, nodes }
+}
+
+pub(crate) fn create_test_flag_value_list() -> FlagValueList {
+ let header = FlagValueHeader {
+ version: 1234,
+ container: String::from("system"),
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
+ num_flags: 8,
+ boolean_value_offset: 27,
+ };
+ let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
+ FlagValueList { header, booleans }
+}
+
+pub(crate) fn write_bytes_to_temp_file(bytes: &[u8]) -> Result<NamedTempFile, AconfigStorageError> {
+ let mut file = NamedTempFile::new().map_err(|_| {
+ AconfigStorageError::FileCreationFail(anyhow!("Failed to create temp file"))
+ })?;
let _ = file.write_all(&bytes);
Ok(file)
}
-
-fn set_file_read_only(file: &NamedTempFile) {
- let mut perms = fs::metadata(file.path()).unwrap().permissions();
- if !perms.readonly() {
- perms.set_readonly(true);
- fs::set_permissions(file.path(), perms).unwrap();
- }
-}
-
-fn set_file_read_write(file: &NamedTempFile) {
- let mut perms = fs::metadata(file.path()).unwrap().permissions();
- if perms.readonly() {
- perms.set_readonly(false);
- fs::set_permissions(file.path(), perms).unwrap();
- }
-}
-
-pub(crate) struct TestStorageFile {
- pub file: NamedTempFile,
- pub name: String,
-}
-
-impl TestStorageFile {
- pub(crate) fn new(source_file: &str, read_only: bool) -> Result<Self> {
- let file = NamedTempFile::new()?;
- fs::copy(source_file, file.path())?;
- if read_only {
- set_file_read_only(&file);
- } else {
- set_file_read_write(&file);
- }
- let name = file.path().display().to_string();
- Ok(Self { file, name })
- }
-}
-
-pub(crate) struct TestStorageFileSet {
- pub package_map: TestStorageFile,
- pub flag_map: TestStorageFile,
- pub flag_val: TestStorageFile,
-}
-
-impl TestStorageFileSet {
- pub(crate) fn new(
- package_map_path: &str,
- flag_map_path: &str,
- flag_val_path: &str,
- read_only: bool,
- ) -> Result<Self> {
- Ok(Self {
- package_map: TestStorageFile::new(package_map_path, read_only)?,
- flag_map: TestStorageFile::new(flag_map_path, read_only)?,
- flag_val: TestStorageFile::new(flag_val_path, read_only)?,
- })
- }
-}
diff --git a/tools/aconfig/aconfig_storage_file/tests/Android.bp b/tools/aconfig/aconfig_storage_file/tests/Android.bp
deleted file mode 100644
index b951273..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/Android.bp
+++ /dev/null
@@ -1,42 +0,0 @@
-rust_test {
- name: "aconfig_storage.test.rust",
- srcs: [
- "storage_lib_rust_test.rs"
- ],
- rustlibs: [
- "libanyhow",
- "libaconfig_storage_file",
- "libprotobuf",
- "libtempfile",
- ],
- data: [
- ":ro.package.map",
- ":ro.flag.map",
- ":ro.flag.val",
- ],
- test_suites: ["general-tests"],
-}
-
-cc_test {
- name: "aconfig_storage.test.cpp",
- srcs: [
- "storage_lib_cc_test.cpp",
- ],
- static_libs: [
- "libgmock",
- "libaconfig_storage_protos_cc",
- "libprotobuf-cpp-lite",
- "libaconfig_storage_cc",
- "libbase",
- "liblog",
- ],
- data: [
- ":ro.package.map",
- ":ro.flag.map",
- ":ro.flag.val",
- ],
- test_suites: [
- "device-tests",
- "general-tests",
- ],
-}
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.map b/tools/aconfig/aconfig_storage_file/tests/flag.map
deleted file mode 100644
index 43b6f9a..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/flag.map
+++ /dev/null
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.val b/tools/aconfig/aconfig_storage_file/tests/flag.val
deleted file mode 100644
index f39f8d3..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/flag.val
+++ /dev/null
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/package.map b/tools/aconfig/aconfig_storage_file/tests/package.map
deleted file mode 100644
index 8ed4767..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/package.map
+++ /dev/null
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp b/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp
deleted file mode 100644
index 7d5ba0a..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <string>
-#include <vector>
-
-#include "aconfig_storage/aconfig_storage.hpp"
-#include <gtest/gtest.h>
-#include <protos/aconfig_storage_metadata.pb.h>
-#include <android-base/file.h>
-
-using android::aconfig_storage_metadata::storage_files;
-using ::android::base::WriteStringToFile;
-using ::aconfig_storage::test_only_api::get_package_offset_impl;
-using ::aconfig_storage::test_only_api::get_flag_offset_impl;
-using ::aconfig_storage::test_only_api::get_boolean_flag_value_impl;
-
-void write_storage_location_pb_to_file(std::string const& file_path) {
- auto const test_dir = android::base::GetExecutableDirectory();
- auto proto = storage_files();
- auto* info = proto.add_files();
- info->set_version(0);
- info->set_container("system");
- info->set_package_map(test_dir + "/tests/tmp.ro.package.map");
- info->set_flag_map(test_dir + "/tests/tmp.ro.flag.map");
- info->set_flag_val(test_dir + "/tests/tmp.ro.flag.val");
- info->set_timestamp(12345);
-
- auto content = std::string();
- proto.SerializeToString(&content);
- ASSERT_TRUE(WriteStringToFile(content, file_path))
- << "Failed to write a file: " << file_path;
-}
-
-TEST(AconfigStorageTest, test_package_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_1");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.package_exists);
- ASSERT_EQ(query.package_id, 0);
- ASSERT_EQ(query.boolean_offset, 0);
-
- query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_2");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.package_exists);
- ASSERT_EQ(query.package_id, 1);
- ASSERT_EQ(query.boolean_offset, 3);
-
- query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_4");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.package_exists);
- ASSERT_EQ(query.package_id, 2);
- ASSERT_EQ(query.boolean_offset, 6);
-}
-
-TEST(AconfigStorageTest, test_invalid_package_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_3");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.package_exists);
-
- query = get_package_offset_impl(
- pb_file, "vendor", "com.android.aconfig.storage.test_1");
- ASSERT_EQ(query.error_message,
- std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
- ASSERT_FALSE(query.query_success);
-}
-
-TEST(AconfigStorageTest, test_flag_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto baseline = std::vector<std::tuple<int, std::string, int>>{
- {0, "enabled_ro", 1},
- {0, "enabled_rw", 2},
- {1, "disabled_ro", 0},
- {2, "enabled_ro", 1},
- {1, "enabled_fixed_ro", 1},
- {1, "enabled_ro", 2},
- {2, "enabled_fixed_ro", 0},
- {0, "disabled_rw", 0},
- };
- for (auto const&[package_id, flag_name, expected_offset] : baseline) {
- auto query = get_flag_offset_impl(pb_file, "system", package_id, flag_name);
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.flag_exists);
- ASSERT_EQ(query.flag_offset, expected_offset);
- }
-}
-
-TEST(AconfigStorageTest, test_invalid_flag_offset_query) {
- auto pb_file = std::string("/tmp/test_invalid_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_flag_offset_impl(pb_file, "system", 0, "none_exist");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.flag_exists);
-
- query = get_flag_offset_impl(pb_file, "system", 3, "enabled_ro");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.flag_exists);
-
- query = get_flag_offset_impl(pb_file, "vendor", 0, "enabled_ro");
- ASSERT_EQ(query.error_message,
- std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
- ASSERT_FALSE(query.query_success);
-}
-
-TEST(AconfigStorageTest, test_boolean_flag_value_query) {
- auto pb_file = std::string("/tmp/test_boolean_flag_value_query.pb");
- write_storage_location_pb_to_file(pb_file);
- for (int offset = 0; offset < 8; ++offset) {
- auto query = get_boolean_flag_value_impl(pb_file, "system", offset);
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.flag_value);
- }
-}
-
-TEST(AconfigStorageTest, test_invalid_boolean_flag_value_query) {
- auto pb_file = std::string("/tmp/test_invalid_boolean_flag_value_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_boolean_flag_value_impl(pb_file, "vendor", 0);
- ASSERT_EQ(query.error_message,
- std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
- ASSERT_FALSE(query.query_success);
-
- query = get_boolean_flag_value_impl(pb_file, "system", 8);
- ASSERT_EQ(query.error_message,
- std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
- ASSERT_FALSE(query.query_success);
-}
diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs b/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs
deleted file mode 100644
index 9916915..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-#[cfg(not(feature = "cargo"))]
-mod aconfig_storage_rust_test {
- use aconfig_storage_file::{
- get_boolean_flag_value_impl, get_flag_offset_impl, get_package_offset_impl, PackageOffset,
- ProtoStorageFiles,
- };
- use protobuf::Message;
- use std::io::Write;
- use tempfile::NamedTempFile;
-
- fn write_storage_location_file() -> NamedTempFile {
- let text_proto = r#"
-files {
- version: 0
- container: "system"
- package_map: "./tests/tmp.ro.package.map"
- flag_map: "./tests/tmp.ro.flag.map"
- flag_val: "./tests/tmp.ro.flag.val"
- timestamp: 12345
-}
-"#;
- let storage_files: ProtoStorageFiles =
- protobuf::text_format::parse_from_str(text_proto).unwrap();
- let mut binary_proto_bytes = Vec::new();
- storage_files.write_to_vec(&mut binary_proto_bytes).unwrap();
- let mut file = NamedTempFile::new().unwrap();
- file.write_all(&binary_proto_bytes).unwrap();
- file
- }
-
- #[test]
- fn test_package_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_1",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_2",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_4",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_3",
- )
- .unwrap();
- assert_eq!(package_offset, None);
- }
-
- #[test]
- fn test_invalid_package_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let package_offset_option = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_3",
- )
- .unwrap();
- assert_eq!(package_offset_option, None);
-
- let err = get_package_offset_impl(
- &file_full_path,
- "vendor",
- "com.android.aconfig.storage.test_1",
- )
- .unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
- }
-
- #[test]
- fn test_flag_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let baseline = vec![
- (0, "enabled_ro", 1u16),
- (0, "enabled_rw", 2u16),
- (1, "disabled_ro", 0u16),
- (2, "enabled_ro", 1u16),
- (1, "enabled_fixed_ro", 1u16),
- (1, "enabled_ro", 2u16),
- (2, "enabled_fixed_ro", 0u16),
- (0, "disabled_rw", 0u16),
- ];
- for (package_id, flag_name, expected_offset) in baseline.into_iter() {
- let flag_offset =
- get_flag_offset_impl(&file_full_path, "system", package_id, flag_name)
- .unwrap()
- .unwrap();
- assert_eq!(flag_offset, expected_offset);
- }
- }
-
- #[test]
- fn test_invalid_flag_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let flag_offset_option =
- get_flag_offset_impl(&file_full_path, "system", 0, "none_exist").unwrap();
- assert_eq!(flag_offset_option, None);
-
- let flag_offset_option =
- get_flag_offset_impl(&file_full_path, "system", 3, "enabled_ro").unwrap();
- assert_eq!(flag_offset_option, None);
-
- let err = get_flag_offset_impl(&file_full_path, "vendor", 0, "enabled_ro").unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
- }
-
- #[test]
- fn test_boolean_flag_value_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let baseline: Vec<bool> = vec![false; 8];
- for (offset, expected_value) in baseline.into_iter().enumerate() {
- let flag_value =
- get_boolean_flag_value_impl(&file_full_path, "system", offset as u32).unwrap();
- assert_eq!(flag_value, expected_value);
- }
- }
-
- #[test]
- fn test_invalid_boolean_flag_value_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let err = get_boolean_flag_value_impl(&file_full_path, "vendor", 0u32).unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
-
- let err = get_boolean_flag_value_impl(&file_full_path, "system", 8u32).unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
- );
- }
-}
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
new file mode 100644
index 0000000..5006161
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -0,0 +1,81 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "aconfig_storage_read_api.defaults",
+ edition: "2021",
+ lints: "none",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libonce_cell",
+ "libtempfile",
+ "libmemmap2",
+ "libcxx",
+ "libthiserror",
+ "libaconfig_storage_file",
+ ],
+}
+
+rust_library {
+ name: "libaconfig_storage_read_api",
+ crate_name: "aconfig_storage_read_api",
+ host_supported: true,
+ defaults: ["aconfig_storage_read_api.defaults"],
+}
+
+rust_test_host {
+ name: "aconfig_storage_read_api.test",
+ test_suites: ["general-tests"],
+ defaults: ["aconfig_storage_read_api.defaults"],
+ data: [
+ "tests/package.map",
+ "tests/flag.map",
+ "tests/flag.val",
+ ],
+}
+
+// cxx source codegen from rust api
+genrule {
+ name: "libcxx_aconfig_storage_read_api_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) > $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["aconfig_storage/lib.rs.cc"],
+}
+
+// cxx header codegen from rust api
+genrule {
+ name: "libcxx_aconfig_storage_read_api_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header > $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["aconfig_storage/lib.rs.h"],
+}
+
+// a static cc lib based on generated code
+rust_ffi_static {
+ name: "libaconfig_storage_read_api_cxx_bridge",
+ crate_name: "aconfig_storage_read_api_cxx_bridge",
+ host_supported: true,
+ defaults: ["aconfig_storage_read_api.defaults"],
+}
+
+// flag read api cc interface
+cc_library_static {
+ name: "libaconfig_storage_read_api_cc",
+ srcs: ["aconfig_storage_read_api.cpp"],
+ generated_headers: [
+ "cxx-bridge-header",
+ "libcxx_aconfig_storage_read_api_bridge_header"
+ ],
+ generated_sources: ["libcxx_aconfig_storage_read_api_bridge_code"],
+ whole_static_libs: ["libaconfig_storage_read_api_cxx_bridge"],
+ export_include_dirs: ["include"],
+ static_libs: [
+ "libaconfig_storage_protos_cc",
+ "libprotobuf-cpp-lite",
+ "libbase",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/Cargo.toml b/tools/aconfig/aconfig_storage_read_api/Cargo.toml
new file mode 100644
index 0000000..30a4298
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "aconfig_storage_read_api"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+memmap2 = "0.8.0"
+once_cell = "1.19.0"
+tempfile = "3.9.0"
+cxx = "1.0"
+thiserror = "1.0.56"
+aconfig_storage_file = { path = "../aconfig_storage_file" }
+
+[build-dependencies]
+protobuf-codegen = "3.2.0"
+cxx-build = "1.0"
diff --git a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
new file mode 100644
index 0000000..ceb5a96
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
@@ -0,0 +1,198 @@
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <protos/aconfig_storage_metadata.pb.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "rust/cxx.h"
+#include "aconfig_storage/lib.rs.h"
+#include "aconfig_storage/aconfig_storage_read_api.hpp"
+
+using storage_records_pb = android::aconfig_storage_metadata::storage_files;
+using storage_record_pb = android::aconfig_storage_metadata::storage_file_info;
+using namespace android::base;
+
+namespace aconfig_storage {
+
+/// Storage location pb file
+static constexpr char kAvailableStorageRecordsPb[] =
+ "/metadata/aconfig/available_storage_file_records.pb";
+
+/// Read aconfig storage records pb file
+static Result<storage_records_pb> read_storage_records_pb(std::string const& pb_file) {
+ auto records = storage_records_pb();
+ auto content = std::string();
+ if (!ReadFileToString(pb_file, &content)) {
+ return ErrnoError() << "ReadFileToString failed";
+ }
+
+ if (!records.ParseFromString(content)) {
+ return ErrnoError() << "Unable to parse persistent storage records protobuf";
+ }
+ return records;
+}
+
+/// Get storage file path
+static Result<std::string> find_storage_file(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type) {
+ auto records_pb = read_storage_records_pb(pb_file);
+ if (!records_pb.ok()) {
+ return Error() << "Unable to read storage records from " << pb_file
+ << " : " << records_pb.error();
+ }
+
+ for (auto& entry : records_pb->files()) {
+ if (entry.container() == container) {
+ switch(file_type) {
+ case StorageFileType::package_map:
+ return entry.package_map();
+ case StorageFileType::flag_map:
+ return entry.flag_map();
+ case StorageFileType::flag_val:
+ return entry.flag_val();
+ default:
+ return Error() << "Invalid file type " << file_type;
+ }
+ }
+ }
+
+ return Error() << "Unable to find storage files for container " << container;;
+}
+
+/// Map a storage file
+static Result<MappedStorageFile> map_storage_file(std::string const& file) {
+ int fd = open(file.c_str(), O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
+ if (fd == -1) {
+ return Error() << "failed to open " << file;
+ };
+
+ struct stat fd_stat;
+ if (fstat(fd, &fd_stat) < 0) {
+ return Error() << "fstat failed";
+ }
+
+ if ((fd_stat.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0) {
+ return Error() << "cannot map writeable file";
+ }
+
+ size_t file_size = fd_stat.st_size;
+
+ void* const map_result = mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (map_result == MAP_FAILED) {
+ return Error() << "mmap failed";
+ }
+
+ auto mapped_file = MappedStorageFile();
+ mapped_file.file_ptr = map_result;
+ mapped_file.file_size = file_size;
+
+ return mapped_file;
+}
+
+namespace private_internal_api {
+
+/// Get mapped file implementation.
+MappedStorageFileQuery get_mapped_file_impl(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type) {
+ auto query = MappedStorageFileQuery();
+
+ auto file_result = find_storage_file(pb_file, container, file_type);
+ if (!file_result.ok()) {
+ query.query_success = false;
+ query.error_message = file_result.error().message();
+ query.mapped_file.file_ptr = nullptr;
+ query.mapped_file.file_size = 0;
+ return query;
+ }
+
+ auto mapped_file_result = map_storage_file(*file_result);
+ if (!mapped_file_result.ok()) {
+ query.query_success = false;
+ query.error_message = mapped_file_result.error().message();
+ query.mapped_file.file_ptr = nullptr;
+ query.mapped_file.file_size = 0;
+ } else {
+ query.query_success = true;
+ query.error_message = "";
+ query.mapped_file = *mapped_file_result;
+ }
+
+ return query;
+}
+
+} // namespace private internal api
+
+/// Get mapped storage file
+MappedStorageFileQuery get_mapped_file(
+ std::string const& container,
+ StorageFileType file_type) {
+ return private_internal_api::get_mapped_file_impl(
+ kAvailableStorageRecordsPb, container, file_type);
+}
+
+/// Get storage file version number
+VersionNumberQuery get_storage_file_version(
+ std::string const& file_path) {
+ auto version_cxx = get_storage_file_version_cxx(
+ rust::Str(file_path.c_str()));
+ auto version = VersionNumberQuery();
+ version.query_success = version_cxx.query_success;
+ version.error_message = std::string(version_cxx.error_message.c_str());
+ version.version_number = version_cxx.version_number;
+ return version;
+}
+
+/// Get package offset
+PackageOffsetQuery get_package_offset(
+ MappedStorageFile const& file,
+ std::string const& package) {
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto offset_cxx = get_package_offset_cxx(content, rust::Str(package.c_str()));
+ auto offset = PackageOffsetQuery();
+ offset.query_success = offset_cxx.query_success;
+ offset.error_message = std::string(offset_cxx.error_message.c_str());
+ offset.package_exists = offset_cxx.package_exists;
+ offset.package_id = offset_cxx.package_id;
+ offset.boolean_offset = offset_cxx.boolean_offset;
+ return offset;
+}
+
+/// Get flag offset
+FlagOffsetQuery get_flag_offset(
+ MappedStorageFile const& file,
+ uint32_t package_id,
+ std::string const& flag_name){
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto offset_cxx = get_flag_offset_cxx(content, package_id, rust::Str(flag_name.c_str()));
+ auto offset = FlagOffsetQuery();
+ offset.query_success = offset_cxx.query_success;
+ offset.error_message = std::string(offset_cxx.error_message.c_str());
+ offset.flag_exists = offset_cxx.flag_exists;
+ offset.flag_offset = offset_cxx.flag_offset;
+ return offset;
+}
+
+/// Get boolean flag value
+BooleanFlagValueQuery get_boolean_flag_value(
+ MappedStorageFile const& file,
+ uint32_t offset) {
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto value_cxx = get_boolean_flag_value_cxx(content, offset);
+ auto value = BooleanFlagValueQuery();
+ value.query_success = value_cxx.query_success;
+ value.error_message = std::string(value_cxx.error_message.c_str());
+ value.flag_value = value_cxx.flag_value;
+ return value;
+}
+
+} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_read_api/build.rs b/tools/aconfig/aconfig_storage_read_api/build.rs
new file mode 100644
index 0000000..7b1aa53
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+ let _ = cxx_build::bridge("src/lib.rs");
+ println!("cargo:rerun-if-changed=src/lib.rs");
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
new file mode 100644
index 0000000..92c03e0
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include <stdint.h>
+#include <string>
+
+namespace aconfig_storage {
+
+/// Storage file type enum
+enum StorageFileType {
+ package_map,
+ flag_map,
+ flag_val
+};
+
+/// Mapped storage file
+struct MappedStorageFile {
+ void* file_ptr;
+ size_t file_size;
+};
+
+/// Mapped storage file query
+struct MappedStorageFileQuery {
+ bool query_success;
+ std::string error_message;
+ MappedStorageFile mapped_file;
+};
+
+/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY
+namespace private_internal_api {
+
+MappedStorageFileQuery get_mapped_file_impl(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type);
+
+} // namespace private_internal_api
+
+/// Storage version number query result
+struct VersionNumberQuery {
+ bool query_success;
+ std::string error_message;
+ uint32_t version_number;
+};
+
+/// Package offset query result
+struct PackageOffsetQuery {
+ bool query_success;
+ std::string error_message;
+ bool package_exists;
+ uint32_t package_id;
+ uint32_t boolean_offset;
+};
+
+/// Flag offset query result
+struct FlagOffsetQuery {
+ bool query_success;
+ std::string error_message;
+ bool flag_exists;
+ uint16_t flag_offset;
+};
+
+/// Boolean flag value query result
+struct BooleanFlagValueQuery {
+ bool query_success;
+ std::string error_message;
+ bool flag_value;
+};
+
+/// Get mapped storage file
+/// \input container: stoarge container name
+/// \input file_type: storage file type enum
+/// \returns a MappedStorageFileQuery
+MappedStorageFileQuery get_mapped_file(
+ std::string const& container,
+ StorageFileType file_type);
+
+/// Get storage file version number
+/// \input file_path: the path to the storage file
+/// \returns a VersionNumberQuery
+VersionNumberQuery get_storage_file_version(
+ std::string const& file_path);
+
+/// Get package offset
+/// \input file: mapped storage file
+/// \input package: the flag package name
+/// \returns a PackageOffsetQuery
+PackageOffsetQuery get_package_offset(
+ MappedStorageFile const& file,
+ std::string const& package);
+
+/// Get flag offset
+/// \input file: mapped storage file
+/// \input package_id: the flag package id obtained from package offset query
+/// \input flag_name: flag name
+/// \returns a FlagOffsetQuery
+FlagOffsetQuery get_flag_offset(
+ MappedStorageFile const& file,
+ uint32_t package_id,
+ std::string const& flag_name);
+
+/// Get boolean flag value
+/// \input file: mapped storage file
+/// \input offset: the boolean flag value byte offset in the file
+/// \returns a BooleanFlagValueQuery
+BooleanFlagValueQuery get_boolean_flag_value(
+ MappedStorageFile const& file,
+ uint32_t offset);
+
+} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
new file mode 100644
index 0000000..403badb
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! flag table query module defines the flag table file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{
+ flag_table::FlagTableHeader, flag_table::FlagTableNode, read_u32_from_bytes,
+};
+use anyhow::anyhow;
+
+pub type FlagOffset = u16;
+
+/// Query flag within package offset
+pub fn find_flag_offset(
+ buf: &[u8],
+ package_id: u32,
+ flag: &str,
+) -> Result<Option<FlagOffset>, AconfigStorageError> {
+ let interpreted_header = FlagTableHeader::from_bytes(buf)?;
+ if interpreted_header.version > crate::FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
+ let bucket_index = FlagTableNode::find_bucket_index(package_id, flag, num_buckets);
+
+ let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
+ let mut flag_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
+ if flag_node_offset < interpreted_header.node_offset as usize
+ || flag_node_offset >= interpreted_header.file_size as usize
+ {
+ return Ok(None);
+ }
+
+ loop {
+ let interpreted_node = FlagTableNode::from_bytes(&buf[flag_node_offset..])?;
+ if interpreted_node.package_id == package_id && interpreted_node.flag_name == flag {
+ return Ok(Some(interpreted_node.flag_id));
+ }
+ match interpreted_node.next_offset {
+ Some(offset) => flag_node_offset = offset as usize,
+ None => return Ok(None),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::{StorageFileType, FlagTable};
+
+ // create test baseline, syntactic sugar
+ fn new_expected_node(
+ package_id: u32,
+ flag_name: &str,
+ flag_type: u16,
+ flag_id: u16,
+ next_offset: Option<u32>,
+ ) -> FlagTableNode {
+ FlagTableNode {
+ package_id,
+ flag_name: flag_name.to_string(),
+ flag_type,
+ flag_id,
+ next_offset,
+ }
+ }
+
+ pub fn create_test_flag_table() -> FlagTable {
+ let header = FlagTableHeader {
+ version: crate::FILE_VERSION,
+ container: String::from("system"),
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
+ num_flags: 8,
+ bucket_offset: 31,
+ node_offset: 99,
+ };
+ let buckets: Vec<Option<u32>> = vec![
+ Some(99),
+ Some(125),
+ None,
+ None,
+ None,
+ Some(178),
+ None,
+ Some(204),
+ None,
+ Some(262),
+ None,
+ None,
+ None,
+ None,
+ None,
+ Some(294),
+ None,
+ ];
+ let nodes = vec![
+ new_expected_node(0, "enabled_ro", 1, 1, None),
+ new_expected_node(0, "enabled_rw", 1, 2, Some(151)),
+ new_expected_node(1, "disabled_ro", 1, 0, None),
+ new_expected_node(2, "enabled_ro", 1, 1, None),
+ new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(236)),
+ new_expected_node(1, "enabled_ro", 1, 2, None),
+ new_expected_node(2, "enabled_fixed_ro", 1, 0, None),
+ new_expected_node(0, "disabled_rw", 1, 0, None),
+ ];
+ FlagTable { header, buckets, nodes }
+ }
+
+ #[test]
+ // this test point locks down table query
+ fn test_flag_query() {
+ let flag_table = create_test_flag_table().as_bytes();
+ let baseline = vec![
+ (0, "enabled_ro", 1u16),
+ (0, "enabled_rw", 2u16),
+ (1, "disabled_ro", 0u16),
+ (2, "enabled_ro", 1u16),
+ (1, "enabled_fixed_ro", 1u16),
+ (1, "enabled_ro", 2u16),
+ (2, "enabled_fixed_ro", 0u16),
+ (0, "disabled_rw", 0u16),
+ ];
+ for (package_id, flag_name, expected_offset) in baseline.into_iter() {
+ let flag_offset =
+ find_flag_offset(&flag_table[..], package_id, flag_name).unwrap().unwrap();
+ assert_eq!(flag_offset, expected_offset);
+ }
+ }
+
+ #[test]
+ // this test point locks down table query of a non exist flag
+ fn test_not_existed_flag_query() {
+ let flag_table = create_test_flag_table().as_bytes();
+ let flag_offset = find_flag_offset(&flag_table[..], 1, "disabled_fixed_ro").unwrap();
+ assert_eq!(flag_offset, None);
+ let flag_offset = find_flag_offset(&flag_table[..], 2, "disabled_rw").unwrap();
+ assert_eq!(flag_offset, None);
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut table = create_test_flag_table();
+ table.header.version = crate::FILE_VERSION + 1;
+ let flag_table = table.as_bytes();
+ let error = find_flag_offset(&flag_table[..], 0, "enabled_ro").unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
new file mode 100644
index 0000000..4b7a65e
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! flag value query module defines the flag value file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{flag_value::FlagValueHeader, read_u8_from_bytes};
+use anyhow::anyhow;
+
+/// Query flag value
+pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, AconfigStorageError> {
+ let interpreted_header = FlagValueHeader::from_bytes(buf)?;
+ if interpreted_header.version > crate::FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let mut head = (interpreted_header.boolean_value_offset + flag_offset) as usize;
+
+ // TODO: right now, there is only boolean flags, with more flag value types added
+ // later, the end of boolean flag value section should be updated (b/322826265).
+ if head >= interpreted_header.file_size as usize {
+ return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!(
+ "Flag value offset goes beyond the end of the file."
+ )));
+ }
+
+ let val = read_u8_from_bytes(buf, &mut head)?;
+ Ok(val == 1)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::{StorageFileType, FlagValueList};
+
+ pub fn create_test_flag_value_list() -> FlagValueList {
+ let header = FlagValueHeader {
+ version: crate::FILE_VERSION,
+ container: String::from("system"),
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
+ num_flags: 8,
+ boolean_value_offset: 27,
+ };
+ let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
+ FlagValueList { header, booleans }
+ }
+
+ #[test]
+ // this test point locks down flag value query
+ fn test_flag_value_query() {
+ let flag_value_list = create_test_flag_value_list().as_bytes();
+ let baseline: Vec<bool> = vec![false, true, false, false, true, true, false, true];
+ for (offset, expected_value) in baseline.into_iter().enumerate() {
+ let flag_value = find_boolean_flag_value(&flag_value_list[..], offset as u32).unwrap();
+ assert_eq!(flag_value, expected_value);
+ }
+ }
+
+ #[test]
+ // this test point locks down query beyond the end of boolean section
+ fn test_boolean_out_of_range() {
+ let flag_value_list = create_test_flag_value_list().as_bytes();
+ let error = find_boolean_flag_value(&flag_value_list[..], 8).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut value_list = create_test_flag_value_list();
+ value_list.header.version = crate::FILE_VERSION + 1;
+ let flag_value = value_list.as_bytes();
+ let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
new file mode 100644
index 0000000..87372c6
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! `aconfig_storage_read_api` is a crate that defines read apis to read flags from storage
+//! files. It provides four apis to interface with storage files:
+//!
+//! 1, function to get package flag value start offset
+//! pub fn get_package_offset(container: &str, package: &str) -> `Result<Option<PackageOffset>>>`
+//!
+//! 2, function to get flag offset within a specific package
+//! pub fn get_flag_offset(container: &str, package_id: u32, flag: &str) -> `Result<Option<u16>>>`
+//!
+//! 3, function to get the actual flag value given the global offset (combined package and
+//! flag offset).
+//! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
+//!
+//! 4, function to get storage file version without mmapping the file.
+//! pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>
+//!
+//! Note these are low level apis that are expected to be only used in auto generated flag
+//! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
+//! please refer to the g3doc go/android-flags
+
+pub mod flag_table_query;
+pub mod flag_value_query;
+pub mod mapped_file;
+pub mod package_table_query;
+
+#[cfg(test)]
+mod test_utils;
+
+pub use aconfig_storage_file::{AconfigStorageError, StorageFileType};
+pub use flag_table_query::FlagOffset;
+pub use package_table_query::PackageOffset;
+
+use aconfig_storage_file::{read_u32_from_bytes, FILE_VERSION};
+use flag_table_query::find_flag_offset;
+use flag_value_query::find_boolean_flag_value;
+use package_table_query::find_package_offset;
+
+use anyhow::anyhow;
+use memmap2::Mmap;
+use std::fs::File;
+use std::io::Read;
+
+/// Storage file location pb file
+pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/available_storage_file_records.pb";
+
+/// Get read only mapped storage files.
+///
+/// \input container: the flag package container
+/// \input file_type: stoarge file type enum
+/// \return a result of read only mapped file
+pub fn get_mapped_storage_file(
+ container: &str,
+ file_type: StorageFileType,
+) -> Result<Mmap, AconfigStorageError> {
+ crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container, file_type)
+}
+
+/// Get package start offset for flags.
+///
+/// \input file: mapped package file
+/// \input package: package name
+///
+/// \return
+/// If a package is found, it returns Ok(Some(PackageOffset))
+/// If a package is not found, it returns Ok(None)
+/// If errors out, it returns an Err(errmsg)
+pub fn get_package_offset(
+ file: &Mmap,
+ package: &str,
+) -> Result<Option<PackageOffset>, AconfigStorageError> {
+ find_package_offset(file, package)
+}
+
+/// Get flag offset within a package given.
+///
+/// \input file: mapped flag file
+/// \input package_id: package id obtained from package mapping file
+/// \input flag: flag name
+///
+/// \return
+/// If a flag is found, it returns Ok(Some(u16))
+/// If a flag is not found, it returns Ok(None)
+/// If errors out, it returns an Err(errmsg)
+pub fn get_flag_offset(
+ file: &Mmap,
+ package_id: u32,
+ flag: &str,
+) -> Result<Option<FlagOffset>, AconfigStorageError> {
+ find_flag_offset(file, package_id, flag)
+}
+
+/// Get the boolean flag value.
+///
+/// \input file: mapped flag file
+/// \input offset: flag value offset
+///
+/// \return
+/// If the provide offset is valid, it returns the boolean flag value, otherwise it
+/// returns the error message.
+pub fn get_boolean_flag_value(file: &Mmap, offset: u32) -> Result<bool, AconfigStorageError> {
+ find_boolean_flag_value(file, offset)
+}
+
+/// Get storage file version number
+///
+/// This function would read the first four bytes of the file and interpret it as the
+/// version number of the file. There are unit tests in aconfig_storage_file crate to
+/// lock down that for all storage files, the first four bytes will be the version
+/// number of the storage file
+pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError> {
+ let mut file = File::open(file_path).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+ let mut buffer = [0; 4];
+ file.read(&mut buffer).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!(
+ "Failed to read 4 bytes from file {}: {}",
+ file_path,
+ errmsg
+ ))
+ })?;
+ let mut head = 0;
+ read_u32_from_bytes(&buffer, &mut head)
+}
+
+// *************************************** //
+// CC INTERLOP
+// *************************************** //
+
+// Exported rust data structure and methods, c++ code will be generated
+#[cxx::bridge]
+mod ffi {
+ // Storage file version query return for cc interlop
+ pub struct VersionNumberQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub version_number: u32,
+ }
+
+ // Package table query return for cc interlop
+ pub struct PackageOffsetQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub package_exists: bool,
+ pub package_id: u32,
+ pub boolean_offset: u32,
+ }
+
+ // Flag table query return for cc interlop
+ pub struct FlagOffsetQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub flag_exists: bool,
+ pub flag_offset: u16,
+ }
+
+ // Flag value query return for cc interlop
+ pub struct BooleanFlagValueQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub flag_value: bool,
+ }
+
+ // Rust export to c++
+ extern "Rust" {
+ pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX;
+
+ pub fn get_package_offset_cxx(file: &[u8], package: &str) -> PackageOffsetQueryCXX;
+
+ pub fn get_flag_offset_cxx(
+ file: &[u8],
+ package_id: u32,
+ flag: &str,
+ ) -> FlagOffsetQueryCXX;
+
+ pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32)
+ -> BooleanFlagValueQueryCXX;
+ }
+}
+
+/// Implement the package offset interlop return type, create from actual package offset api return type
+impl ffi::PackageOffsetQueryCXX {
+ pub(crate) fn new(offset_result: Result<Option<PackageOffset>, AconfigStorageError>) -> Self {
+ match offset_result {
+ Ok(offset_opt) => match offset_opt {
+ Some(offset) => Self {
+ query_success: true,
+ error_message: String::from(""),
+ package_exists: true,
+ package_id: offset.package_id,
+ boolean_offset: offset.boolean_offset,
+ },
+ None => Self {
+ query_success: true,
+ error_message: String::from(""),
+ package_exists: false,
+ package_id: 0,
+ boolean_offset: 0,
+ },
+ },
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ package_exists: false,
+ package_id: 0,
+ boolean_offset: 0,
+ },
+ }
+ }
+}
+
+/// Implement the flag offset interlop return type, create from actual flag offset api return type
+impl ffi::FlagOffsetQueryCXX {
+ pub(crate) fn new(offset_result: Result<Option<FlagOffset>, AconfigStorageError>) -> Self {
+ match offset_result {
+ Ok(offset_opt) => match offset_opt {
+ Some(offset) => Self {
+ query_success: true,
+ error_message: String::from(""),
+ flag_exists: true,
+ flag_offset: offset,
+ },
+ None => Self {
+ query_success: true,
+ error_message: String::from(""),
+ flag_exists: false,
+ flag_offset: 0,
+ },
+ },
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ flag_exists: false,
+ flag_offset: 0,
+ },
+ }
+ }
+}
+
+/// Implement the flag value interlop return type, create from actual flag value api return type
+impl ffi::BooleanFlagValueQueryCXX {
+ pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
+ match value_result {
+ Ok(value) => {
+ Self { query_success: true, error_message: String::from(""), flag_value: value }
+ }
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ flag_value: false,
+ },
+ }
+ }
+}
+
+/// Implement the storage version number interlop return type, create from actual version number
+/// api return type
+impl ffi::VersionNumberQueryCXX {
+ pub(crate) fn new(version_result: Result<u32, AconfigStorageError>) -> Self {
+ match version_result {
+ Ok(version) => Self {
+ query_success: true,
+ error_message: String::from(""),
+ version_number: version,
+ },
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ version_number: 0,
+ },
+ }
+ }
+}
+
+/// Get package start offset cc interlop
+pub fn get_package_offset_cxx(file: &[u8], package: &str) -> ffi::PackageOffsetQueryCXX {
+ ffi::PackageOffsetQueryCXX::new(find_package_offset(file, package))
+}
+
+/// Get flag start offset cc interlop
+pub fn get_flag_offset_cxx(
+ file: &[u8],
+ package_id: u32,
+ flag: &str,
+) -> ffi::FlagOffsetQueryCXX {
+ ffi::FlagOffsetQueryCXX::new(find_flag_offset(file, package_id, flag))
+}
+
+/// Get boolean flag value cc interlop
+pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX {
+ ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset))
+}
+
+/// Get storage version number cc interlop
+pub fn get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX {
+ ffi::VersionNumberQueryCXX::new(get_storage_file_version(file_path))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::mapped_file::get_mapped_file;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use tempfile::NamedTempFile;
+
+ fn create_test_storage_files(read_only: bool) -> [NamedTempFile; 4] {
+ let package_map = copy_to_temp_file("./tests/package.map", read_only).unwrap();
+ let flag_map = copy_to_temp_file("./tests/flag.map", read_only).unwrap();
+ let flag_val = copy_to_temp_file("./tests/flag.val", read_only).unwrap();
+
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ package_map.path().display(),
+ flag_map.path().display(),
+ flag_val.path().display()
+ );
+ let pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ [package_map, flag_map, flag_val, pb_file]
+ }
+
+ #[test]
+ // this test point locks down flag package offset query
+ fn test_package_offset_query() {
+ let [package_map, flag_map, flag_val, pb_file] = create_test_storage_files(true);
+ let pb_file_path = pb_file.path().display().to_string();
+ let package_mapped_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::PackageMap).unwrap();
+
+ let package_offset =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_1")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
+ assert_eq!(package_offset, expected_package_offset);
+
+ let package_offset =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_2")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
+ assert_eq!(package_offset, expected_package_offset);
+
+ let package_offset =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_4")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
+ assert_eq!(package_offset, expected_package_offset);
+ }
+
+ #[test]
+ // this test point locks down flag offset query
+ fn test_flag_offset_query() {
+ let [package_map, flag_map, flag_val, pb_file] = create_test_storage_files(true);
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_mapped_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::FlagMap).unwrap();
+
+ let baseline = vec![
+ (0, "enabled_ro", 1u16),
+ (0, "enabled_rw", 2u16),
+ (1, "disabled_ro", 0u16),
+ (2, "enabled_ro", 1u16),
+ (1, "enabled_fixed_ro", 1u16),
+ (1, "enabled_ro", 2u16),
+ (2, "enabled_fixed_ro", 0u16),
+ (0, "disabled_rw", 0u16),
+ ];
+ for (package_id, flag_name, expected_offset) in baseline.into_iter() {
+ let flag_offset =
+ get_flag_offset(&flag_mapped_file, package_id, flag_name).unwrap().unwrap();
+ assert_eq!(flag_offset, expected_offset);
+ }
+ }
+
+ #[test]
+ // this test point locks down flag offset query
+ fn test_flag_value_query() {
+ let [package_map, flag_map, flag_val, pb_file] = create_test_storage_files(true);
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_value_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::FlagVal).unwrap();
+ let baseline: Vec<bool> = vec![false; 8];
+ for (offset, expected_value) in baseline.into_iter().enumerate() {
+ let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
+ assert_eq!(flag_value, expected_value);
+ }
+ }
+
+ #[test]
+ // this test point locks down flag storage file version number query api
+ fn test_storage_version_query() {
+ let _ro_files = create_test_storage_files(true);
+ assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 1);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
new file mode 100644
index 0000000..09ecdb6
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use std::fs::{self, File};
+use std::io::{BufReader, Read};
+
+use anyhow::anyhow;
+use memmap2::Mmap;
+
+use crate::AconfigStorageError::{
+ self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
+};
+use crate::StorageFileType;
+use aconfig_storage_file::protos::{
+ storage_record_pb::try_from_binary_proto, ProtoStorageFileInfo, ProtoStorageFiles,
+};
+
+/// Find where storage files are stored for a particular container
+fn find_container_storage_location(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<ProtoStorageFileInfo, AconfigStorageError> {
+ let file = File::open(location_pb_file).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", location_pb_file, errmsg))
+ })?;
+ let mut reader = BufReader::new(file);
+ let mut bytes = Vec::new();
+ reader.read_to_end(&mut bytes).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to read file {}: {}", location_pb_file, errmsg))
+ })?;
+ let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes).map_err(|errmsg| {
+ ProtobufParseFail(anyhow!(
+ "Failed to parse storage location pb file {}: {}",
+ location_pb_file,
+ errmsg
+ ))
+ })?;
+ for location_info in storage_locations.files.iter() {
+ if location_info.container() == container {
+ return Ok(location_info.clone());
+ }
+ }
+ Err(StorageFileNotFound(anyhow!("Storage file does not exist for {}", container)))
+}
+
+/// Verify the file is read only and then map it
+fn verify_read_only_and_map(file_path: &str) -> Result<Mmap, AconfigStorageError> {
+ // ensure file has read only permission
+ let perms = fs::metadata(file_path).unwrap().permissions();
+ if !perms.readonly() {
+ return Err(MapFileFail(anyhow!("fail to map non read only storage file {}", file_path)));
+ }
+
+ let file = File::open(file_path)
+ .map_err(|errmsg| FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)))?;
+
+ // SAFETY:
+ //
+ // Mmap constructors are unsafe as it would have undefined behaviors if the file
+ // is modified after mapped (https://docs.rs/memmap2/latest/memmap2/struct.Mmap.html).
+ //
+ // We either have to make this api unsafe or ensure that the file will not be modified
+ // which means it is read only. Here in the code, we check explicitly that the file
+ // being mapped must only have read permission, otherwise, error out, thus making sure
+ // it is safe.
+ unsafe {
+ let mapped_file = Mmap::map(&file).map_err(|errmsg| {
+ MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
+ })?;
+ Ok(mapped_file)
+ }
+}
+
+/// Get a mapped storage file given the container and file type
+pub fn get_mapped_file(
+ location_pb_file: &str,
+ container: &str,
+ file_type: StorageFileType,
+) -> Result<Mmap, AconfigStorageError> {
+ let files_location = find_container_storage_location(location_pb_file, container)?;
+ match file_type {
+ StorageFileType::PackageMap => verify_read_only_and_map(files_location.package_map()),
+ StorageFileType::FlagMap => verify_read_only_and_map(files_location.flag_map()),
+ StorageFileType::FlagVal => verify_read_only_and_map(files_location.flag_val()),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn test_find_storage_file_location() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "/system/etc/package.map"
+ flag_map: "/system/etc/flag.map"
+ flag_val: "/metadata/aconfig/system.val"
+ timestamp: 12345
+}
+files {
+ version: 1
+ container: "product"
+ package_map: "/product/etc/package.map"
+ flag_map: "/product/etc/flag.map"
+ flag_val: "/metadata/aconfig/product.val"
+ timestamp: 54321
+}
+"#;
+ let file = write_proto_to_temp_file(&text_proto).unwrap();
+ let file_full_path = file.path().display().to_string();
+ let file_info = find_container_storage_location(&file_full_path, "system").unwrap();
+ assert_eq!(file_info.version(), 0);
+ assert_eq!(file_info.container(), "system");
+ assert_eq!(file_info.package_map(), "/system/etc/package.map");
+ assert_eq!(file_info.flag_map(), "/system/etc/flag.map");
+ assert_eq!(file_info.flag_val(), "/metadata/aconfig/system.val");
+ assert_eq!(file_info.timestamp(), 12345);
+
+ let file_info = find_container_storage_location(&file_full_path, "product").unwrap();
+ assert_eq!(file_info.version(), 1);
+ assert_eq!(file_info.container(), "product");
+ assert_eq!(file_info.package_map(), "/product/etc/package.map");
+ assert_eq!(file_info.flag_map(), "/product/etc/flag.map");
+ assert_eq!(file_info.flag_val(), "/metadata/aconfig/product.val");
+ assert_eq!(file_info.timestamp(), 54321);
+
+ let err = find_container_storage_location(&file_full_path, "vendor").unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "StorageFileNotFound(Storage file does not exist for vendor)"
+ );
+ }
+
+ fn map_and_verify(location_pb_file: &str, file_type: StorageFileType, actual_file: &str) {
+ let mut opened_file = File::open(actual_file).unwrap();
+ let mut content = Vec::new();
+ opened_file.read_to_end(&mut content).unwrap();
+
+ let mmaped_file = get_mapped_file(location_pb_file, "system", file_type).unwrap();
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+
+ fn create_test_storage_files(read_only: bool) -> [NamedTempFile; 4] {
+ let package_map = copy_to_temp_file("./tests/package.map", read_only).unwrap();
+ let flag_map = copy_to_temp_file("./tests/flag.map", read_only).unwrap();
+ let flag_val = copy_to_temp_file("./tests/package.map", read_only).unwrap();
+
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ package_map.path().display(),
+ flag_map.path().display(),
+ flag_val.path().display()
+ );
+ let pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ [package_map, flag_map, flag_val, pb_file]
+ }
+
+ #[test]
+ fn test_mapped_file_contents() {
+ let [package_map, flag_map, flag_val, pb_file] = create_test_storage_files(true);
+ let pb_file_path = pb_file.path().display().to_string();
+ map_and_verify(
+ &pb_file_path,
+ StorageFileType::PackageMap,
+ &package_map.path().display().to_string(),
+ );
+ map_and_verify(
+ &pb_file_path,
+ StorageFileType::FlagMap,
+ &flag_map.path().display().to_string(),
+ );
+ map_and_verify(
+ &pb_file_path,
+ StorageFileType::FlagVal,
+ &flag_val.path().display().to_string(),
+ );
+ }
+
+ #[test]
+ fn test_map_non_read_only_file() {
+ let [package_map, flag_map, flag_val, pb_file] = create_test_storage_files(false);
+ let pb_file_path = pb_file.path().display().to_string();
+ let error =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::PackageMap).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(fail to map non read only storage file {})",
+ package_map.path().display()
+ )
+ );
+ let error = get_mapped_file(&pb_file_path, "system", StorageFileType::FlagMap).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(fail to map non read only storage file {})",
+ flag_map.path().display()
+ )
+ );
+ let error = get_mapped_file(&pb_file_path, "system", StorageFileType::FlagVal).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(fail to map non read only storage file {})",
+ flag_val.path().display()
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs
new file mode 100644
index 0000000..81feec6
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! package table query module defines the package table file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{
+ package_table::PackageTableHeader, package_table::PackageTableNode, read_u32_from_bytes,
+};
+use anyhow::anyhow;
+
+/// Package table query return
+#[derive(PartialEq, Debug)]
+pub struct PackageOffset {
+ pub package_id: u32,
+ pub boolean_offset: u32,
+}
+
+/// Query package id and start offset
+pub fn find_package_offset(
+ buf: &[u8],
+ package: &str,
+) -> Result<Option<PackageOffset>, AconfigStorageError> {
+ let interpreted_header = PackageTableHeader::from_bytes(buf)?;
+ if interpreted_header.version > FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
+ let bucket_index = PackageTableNode::find_bucket_index(package, num_buckets);
+
+ let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
+ let mut package_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
+ if package_node_offset < interpreted_header.node_offset as usize
+ || package_node_offset >= interpreted_header.file_size as usize
+ {
+ return Ok(None);
+ }
+
+ loop {
+ let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?;
+ if interpreted_node.package_name == package {
+ return Ok(Some(PackageOffset {
+ package_id: interpreted_node.package_id,
+ boolean_offset: interpreted_node.boolean_offset,
+ }));
+ }
+ match interpreted_node.next_offset {
+ Some(offset) => package_node_offset = offset as usize,
+ None => return Ok(None),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::{StorageFileType, PackageTable};
+
+ pub fn create_test_package_table() -> PackageTable {
+ let header = PackageTableHeader {
+ version: crate::FILE_VERSION,
+ container: String::from("system"),
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
+ num_packages: 3,
+ bucket_offset: 31,
+ node_offset: 59,
+ };
+ let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
+ let first_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_2"),
+ package_id: 1,
+ boolean_offset: 3,
+ next_offset: None,
+ };
+ let second_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_1"),
+ package_id: 0,
+ boolean_offset: 0,
+ next_offset: Some(159),
+ };
+ let third_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_4"),
+ package_id: 2,
+ boolean_offset: 6,
+ next_offset: None,
+ };
+ let nodes = vec![first_node, second_node, third_node];
+ PackageTable { header, buckets, nodes }
+ }
+
+ #[test]
+ // this test point locks down table query
+ fn test_package_query() {
+ let package_table = create_test_package_table().as_bytes();
+ let package_offset =
+ find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
+ assert_eq!(package_offset, expected_package_offset);
+ let package_offset =
+ find_package_offset(&package_table[..], "com.android.aconfig.storage.test_2")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
+ assert_eq!(package_offset, expected_package_offset);
+ let package_offset =
+ find_package_offset(&package_table[..], "com.android.aconfig.storage.test_4")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
+ assert_eq!(package_offset, expected_package_offset);
+ }
+
+ #[test]
+ // this test point locks down table query of a non exist package
+ fn test_not_existed_package_query() {
+ // this will land at an empty bucket
+ let package_table = create_test_package_table().as_bytes();
+ let package_offset =
+ find_package_offset(&package_table[..], "com.android.aconfig.storage.test_3").unwrap();
+ assert_eq!(package_offset, None);
+ // this will land at the end of a linked list
+ let package_offset =
+ find_package_offset(&package_table[..], "com.android.aconfig.storage.test_5").unwrap();
+ assert_eq!(package_offset, None);
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut table = create_test_package_table();
+ table.header.version = crate::FILE_VERSION + 1;
+ let package_table = table.as_bytes();
+ let error = find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1")
+ .unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
new file mode 100644
index 0000000..ff72499
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::Result;
+use std::fs;
+use tempfile::NamedTempFile;
+
+/// Create temp file copy
+pub(crate) fn copy_to_temp_file(source_file: &str, read_only: bool) -> Result<NamedTempFile> {
+ let file = NamedTempFile::new()?;
+ fs::copy(source_file, file.path())?;
+ if read_only {
+ let file_name = file.path().display().to_string();
+ let mut perms = fs::metadata(file_name).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(file.path(), perms.clone()).unwrap();
+ }
+ Ok(file)
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
new file mode 100644
index 0000000..d9cf238
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
@@ -0,0 +1,43 @@
+rust_test {
+ name: "aconfig_storage_read_api.test.rust",
+ srcs: [
+ "storage_read_api_test.rs"
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libaconfig_storage_file",
+ "libaconfig_storage_read_api",
+ "libprotobuf",
+ "libtempfile",
+ ],
+ data: [
+ "package.map",
+ "flag.map",
+ "flag.val",
+ ],
+ test_suites: ["general-tests"],
+}
+
+cc_test {
+ name: "aconfig_storage_read_api.test.cpp",
+ srcs: [
+ "storage_read_api_test.cpp",
+ ],
+ static_libs: [
+ "libgmock",
+ "libaconfig_storage_protos_cc",
+ "libprotobuf-cpp-lite",
+ "libaconfig_storage_read_api_cc",
+ "libbase",
+ "liblog",
+ ],
+ data: [
+ "package.map",
+ "flag.map",
+ "flag.val",
+ ],
+ test_suites: [
+ "device-tests",
+ "general-tests",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
new file mode 100644
index 0000000..5507894
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.val b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
new file mode 100644
index 0000000..75b8564
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/package.map b/tools/aconfig/aconfig_storage_read_api/tests/package.map
new file mode 100644
index 0000000..02267e5
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/package.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
new file mode 100644
index 0000000..ff2f849
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+#include <cstdio>
+
+#include <sys/stat.h>
+#include "aconfig_storage/aconfig_storage_read_api.hpp"
+#include <gtest/gtest.h>
+#include <protos/aconfig_storage_metadata.pb.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+using android::aconfig_storage_metadata::storage_files;
+using namespace android::base;
+
+namespace api = aconfig_storage;
+namespace private_api = aconfig_storage::private_internal_api;
+
+class AconfigStorageTest : public ::testing::Test {
+ protected:
+ Result<std::string> copy_to_ro_temp_file(std::string const& source_file) {
+ auto temp_file = std::string(std::tmpnam(nullptr));
+ auto content = std::string();
+ if (!ReadFileToString(source_file, &content)) {
+ return Error() << "failed to read file: " << source_file;
+ }
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to copy file: " << source_file;
+ }
+ if (chmod(temp_file.c_str(), S_IRUSR | S_IRGRP | S_IROTH) == -1) {
+ return Error() << "failed to make file read only";
+ }
+ return temp_file;
+ }
+
+ Result<std::string> write_storage_location_pb_file(std::string const& package_map,
+ std::string const& flag_map,
+ std::string const& flag_val) {
+ auto temp_file = std::tmpnam(nullptr);
+ auto proto = storage_files();
+ auto* info = proto.add_files();
+ info->set_version(0);
+ info->set_container("system");
+ info->set_package_map(package_map);
+ info->set_flag_map(flag_map);
+ info->set_flag_val(flag_val);
+ info->set_timestamp(12345);
+
+ auto content = std::string();
+ proto.SerializeToString(&content);
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to write storage records pb file";
+ }
+ return temp_file;
+ }
+
+ void SetUp() override {
+ auto const test_dir = android::base::GetExecutableDirectory();
+ package_map = *copy_to_ro_temp_file(test_dir + "/package.map");
+ flag_map = *copy_to_ro_temp_file(test_dir + "/flag.map");
+ flag_val = *copy_to_ro_temp_file(test_dir + "/flag.val");
+ storage_record_pb = *write_storage_location_pb_file(
+ package_map, flag_map, flag_val);
+ }
+
+ void TearDown() override {
+ std::remove(package_map.c_str());
+ std::remove(flag_map.c_str());
+ std::remove(flag_val.c_str());
+ std::remove(storage_record_pb.c_str());
+ }
+
+ std::string package_map;
+ std::string flag_map;
+ std::string flag_val;
+ std::string storage_record_pb;
+};
+
+/// Test to lock down storage file version query api
+TEST_F(AconfigStorageTest, test_storage_version_query) {
+ auto query = api::get_storage_file_version(package_map);
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_EQ(query.version_number, 1);
+ query = api::get_storage_file_version(flag_map);
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_EQ(query.version_number, 1);
+ query = api::get_storage_file_version(flag_val);
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_EQ(query.version_number, 1);
+}
+
+/// Negative test to lock down the error when mapping none exist storage files
+TEST_F(AconfigStorageTest, test_none_exist_storage_file_mapping) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "vendor", api::StorageFileType::package_map);
+ ASSERT_FALSE(mapped_file_query.query_success);
+ ASSERT_EQ(mapped_file_query.error_message,
+ "Unable to find storage files for container vendor");
+}
+
+/// Negative test to lock down the error when mapping a writeable storage file
+TEST_F(AconfigStorageTest, test_writable_storage_file_mapping) {
+ ASSERT_TRUE(chmod(package_map.c_str(),
+ S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH) != -1);
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::package_map);
+ ASSERT_FALSE(mapped_file_query.query_success);
+ ASSERT_EQ(mapped_file_query.error_message, "cannot map writeable file");
+
+ ASSERT_TRUE(chmod(flag_map.c_str(),
+ S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH) != -1);
+ mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::flag_map);
+ ASSERT_FALSE(mapped_file_query.query_success);
+ ASSERT_EQ(mapped_file_query.error_message, "cannot map writeable file");
+
+ ASSERT_TRUE(chmod(flag_val.c_str(),
+ S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH) != -1);
+ mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::flag_val);
+ ASSERT_FALSE(mapped_file_query.query_success);
+ ASSERT_EQ(mapped_file_query.error_message, "cannot map writeable file");
+}
+
+/// Test to lock down storage package offset query api
+TEST_F(AconfigStorageTest, test_package_offset_query) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::package_map);
+ ASSERT_TRUE(mapped_file_query.query_success);
+ auto mapped_file = mapped_file_query.mapped_file;
+
+ auto query = api::get_package_offset(
+ mapped_file, "com.android.aconfig.storage.test_1");
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_TRUE(query.package_exists);
+ ASSERT_EQ(query.package_id, 0);
+ ASSERT_EQ(query.boolean_offset, 0);
+
+ query = api::get_package_offset(
+ mapped_file, "com.android.aconfig.storage.test_2");
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_TRUE(query.package_exists);
+ ASSERT_EQ(query.package_id, 1);
+ ASSERT_EQ(query.boolean_offset, 3);
+
+ query = api::get_package_offset(
+ mapped_file, "com.android.aconfig.storage.test_4");
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_TRUE(query.package_exists);
+ ASSERT_EQ(query.package_id, 2);
+ ASSERT_EQ(query.boolean_offset, 6);
+}
+
+/// Test to lock down when querying none exist package
+TEST_F(AconfigStorageTest, test_none_existent_package_offset_query) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::package_map);
+ ASSERT_TRUE(mapped_file_query.query_success);
+ auto mapped_file = mapped_file_query.mapped_file;
+
+ auto query = api::get_package_offset(
+ mapped_file, "com.android.aconfig.storage.test_3");
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_FALSE(query.package_exists);
+}
+
+/// Test to lock down storage flag offset query api
+TEST_F(AconfigStorageTest, test_flag_offset_query) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::flag_map);
+ ASSERT_TRUE(mapped_file_query.query_success);
+ auto mapped_file = mapped_file_query.mapped_file;
+
+ auto baseline = std::vector<std::tuple<int, std::string, int>>{
+ {0, "enabled_ro", 1},
+ {0, "enabled_rw", 2},
+ {1, "disabled_ro", 0},
+ {2, "enabled_ro", 1},
+ {1, "enabled_fixed_ro", 1},
+ {1, "enabled_ro", 2},
+ {2, "enabled_fixed_ro", 0},
+ {0, "disabled_rw", 0},
+ };
+ for (auto const&[package_id, flag_name, expected_offset] : baseline) {
+ auto query = api::get_flag_offset(mapped_file, package_id, flag_name);
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_TRUE(query.flag_exists);
+ ASSERT_EQ(query.flag_offset, expected_offset);
+ }
+}
+
+/// Test to lock down when querying none exist flag
+TEST_F(AconfigStorageTest, test_none_existent_flag_offset_query) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::flag_map);
+ ASSERT_TRUE(mapped_file_query.query_success);
+ auto mapped_file = mapped_file_query.mapped_file;
+
+ auto query = api::get_flag_offset(mapped_file, 0, "none_exist");
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_FALSE(query.flag_exists);
+
+ query = api::get_flag_offset(mapped_file, 3, "enabled_ro");
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_FALSE(query.flag_exists);
+}
+
+/// Test to lock down storage flag value query api
+TEST_F(AconfigStorageTest, test_boolean_flag_value_query) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::flag_val);
+ ASSERT_TRUE(mapped_file_query.query_success);
+ auto mapped_file = mapped_file_query.mapped_file;
+
+ for (int offset = 0; offset < 8; ++offset) {
+ auto query = api::get_boolean_flag_value(mapped_file, offset);
+ ASSERT_EQ(query.error_message, std::string());
+ ASSERT_TRUE(query.query_success);
+ ASSERT_FALSE(query.flag_value);
+ }
+}
+
+/// Negative test to lock down the error when querying flag value out of range
+TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_query) {
+ auto mapped_file_query = private_api::get_mapped_file_impl(
+ storage_record_pb, "system", api::StorageFileType::flag_val);
+ ASSERT_TRUE(mapped_file_query.query_success);
+ auto mapped_file = mapped_file_query.mapped_file;
+
+ auto query = api::get_boolean_flag_value(mapped_file, 8);
+ ASSERT_EQ(query.error_message,
+ std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
+ ASSERT_FALSE(query.query_success);
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
new file mode 100644
index 0000000..64239e5
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
@@ -0,0 +1,175 @@
+#[cfg(not(feature = "cargo"))]
+mod aconfig_storage_rust_test {
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use aconfig_storage_file::StorageFileType;
+ use aconfig_storage_read_api::{
+ get_boolean_flag_value, get_flag_offset, get_package_offset, get_storage_file_version,
+ mapped_file::get_mapped_file, PackageOffset,
+ };
+ use std::fs;
+ use tempfile::NamedTempFile;
+
+ pub fn copy_to_ro_temp_file(source_file: &str) -> NamedTempFile {
+ let file = NamedTempFile::new().unwrap();
+ fs::copy(source_file, file.path()).unwrap();
+ let file_name = file.path().display().to_string();
+ let mut perms = fs::metadata(file_name).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(file.path(), perms.clone()).unwrap();
+ file
+ }
+
+ fn create_test_storage_files() -> [NamedTempFile; 4] {
+ let package_map = copy_to_ro_temp_file("./package.map");
+ let flag_map = copy_to_ro_temp_file("./flag.map");
+ let flag_val = copy_to_ro_temp_file("./flag.val");
+
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ package_map.path().display(),
+ flag_map.path().display(),
+ flag_val.path().display()
+ );
+ let pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ [package_map, flag_map, flag_val, pb_file]
+ }
+
+ #[test]
+ fn test_unavailable_stoarge() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let err =
+ get_mapped_file(&pb_file_path, "vendor", StorageFileType::PackageMap).unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "StorageFileNotFound(Storage file does not exist for vendor)"
+ );
+ }
+
+ #[test]
+ fn test_package_offset_query() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let package_mapped_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::PackageMap).unwrap();
+
+ let package_offset =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_1")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
+ assert_eq!(package_offset, expected_package_offset);
+
+ let package_offset =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_2")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
+ assert_eq!(package_offset, expected_package_offset);
+
+ let package_offset =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_4")
+ .unwrap()
+ .unwrap();
+ let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
+ assert_eq!(package_offset, expected_package_offset);
+ }
+
+ #[test]
+ fn test_none_exist_package_offset_query() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let package_mapped_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::PackageMap).unwrap();
+ let package_offset_option =
+ get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_3").unwrap();
+ assert_eq!(package_offset_option, None);
+ }
+
+ #[test]
+ fn test_flag_offset_query() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_mapped_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::FlagMap).unwrap();
+
+ let baseline = vec![
+ (0, "enabled_ro", 1u16),
+ (0, "enabled_rw", 2u16),
+ (1, "disabled_ro", 0u16),
+ (2, "enabled_ro", 1u16),
+ (1, "enabled_fixed_ro", 1u16),
+ (1, "enabled_ro", 2u16),
+ (2, "enabled_fixed_ro", 0u16),
+ (0, "disabled_rw", 0u16),
+ ];
+ for (package_id, flag_name, expected_offset) in baseline.into_iter() {
+ let flag_offset =
+ get_flag_offset(&flag_mapped_file, package_id, flag_name)
+ .unwrap()
+ .unwrap();
+ assert_eq!(flag_offset, expected_offset);
+ }
+ }
+
+ #[test]
+ fn test_none_exist_flag_offset_query() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_mapped_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::FlagMap).unwrap();
+
+ let flag_offset_option =
+ get_flag_offset(&flag_mapped_file, 0, "none_exist").unwrap();
+ assert_eq!(flag_offset_option, None);
+
+ let flag_offset_option =
+ get_flag_offset(&flag_mapped_file, 3, "enabled_ro").unwrap();
+ assert_eq!(flag_offset_option, None);
+ }
+
+ #[test]
+ fn test_boolean_flag_value_query() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_value_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::FlagVal).unwrap();
+
+ let baseline: Vec<bool> = vec![false; 8];
+ for (offset, expected_value) in baseline.into_iter().enumerate() {
+ let flag_value =
+ get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
+ assert_eq!(flag_value, expected_value);
+ }
+ }
+
+ #[test]
+ fn test_invalid_boolean_flag_value_query() {
+ let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_value_file =
+ get_mapped_file(&pb_file_path, "system", StorageFileType::FlagVal).unwrap();
+
+ let err = get_boolean_flag_value(&flag_value_file, 8u32).unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ fn test_storage_version_query() {
+ assert_eq!(get_storage_file_version("./package.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.val").unwrap(), 1);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/Android.bp b/tools/aconfig/aconfig_storage_write_api/Android.bp
new file mode 100644
index 0000000..1382aba
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/Android.bp
@@ -0,0 +1,37 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "aconfig_storage_write_api.defaults",
+ edition: "2021",
+ lints: "none",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libtempfile",
+ "libmemmap2",
+ "libcxx",
+ "libthiserror",
+ "libaconfig_storage_file",
+ ],
+}
+
+rust_library {
+ name: "libaconfig_storage_write_api",
+ crate_name: "aconfig_storage_write_api",
+ host_supported: true,
+ defaults: ["aconfig_storage_write_api.defaults"],
+}
+
+rust_test_host {
+ name: "aconfig_storage_write_api.test",
+ test_suites: ["general-tests"],
+ defaults: ["aconfig_storage_write_api.defaults"],
+ data: [
+ "tests/flag.val",
+ ],
+ rustlibs: [
+ "libaconfig_storage_read_api",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/Cargo.toml b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
new file mode 100644
index 0000000..494c19c
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "aconfig_storage_write_api"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+memmap2 = "0.8.0"
+tempfile = "3.9.0"
+thiserror = "1.0.56"
+protobuf = "3.2.0"
+once_cell = "1.19.0"
+aconfig_storage_file = { path = "../aconfig_storage_file" }
+aconfig_storage_read_api = { path = "../aconfig_storage_read_api" }
+
+[build-dependencies]
+cxx-build = "1.0"
diff --git a/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs
new file mode 100644
index 0000000..c2375dd
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! flag value update module defines the flag value file write to mapped bytes
+
+use aconfig_storage_file::{AconfigStorageError, FlagValueHeader, FILE_VERSION};
+use anyhow::anyhow;
+
+/// Set flag value
+pub fn update_boolean_flag_value(
+ buf: &mut [u8],
+ flag_offset: u32,
+ flag_value: bool,
+) -> Result<(), AconfigStorageError> {
+ let interpreted_header = FlagValueHeader::from_bytes(buf)?;
+ if interpreted_header.version > FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot write to storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let head = (interpreted_header.boolean_value_offset + flag_offset) as usize;
+
+ // TODO: right now, there is only boolean flags, with more flag value types added
+ // later, the end of boolean flag value section should be updated (b/322826265).
+ if head >= interpreted_header.file_size as usize {
+ return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!(
+ "Flag value offset goes beyond the end of the file."
+ )));
+ }
+
+ buf[head] = u8::from(flag_value).to_le_bytes()[0];
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::{FlagValueList, StorageFileType};
+
+ pub fn create_test_flag_value_list() -> FlagValueList {
+ let header = FlagValueHeader {
+ version: FILE_VERSION,
+ container: String::from("system"),
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
+ num_flags: 8,
+ boolean_value_offset: 27,
+ };
+ let booleans: Vec<bool> = vec![false; 8];
+ FlagValueList { header, booleans }
+ }
+
+ #[test]
+ // this test point locks down flag value update
+ fn test_boolean_flag_value_update() {
+ let flag_value_list = create_test_flag_value_list();
+ let value_offset = flag_value_list.header.boolean_value_offset;
+ let mut content = flag_value_list.as_bytes();
+ let true_byte = u8::from(true).to_le_bytes()[0];
+ let false_byte = u8::from(false).to_le_bytes()[0];
+
+ for i in 0..flag_value_list.header.num_flags {
+ let offset = (value_offset + i) as usize;
+ update_boolean_flag_value(&mut content, i, true).unwrap();
+ assert_eq!(content[offset], true_byte);
+ update_boolean_flag_value(&mut content, i, false).unwrap();
+ assert_eq!(content[offset], false_byte);
+ }
+ }
+
+ #[test]
+ // this test point locks down update beyond the end of boolean section
+ fn test_boolean_out_of_range() {
+ let mut flag_value_list = create_test_flag_value_list().as_bytes();
+ let error = update_boolean_flag_value(&mut flag_value_list[..], 8, true).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut value_list = create_test_flag_value_list();
+ value_list.header.version = FILE_VERSION + 1;
+ let mut flag_value = value_list.as_bytes();
+ let error = update_boolean_flag_value(&mut flag_value[..], 4, true).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot write to storage file with a higher version of {} with lib version {})",
+ FILE_VERSION + 1,
+ FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/lib.rs b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
new file mode 100644
index 0000000..17a6538
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! `aconfig_storage_write_api` is a crate that defines write apis to update flag value
+//! in storage file. It provides one api to interface with storage files.
+
+pub mod flag_value_update;
+pub mod mapped_file;
+
+#[cfg(test)]
+mod test_utils;
+
+use aconfig_storage_file::AconfigStorageError;
+
+use anyhow::anyhow;
+use memmap2::MmapMut;
+
+/// Storage file location pb file
+pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/persistent_storage_file_records.pb";
+
+/// Get mmaped flag value file given the container name
+///
+/// \input container: the flag package container
+/// \return a result of mapped file
+///
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+pub unsafe fn get_mapped_flag_value_file(container: &str) -> Result<MmapMut, AconfigStorageError> {
+ unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container) }
+}
+
+/// Set boolean flag value thru mapped file and flush the change to file
+///
+/// \input mapped_file: the mapped flag value file
+/// \input offset: flag value offset
+/// \input value: updated flag value
+/// \return a result of ()
+///
+pub fn set_boolean_flag_value(
+ file: &mut MmapMut,
+ offset: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ crate::flag_value_update::update_boolean_flag_value(file, offset, value)?;
+ file.flush().map_err(|errmsg| {
+ AconfigStorageError::MapFlushFail(anyhow!("fail to flush storage file: {}", errmsg))
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+ use std::fs::File;
+ use std::io::Read;
+
+ fn get_boolean_flag_value_at_offset(file: &str, offset: u32) -> bool {
+ let mut f = File::open(&file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_boolean_flag_value(&bytes, offset).unwrap()
+ }
+
+ #[test]
+ fn test_set_boolean_flag_value() {
+ let flag_value_file = copy_to_temp_file("./tests/flag.val", false).unwrap();
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ flag_value_path
+ );
+ let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed as only this single threaded test process will
+ // write to this file
+ unsafe {
+ let mut file = crate::mapped_file::get_mapped_file(&record_pb_path, "system").unwrap();
+ for i in 0..8 {
+ set_boolean_flag_value(&mut file, i, true).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert_eq!(value, true);
+
+ set_boolean_flag_value(&mut file, i, false).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert_eq!(value, false);
+ }
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs
new file mode 100644
index 0000000..4c98be4
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use std::fs::{self, File, OpenOptions};
+use std::io::{BufReader, Read};
+
+use anyhow::anyhow;
+use memmap2::MmapMut;
+
+use aconfig_storage_file::protos::{storage_record_pb::try_from_binary_proto, ProtoStorageFiles};
+use aconfig_storage_file::AconfigStorageError::{
+ self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
+};
+
+/// Find where persistent storage value file is for a particular container
+fn find_persist_flag_value_file(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<String, AconfigStorageError> {
+ let file = File::open(location_pb_file).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", location_pb_file, errmsg))
+ })?;
+ let mut reader = BufReader::new(file);
+ let mut bytes = Vec::new();
+ reader.read_to_end(&mut bytes).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to read file {}: {}", location_pb_file, errmsg))
+ })?;
+ let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes).map_err(|errmsg| {
+ ProtobufParseFail(anyhow!(
+ "Failed to parse storage location pb file {}: {}",
+ location_pb_file,
+ errmsg
+ ))
+ })?;
+ for location_info in storage_locations.files.iter() {
+ if location_info.container() == container {
+ return Ok(location_info.flag_val().to_string());
+ }
+ }
+ Err(StorageFileNotFound(anyhow!("Persistent flag value file does not exist for {}", container)))
+}
+
+/// Get a mapped storage file given the container and file type
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+pub unsafe fn get_mapped_file(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<MmapMut, AconfigStorageError> {
+ let file_path = find_persist_flag_value_file(location_pb_file, container)?;
+
+ // make sure file has read write permission
+ let perms = fs::metadata(&file_path).unwrap().permissions();
+ if perms.readonly() {
+ return Err(MapFileFail(anyhow!("fail to map non read write storage file {}", file_path)));
+ }
+
+ let file =
+ OpenOptions::new().read(true).write(true).open(&file_path).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+
+ unsafe {
+ MmapMut::map_mut(&file).map_err(|errmsg| {
+ MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+
+ #[test]
+ fn test_find_persist_flag_value_file_location() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "/system/etc/package.map"
+ flag_map: "/system/etc/flag.map"
+ flag_val: "/metadata/aconfig/system.val"
+ timestamp: 12345
+}
+files {
+ version: 1
+ container: "product"
+ package_map: "/product/etc/package.map"
+ flag_map: "/product/etc/flag.map"
+ flag_val: "/metadata/aconfig/product.val"
+ timestamp: 54321
+}
+"#;
+ let file = write_proto_to_temp_file(&text_proto).unwrap();
+ let file_full_path = file.path().display().to_string();
+ let flag_value_file = find_persist_flag_value_file(&file_full_path, "system").unwrap();
+ assert_eq!(flag_value_file, "/metadata/aconfig/system.val");
+ let flag_value_file = find_persist_flag_value_file(&file_full_path, "product").unwrap();
+ assert_eq!(flag_value_file, "/metadata/aconfig/product.val");
+ let err = find_persist_flag_value_file(&file_full_path, "vendor").unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "StorageFileNotFound(Persistent flag value file does not exist for vendor)"
+ );
+ }
+
+ #[test]
+ fn test_mapped_file_contents() {
+ let mut rw_file = copy_to_temp_file("./tests/flag.val", false).unwrap();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ rw_file.path().display().to_string()
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ let mut content = Vec::new();
+ rw_file.read_to_end(&mut content).unwrap();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let mmaped_file = get_mapped_file(&storage_record_file_path, "system").unwrap();
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+ }
+
+ #[test]
+ fn test_mapped_read_only_file() {
+ let ro_file = copy_to_temp_file("./tests/flag.val", true).unwrap();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ ro_file.path().display().to_string()
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let error = get_mapped_file(&storage_record_file_path, "system").unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(fail to map non read write storage file {})",
+ ro_file.path().display().to_string()
+ )
+ );
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs
new file mode 100644
index 0000000..06e2e22
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::Result;
+use std::fs;
+use tempfile::NamedTempFile;
+
+/// Create temp file copy
+pub(crate) fn copy_to_temp_file(source_file: &str, read_only: bool) -> Result<NamedTempFile> {
+ let file = NamedTempFile::new()?;
+ fs::copy(source_file, file.path())?;
+ if read_only {
+ let file_name = file.path().display().to_string();
+ let mut perms = fs::metadata(file_name).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(file.path(), perms.clone()).unwrap();
+ }
+ Ok(file)
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
new file mode 100644
index 0000000..bb8c6df
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
@@ -0,0 +1,19 @@
+
+rust_test {
+ name: "aconfig_storage_write_api.test.rust",
+ srcs: [
+ "storage_write_api_test.rs"
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libaconfig_storage_file",
+ "libaconfig_storage_read_api",
+ "libaconfig_storage_write_api",
+ "libprotobuf",
+ "libtempfile",
+ ],
+ data: [
+ "flag.val",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/flag.val b/tools/aconfig/aconfig_storage_write_api/tests/flag.val
new file mode 100644
index 0000000..75b8564
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs
new file mode 100644
index 0000000..f6c1bbc
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs
@@ -0,0 +1,73 @@
+#[cfg(not(feature = "cargo"))]
+mod aconfig_storage_write_api_test {
+ use aconfig_storage_file::protos::ProtoStorageFiles;
+ use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+ use aconfig_storage_write_api::{mapped_file::get_mapped_file, set_boolean_flag_value};
+
+ use protobuf::Message;
+ use std::fs::{self, File};
+ use std::io::{Read, Write};
+ use tempfile::NamedTempFile;
+
+ /// Write storage location record pb to a temp file
+ fn write_storage_record_file(flag_val: &str) -> NamedTempFile {
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package_map"
+ flag_map: "some_flag_map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ flag_val
+ );
+ let storage_files: ProtoStorageFiles =
+ protobuf::text_format::parse_from_str(&text_proto).unwrap();
+ let mut binary_proto_bytes = Vec::new();
+ storage_files.write_to_vec(&mut binary_proto_bytes).unwrap();
+ let mut file = NamedTempFile::new().unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+ file
+ }
+
+ /// Create temp file copy
+ fn copy_to_temp_rw_file(source_file: &str) -> NamedTempFile {
+ let file = NamedTempFile::new().unwrap();
+ fs::copy(source_file, file.path()).unwrap();
+ file
+ }
+
+ /// Get boolean flag value from offset
+ fn get_boolean_flag_value_at_offset(file: &str, offset: u32) -> bool {
+ let mut f = File::open(file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_boolean_flag_value(&bytes, offset).unwrap()
+ }
+
+ #[test]
+ /// Test to lock down flag value update api
+ fn test_boolean_flag_value_update() {
+ let flag_value_file = copy_to_temp_rw_file("./flag.val");
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let record_pb_file = write_storage_record_file(&flag_value_path);
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is ensured as only this single threaded test process will
+ // write to this file
+ let mut file = unsafe { get_mapped_file(&record_pb_path, "system").unwrap() };
+ for i in 0..8 {
+ set_boolean_flag_value(&mut file, i, true).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert!(value);
+
+ set_boolean_flag_value(&mut file, i, false).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert!(!value);
+ }
+ }
+}
diff --git a/tools/aconfig/aflags/Android.bp b/tools/aconfig/aflags/Android.bp
index c65da97..b36aa34 100644
--- a/tools/aconfig/aflags/Android.bp
+++ b/tools/aconfig/aflags/Android.bp
@@ -12,6 +12,7 @@
"libaconfig_protos",
"libanyhow",
"libclap",
+ "libnix",
"libprotobuf",
"libregex",
],
diff --git a/tools/aconfig/aflags/Cargo.toml b/tools/aconfig/aflags/Cargo.toml
index 3350a6cd..6a08da6 100644
--- a/tools/aconfig/aflags/Cargo.toml
+++ b/tools/aconfig/aflags/Cargo.toml
@@ -10,3 +10,4 @@
protobuf = "3.2.0"
regex = "1.10.3"
aconfig_protos = { path = "../aconfig_protos" }
+nix = { version = "0.28.0", features = ["user"] }
diff --git a/tools/aconfig/aflags/src/device_config_source.rs b/tools/aconfig/aflags/src/device_config_source.rs
index 12a62cf..089f33d 100644
--- a/tools/aconfig/aflags/src/device_config_source.rs
+++ b/tools/aconfig/aflags/src/device_config_source.rs
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-use crate::{Flag, FlagPermission, FlagSource, ValuePickedFrom};
+use crate::{Flag, FlagPermission, FlagSource, FlagValue, ValuePickedFrom};
use aconfig_protos::ProtoFlagPermission as ProtoPermission;
use aconfig_protos::ProtoFlagState as ProtoState;
use aconfig_protos::ProtoParsedFlag;
@@ -40,10 +40,9 @@
};
let value = match flag.state() {
- ProtoState::ENABLED => "true",
- ProtoState::DISABLED => "false",
- }
- .to_string();
+ ProtoState::ENABLED => FlagValue::Enabled,
+ ProtoState::DISABLED => FlagValue::Disabled,
+ };
let permission = match flag.permission() {
ProtoPermission::READ_ONLY => FlagPermission::ReadOnly,
@@ -56,6 +55,7 @@
name,
container,
value,
+ staged_value: None,
permission,
value_picked_from: ValuePickedFrom::Default,
}
@@ -64,7 +64,7 @@
fn read_pb_files() -> Result<Vec<Flag>> {
let mut flags: BTreeMap<String, Flag> = BTreeMap::new();
for partition in ["system", "system_ext", "product", "vendor"] {
- let path = format!("/{}/etc/aconfig_flags.pb", partition);
+ let path = format!("/{partition}/etc/aconfig_flags.pb");
let Ok(bytes) = fs::read(&path) else {
eprintln!("warning: failed to read {}", path);
continue;
@@ -86,53 +86,94 @@
Ok(flags.values().cloned().collect())
}
-fn parse_device_config(raw: &str) -> Result<HashMap<String, String>> {
+fn parse_device_config(raw: &str) -> Result<HashMap<String, FlagValue>> {
let mut flags = HashMap::new();
let regex = Regex::new(r"(?m)^([[[:alnum:]]_]+/[[[:alnum:]]_\.]+)=(true|false)$")?;
for capture in regex.captures_iter(raw) {
let key =
capture.get(1).ok_or(anyhow!("invalid device_config output"))?.as_str().to_string();
- let value = capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str();
- flags.insert(key, value.to_string());
+ let value = FlagValue::try_from(
+ capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str(),
+ )?;
+ flags.insert(key, value);
}
Ok(flags)
}
-fn read_device_config_output(command: &str) -> Result<String> {
- let output = Command::new("/system/bin/device_config").arg(command).output()?;
+fn read_device_config_output(command: &[&str]) -> Result<String> {
+ let output = Command::new("/system/bin/device_config").args(command).output()?;
if !output.status.success() {
let reason = match output.status.code() {
- Some(code) => format!("exit code {}", code),
+ Some(code) => {
+ let output = str::from_utf8(&output.stdout)?;
+ if !output.is_empty() {
+ format!("exit code {code}, output was {output}")
+ } else {
+ format!("exit code {code}")
+ }
+ }
None => "terminated by signal".to_string(),
};
- bail!("failed to execute device_config: {}", reason);
+ bail!("failed to access flag storage: {}", reason);
}
Ok(str::from_utf8(&output.stdout)?.to_string())
}
-fn read_device_config_flags() -> Result<HashMap<String, String>> {
- let list_output = read_device_config_output("list")?;
+fn read_device_config_flags() -> Result<HashMap<String, FlagValue>> {
+ let list_output = read_device_config_output(&["list"])?;
parse_device_config(&list_output)
}
-fn reconcile(pb_flags: &[Flag], dc_flags: HashMap<String, String>) -> Vec<Flag> {
+/// Parse the list of newline-separated staged flags.
+///
+/// The output is a newline-sepaarated list of entries which follow this format:
+/// `namespace*flagname=value`
+///
+/// The resulting map maps from `namespace/flagname` to `value`, if a staged flag exists for
+/// `namespace/flagname`.
+fn parse_staged_flags(raw: &str) -> Result<HashMap<String, FlagValue>> {
+ let mut flags = HashMap::new();
+ for line in raw.split('\n') {
+ match (line.find('*'), line.find('=')) {
+ (Some(star_index), Some(equal_index)) => {
+ let namespace = &line[..star_index];
+ let flag = &line[star_index + 1..equal_index];
+ if let Ok(value) = FlagValue::try_from(&line[equal_index + 1..]) {
+ flags.insert(namespace.to_owned() + "/" + flag, value);
+ }
+ }
+ _ => continue,
+ };
+ }
+ Ok(flags)
+}
+
+fn read_staged_flags() -> Result<HashMap<String, FlagValue>> {
+ let staged_flags_output = read_device_config_output(&["list", "staged"])?;
+ parse_staged_flags(&staged_flags_output)
+}
+
+fn reconcile(
+ pb_flags: &[Flag],
+ dc_flags: HashMap<String, FlagValue>,
+ staged_flags: HashMap<String, FlagValue>,
+) -> Vec<Flag> {
pb_flags
.iter()
.map(|f| {
- dc_flags
- .get(&format!("{}/{}.{}", f.namespace, f.package, f.name))
- .map(|value| {
- if value.eq(&f.value) {
- Flag { value_picked_from: ValuePickedFrom::Default, ..f.clone() }
- } else {
- Flag {
- value_picked_from: ValuePickedFrom::Server,
- value: value.to_string(),
- ..f.clone()
- }
- }
- })
- .unwrap_or(f.clone())
+ let server_override = dc_flags.get(&format!("{}/{}", f.namespace, f.qualified_name()));
+ let (value_picked_from, selected_value) = match server_override {
+ Some(value) if *value != f.value => (ValuePickedFrom::Server, *value),
+ _ => (ValuePickedFrom::Default, f.value),
+ };
+ Flag { value_picked_from, value: selected_value, ..f.clone() }
+ })
+ .map(|f| {
+ let staged_value = staged_flags
+ .get(&format!("{}/{}", f.namespace, f.qualified_name()))
+ .map(|value| if *value != f.value { Some(*value) } else { None })
+ .unwrap_or(None);
+ Flag { staged_value, ..f }
})
.collect()
}
@@ -141,10 +182,15 @@
fn list_flags() -> Result<Vec<Flag>> {
let pb_flags = read_pb_files()?;
let dc_flags = read_device_config_flags()?;
+ let staged_flags = read_staged_flags()?;
- let flags = reconcile(&pb_flags, dc_flags);
+ let flags = reconcile(&pb_flags, dc_flags, staged_flags);
Ok(flags)
}
+
+ fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()> {
+ read_device_config_output(&["put", namespace, qualified_name, value]).map(|_| ())
+ }
}
#[cfg(test)]
@@ -161,9 +207,9 @@
namespace_two/android.flag_two=nonsense
"#;
let expected = HashMap::from([
- ("namespace_one/com.foo.bar.flag_one".to_string(), "true".to_string()),
- ("namespace_one/com.foo.bar.flag_two".to_string(), "false".to_string()),
- ("namespace_two/android.flag_one".to_string(), "true".to_string()),
+ ("namespace_one/com.foo.bar.flag_one".to_string(), FlagValue::Enabled),
+ ("namespace_one/com.foo.bar.flag_two".to_string(), FlagValue::Disabled),
+ ("namespace_two/android.flag_one".to_string(), FlagValue::Enabled),
]);
let actual = parse_device_config(input).unwrap();
assert_eq!(expected, actual);
diff --git a/tools/aconfig/aflags/src/main.rs b/tools/aconfig/aflags/src/main.rs
index 1e2a7a0..808ffa0 100644
--- a/tools/aconfig/aflags/src/main.rs
+++ b/tools/aconfig/aflags/src/main.rs
@@ -16,13 +16,13 @@
//! `aflags` is a device binary to read and write aconfig flags.
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
use clap::Parser;
mod device_config_source;
use device_config_source::DeviceConfigSource;
-#[derive(Clone)]
+#[derive(Clone, PartialEq, Debug)]
enum FlagPermission {
ReadOnly,
ReadWrite,
@@ -37,7 +37,7 @@
}
}
-#[derive(Clone)]
+#[derive(Clone, Debug)]
enum ValuePickedFrom {
Default,
Server,
@@ -52,19 +52,61 @@
}
}
-#[derive(Clone)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum FlagValue {
+ Enabled,
+ Disabled,
+}
+
+impl TryFrom<&str> for FlagValue {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+ match value {
+ "true" | "enabled" => Ok(Self::Enabled),
+ "false" | "disabled" => Ok(Self::Disabled),
+ _ => Err(anyhow!("cannot convert string '{}' to FlagValue", value)),
+ }
+ }
+}
+
+impl ToString for FlagValue {
+ fn to_string(&self) -> String {
+ match &self {
+ Self::Enabled => "enabled".into(),
+ Self::Disabled => "disabled".into(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
struct Flag {
namespace: String,
name: String,
package: String,
container: String,
- value: String,
+ value: FlagValue,
+ staged_value: Option<FlagValue>,
permission: FlagPermission,
value_picked_from: ValuePickedFrom,
}
+impl Flag {
+ fn qualified_name(&self) -> String {
+ format!("{}.{}", self.package, self.name)
+ }
+
+ fn display_staged_value(&self) -> String {
+ match self.staged_value {
+ Some(v) => format!("(->{})", v.to_string()),
+ None => "-".to_string(),
+ }
+ }
+}
+
trait FlagSource {
fn list_flags() -> Result<Vec<Flag>>;
+ fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>;
}
const ABOUT_TEXT: &str = "Tool for reading and writing flags.
@@ -76,6 +118,10 @@
* `package`: package set for this flag in its .aconfig definition.
* `flag_name`: flag name, also set in definition.
* `value`: the value read from the flag.
+ * `staged_value`: the value on next boot:
+ + `-`: same as current value
+ + `(->enabled) flipped to enabled on boot.
+ + `(->disabled) flipped to disabled on boot.
* `provenance`: one of:
+ `default`: the flag value comes from its build-time default.
+ `server`: the flag value comes from a server override.
@@ -94,25 +140,37 @@
enum Command {
/// List all aconfig flags on this device.
List,
+
+ /// Enable an aconfig flag on this device, on the next boot.
+ Enable {
+ /// <package>.<flag_name>
+ qualified_name: String,
+ },
+
+ /// Disable an aconfig flag on this device, on the next boot.
+ Disable {
+ /// <package>.<flag_name>
+ qualified_name: String,
+ },
}
struct PaddingInfo {
- longest_package_col: usize,
- longest_name_col: usize,
+ longest_flag_col: usize,
longest_val_col: usize,
+ longest_staged_val_col: usize,
longest_value_picked_from_col: usize,
longest_permission_col: usize,
}
fn format_flag_row(flag: &Flag, info: &PaddingInfo) -> String {
- let pkg = &flag.package;
- let p0 = info.longest_package_col + 1;
-
- let name = &flag.name;
- let p1 = info.longest_name_col + 1;
+ let full_name = flag.qualified_name();
+ let p0 = info.longest_flag_col + 1;
let val = flag.value.to_string();
- let p2 = info.longest_val_col + 1;
+ let p1 = info.longest_val_col + 1;
+
+ let staged_val = flag.display_staged_value();
+ let p2 = info.longest_staged_val_col + 1;
let value_picked_from = flag.value_picked_from.to_string();
let p3 = info.longest_value_picked_from_col + 1;
@@ -122,15 +180,37 @@
let container = &flag.container;
- format!("{pkg:p0$}{name:p1$}{val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n")
+ format!(
+ "{full_name:p0$}{val:p1$}{staged_val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n"
+ )
+}
+
+fn set_flag(qualified_name: &str, value: &str) -> Result<()> {
+ ensure!(nix::unistd::Uid::current().is_root(), "must be root to mutate flags");
+
+ let flags_binding = DeviceConfigSource::list_flags()?;
+ let flag = flags_binding.iter().find(|f| f.qualified_name() == qualified_name).ok_or(
+ anyhow!("no aconfig flag '{qualified_name}'. Does the flag have an .aconfig definition?"),
+ )?;
+
+ ensure!(flag.permission == FlagPermission::ReadWrite,
+ format!("could not write flag '{qualified_name}', it is read-only for the current release configuration."));
+
+ DeviceConfigSource::override_flag(&flag.namespace, qualified_name, value)?;
+
+ Ok(())
}
fn list() -> Result<String> {
let flags = DeviceConfigSource::list_flags()?;
let padding_info = PaddingInfo {
- longest_package_col: flags.iter().map(|f| f.package.len()).max().unwrap_or(0),
- longest_name_col: flags.iter().map(|f| f.name.len()).max().unwrap_or(0),
+ longest_flag_col: flags.iter().map(|f| f.qualified_name().len()).max().unwrap_or(0),
longest_val_col: flags.iter().map(|f| f.value.to_string().len()).max().unwrap_or(0),
+ longest_staged_val_col: flags
+ .iter()
+ .map(|f| f.display_staged_value().len())
+ .max()
+ .unwrap_or(0),
longest_value_picked_from_col: flags
.iter()
.map(|f| f.value_picked_from.to_string().len())
@@ -154,10 +234,13 @@
fn main() {
let cli = Cli::parse();
let output = match cli.command {
- Command::List => list(),
+ Command::List => list().map(Some),
+ Command::Enable { qualified_name } => set_flag(&qualified_name, "true").map(|_| None),
+ Command::Disable { qualified_name } => set_flag(&qualified_name, "false").map(|_| None),
};
match output {
- Ok(text) => println!("{text}"),
- Err(msg) => println!("Error: {}", msg),
+ Ok(Some(text)) => println!("{text}"),
+ Ok(None) => (),
+ Err(message) => println!("Error: {message}"),
}
}
diff --git a/tools/aconfig/printflags/src/main.rs b/tools/aconfig/printflags/src/main.rs
index a0c9ee8..7838b51 100644
--- a/tools/aconfig/printflags/src/main.rs
+++ b/tools/aconfig/printflags/src/main.rs
@@ -67,9 +67,23 @@
let device_config_flags = parse_device_config(dc_stdout);
// read aconfig_flags.pb files
+ let apex_pattern = Regex::new(r"^/apex/[^@]+\.[^@]+$").unwrap();
+ let mut mount_points = vec![
+ "system".to_string(),
+ "system_ext".to_string(),
+ "product".to_string(),
+ "vendor".to_string(),
+ ];
+ for apex in fs::read_dir("/apex")? {
+ let path_name = apex?.path().display().to_string();
+ if let Some(canonical_path) = apex_pattern.captures(&path_name) {
+ mount_points.push(canonical_path.get(0).unwrap().as_str().to_owned());
+ }
+ }
+
let mut flags: BTreeMap<String, Vec<String>> = BTreeMap::new();
- for partition in ["system", "system_ext", "product", "vendor"] {
- let path = format!("/{}/etc/aconfig_flags.pb", partition);
+ for mount_point in mount_points {
+ let path = format!("/{}/etc/aconfig_flags.pb", mount_point);
let Ok(bytes) = fs::read(&path) else {
eprintln!("warning: failed to read {}", path);
continue;
@@ -80,7 +94,7 @@
})?;
for flag in parsed_flags.parsed_flag {
let key = format!("{}/{}.{}", flag.namespace(), flag.package(), flag.name());
- let value = format!("{:?} + {:?} ({})", flag.permission(), flag.state(), partition);
+ let value = format!("{:?} + {:?} ({})", flag.permission(), flag.state(), mount_point);
flags.entry(key).or_default().push(value);
}
}
diff --git a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
index 37c0011..671b036 100755
--- a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+++ b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
@@ -69,6 +69,33 @@
rm "$tmpfile"
}
+function bumpSdkExtensionsVersion() {
+ local SDKEXT="packages/modules/SdkExtensions/"
+
+ # This used to call bump_sdk.sh utility.
+ # However due to TS, we have to build the gen_sdk with a correct set of settings.
+
+ # "$top/packages/modules/SdkExtensions/gen_sdk/bump_sdk.sh" ${FINAL_MAINLINE_EXTENSION}
+ # Leave the last commit as a set of modified files.
+ # The code to create a finalization topic will pick it up later.
+ # git -C ${SDKEXT} reset HEAD~1
+
+ local sdk="${FINAL_MAINLINE_EXTENSION}"
+ local modules_arg=
+
+ TARGET_PRODUCT=aosp_arm64 \
+ TARGET_RELEASE=fina_1 \
+ TARGET_BUILD_VARIANT=userdebug \
+ DIST_DIR=out/dist \
+ $top/build/soong/soong_ui.bash --make-mode --soong-only gen_sdk
+
+ ANDROID_BUILD_TOP="$top" out/soong/host/linux-x86/bin/gen_sdk \
+ --database ${SDKEXT}/gen_sdk/extensions_db.textpb \
+ --action new_sdk \
+ --sdk "$sdk" \
+ $modules_arg
+}
+
function finalize_aidl_vndk_sdk_resources() {
local top="$(dirname "$0")"/../../../..
source $top/build/make/tools/finalization/environment.sh
@@ -76,9 +103,6 @@
local SDK_CODENAME="public static final int $FINAL_PLATFORM_CODENAME_JAVA = CUR_DEVELOPMENT;"
local SDK_VERSION="public static final int $FINAL_PLATFORM_CODENAME_JAVA = $FINAL_PLATFORM_SDK_VERSION;"
- # default target to modify tree and build SDK
- local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
-
# The full process can be found at (INTERNAL) go/android-sdk-finalization.
# apply droidstubs hack to prevent tools from incrementing an API version
@@ -87,20 +111,12 @@
# bionic/NDK
finalize_bionic_ndk
- # VNDK definitions for new SDK version
- cp "$top/development/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-current.txt" \
- "$top/development/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-$FINAL_PLATFORM_SDK_VERSION.txt"
+ # pre-finalization build target (trunk)
+ local aidl_m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_RELEASE=trunk TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
+ AIDL_TRANSITIVE_FREEZE=true $aidl_m aidl-freeze-api
- AIDL_TRANSITIVE_FREEZE=true $m aidl-freeze-api create_reference_dumps
-
- # Generate ABI dumps
- ANDROID_BUILD_TOP="$top" out/host/linux-x86/bin/create_reference_dumps
-
- echo "NOTE: THIS INTENTIONALLY MAY FAIL AND REPAIR ITSELF (until 'DONE')"
- # Update new versions of files. See update-vndk-list.sh (which requires envsetup.sh)
- $m check-vndk-list || \
- { cp $top/out/soong/vndk/vndk.libraries.txt $top/build/make/target/product/gsi/current.txt; }
- echo "DONE: THIS INTENTIONALLY MAY FAIL AND REPAIR ITSELF"
+ # TODO(b/309880485)
+ # Add back create_reference_dumps and $top/build/make/target/product/gsi/current.txt
# Finalize SDK
@@ -114,9 +130,6 @@
sed -i -e 's/Pkg\.Revision.*/Pkg\.Revision=${PLATFORM_SDK_VERSION}.0.0/g' $build_tools_source
# build/make
- local version_defaults="$top/build/make/core/version_defaults.mk"
- sed -i -e "s/PLATFORM_SDK_VERSION := .*/PLATFORM_SDK_VERSION := ${FINAL_PLATFORM_SDK_VERSION}/g" $version_defaults
- sed -i -e "s/PLATFORM_VERSION_LAST_STABLE := .*/PLATFORM_VERSION_LAST_STABLE := ${FINAL_PLATFORM_VERSION}/g" $version_defaults
sed -i -e "s/sepolicy_major_vers := .*/sepolicy_major_vers := ${FINAL_PLATFORM_SDK_VERSION}/g" "$top/build/make/core/config.mk"
cp "$top/build/make/target/product/gsi/current.txt" "$top/build/make/target/product/gsi/$FINAL_PLATFORM_SDK_VERSION.txt"
@@ -156,18 +169,14 @@
sed -i -e "/=.*$((${FINAL_PLATFORM_SDK_VERSION}-1)),/a \\ SDK_${FINAL_PLATFORM_CODENAME_JAVA} = ${FINAL_PLATFORM_SDK_VERSION}," "$top/frameworks/base/tools/aapt2/SdkConstants.h"
# Bump Mainline SDK extension version.
- local SDKEXT="packages/modules/SdkExtensions/"
- "$top/packages/modules/SdkExtensions/gen_sdk/bump_sdk.sh" ${FINAL_MAINLINE_EXTENSION}
- # Leave the last commit as a set of modified files.
- # The code to create a finalization topic will pick it up later.
- git -C ${SDKEXT} reset HEAD~1
+ bumpSdkExtensionsVersion
- local version_defaults="$top/build/make/core/version_defaults.mk"
- sed -i -e "s/PLATFORM_SDK_EXTENSION_VERSION := .*/PLATFORM_SDK_EXTENSION_VERSION := ${FINAL_MAINLINE_EXTENSION}/g" $version_defaults
+ # target to build SDK
+ local sdk_m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
# Force update current.txt
- $m clobber
- $m update-api
+ $sdk_m clobber
+ $sdk_m update-api
}
finalize_aidl_vndk_sdk_resources
diff --git a/tools/finalization/finalize-sdk-rel.sh b/tools/finalization/finalize-sdk-rel.sh
index d4ed380..245305b 100755
--- a/tools/finalization/finalize-sdk-rel.sh
+++ b/tools/finalization/finalize-sdk-rel.sh
@@ -33,10 +33,6 @@
# in REL mode, resources would correctly set the resources_sdk_int, no fix required
revert_resources_sdk_int_fix
- # build/make/core/version_defaults.mk
- # Mark all versions "released".
- sed -i 's/\(PLATFORM_VERSION_CODENAME\.[^[:space:]]*\) := [^[:space:]]*/\1 := REL/g' "$top/build/make/core/version_defaults.mk"
-
# cts
echo "$FINAL_PLATFORM_VERSION" > "$top/cts/tests/tests/os/assets/platform_versions.txt"
if [ "$FINAL_PLATFORM_CODENAME" != "$CURRENT_PLATFORM_CODENAME" ]; then
@@ -47,18 +43,19 @@
# system/sepolicy
system/sepolicy/tools/finalize-sdk-rel.sh "$top" "$FINAL_PLATFORM_SDK_VERSION"
- # prebuilts/abi-dumps/ndk
- mkdir -p "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION"
- cp -r "$top/prebuilts/abi-dumps/ndk/current/64/" "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION/"
-
# prebuilts/abi-dumps/platform
mkdir -p "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION"
cp -r "$top/prebuilts/abi-dumps/platform/current/64/" "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION/"
- if [ "$FINAL_STATE" != "sdk" ] || [ "$FINAL_PLATFORM_CODENAME" == "$CURRENT_PLATFORM_CODENAME" ] ; then
+ # TODO(b/309880485)
+ # uncomment and update
+ # prebuilts/abi-dumps/ndk
+ #mkdir -p "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION"
+ #cp -r "$top/prebuilts/abi-dumps/ndk/current/64/" "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION/"
+ #if [ "$FINAL_STATE" != "sdk" ] || [ "$FINAL_PLATFORM_CODENAME" == "$CURRENT_PLATFORM_CODENAME" ] ; then
# prebuilts/abi-dumps/vndk
- mv "$top/prebuilts/abi-dumps/vndk/$CURRENT_PLATFORM_CODENAME" "$top/prebuilts/abi-dumps/vndk/$FINAL_PLATFORM_SDK_VERSION"
- fi;
+ #mv "$top/prebuilts/abi-dumps/vndk/$CURRENT_PLATFORM_CODENAME" "$top/prebuilts/abi-dumps/vndk/$FINAL_PLATFORM_SDK_VERSION"
+ #fi;
}
finalize_sdk_rel
diff --git a/tools/finalization/localonly-steps.sh b/tools/finalization/localonly-steps.sh
index 7318ca1..bebd563 100755
--- a/tools/finalization/localonly-steps.sh
+++ b/tools/finalization/localonly-steps.sh
@@ -7,17 +7,17 @@
source $top/build/make/tools/finalization/environment.sh
# default target to modify tree and build SDK
- local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
+ local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
# adb keys
$m adb
LOGNAME=android-eng HOSTNAME=google.com "$top/out/host/linux-x86/bin/adb" keygen "$top/vendor/google/security/adb/${FINAL_PLATFORM_VERSION}.adb_key"
# Build Platform SDKs.
- $top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=sdk TARGET_BUILD_VARIANT=userdebug sdk dist sdk_repo DIST_DIR=out/dist
+ $top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=sdk TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug sdk dist sdk_repo DIST_DIR=out/dist
# Build Modules SDKs.
- TARGET_BUILD_VARIANT=userdebug UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true DIST_DIR=out/dist "$top/vendor/google/build/mainline_modules_sdks.sh" --build-release=latest
+ TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true DIST_DIR=out/dist "$top/vendor/google/build/mainline_modules_sdks.sh" --build-release=latest
# Update prebuilts.
"$top/prebuilts/build-tools/path/linux-x86/python3" -W ignore::DeprecationWarning "$top/prebuilts/sdk/update_prebuilts.py" --local_mode -f ${FINAL_PLATFORM_SDK_VERSION} -e ${FINAL_MAINLINE_EXTENSION} --bug 1 1
diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp
index 55fdca4..bd9543a 100644
--- a/tools/fs_config/Android.bp
+++ b/tools/fs_config/Android.bp
@@ -35,7 +35,6 @@
srcs: ["fs_config.c"],
shared_libs: [
"libcutils",
- "libselinux",
],
cflags: ["-Werror"],
}
diff --git a/tools/fs_config/fs_config.c b/tools/fs_config/fs_config.c
index 2a75add..80bd3c1 100644
--- a/tools/fs_config/fs_config.c
+++ b/tools/fs_config/fs_config.c
@@ -22,9 +22,6 @@
#include <string.h>
#include <inttypes.h>
-#include <selinux/selinux.h>
-#include <selinux/label.h>
-
#include "private/android_filesystem_config.h"
#include "private/fs_config.h"
@@ -35,8 +32,8 @@
//
// After the first 4 columns, optional key=value pairs are emitted
// for each file. Currently, the following keys are supported:
-// * -S: selabel=[selinux_label]
-// * -C: capabilities=[hex capabilities value]
+//
+// -C: capabilities=[hex capabilities value]
//
// Example input:
//
@@ -48,45 +45,24 @@
// system/etc/dbus.conf 1002 1002 440
// data/app 1000 1000 771
//
-// or if, for example, -S is used:
-//
-// system/etc/dbus.conf 1002 1002 440 selabel=u:object_r:system_file:s0
-// data/app 1000 1000 771 selabel=u:object_r:apk_data_file:s0
-//
// Note that the output will omit the trailing slash from
// directories.
-static struct selabel_handle* get_sehnd(const char* context_file) {
- struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, context_file } };
- struct selabel_handle* sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1);
-
- if (!sehnd) {
- perror("error running selabel_open");
- exit(EXIT_FAILURE);
- }
- return sehnd;
-}
-
static void usage() {
- fprintf(stderr, "Usage: fs_config [-D product_out_path] [-S context_file] [-R root] [-C]\n");
+ fprintf(stderr, "Usage: fs_config [-D product_out_path] [-R root] [-C]\n");
}
int main(int argc, char** argv) {
char buffer[1024];
- const char* context_file = NULL;
const char* product_out_path = NULL;
char* root_path = NULL;
- struct selabel_handle* sehnd = NULL;
int print_capabilities = 0;
int opt;
- while((opt = getopt(argc, argv, "CS:R:D:")) != -1) {
+ while((opt = getopt(argc, argv, "CR:D:")) != -1) {
switch(opt) {
case 'C':
print_capabilities = 1;
break;
- case 'S':
- context_file = optarg;
- break;
case 'R':
root_path = optarg;
break;
@@ -99,10 +75,6 @@
}
}
- if (context_file != NULL) {
- sehnd = get_sehnd(context_file);
- }
-
if (root_path != NULL) {
size_t root_len = strlen(root_path);
/* Trim any trailing slashes from the root path. */
@@ -141,33 +113,6 @@
}
printf("%s %d %d %o", buffer, uid, gid, mode);
- if (sehnd != NULL) {
- size_t buffer_strlen = strnlen(buffer, sizeof(buffer));
- if (buffer_strlen >= sizeof(buffer)) {
- fprintf(stderr, "non null terminated buffer, aborting\n");
- exit(EXIT_FAILURE);
- }
- size_t full_name_size = buffer_strlen + 2;
- char* full_name = (char*) malloc(full_name_size);
- if (full_name == NULL) {
- perror("malloc");
- exit(EXIT_FAILURE);
- }
-
- full_name[0] = '/';
- strncpy(full_name + 1, buffer, full_name_size - 1);
- full_name[full_name_size - 1] = '\0';
-
- char* secontext;
- if (selabel_lookup(sehnd, &secontext, full_name, ( mode | (is_dir ? S_IFDIR : S_IFREG)))) {
- secontext = strdup("u:object_r:unlabeled:s0");
- }
-
- printf(" selabel=%s", secontext);
- free(full_name);
- freecon(secontext);
- }
-
if (print_capabilities) {
printf(" capabilities=0x%" PRIx64, capabilities);
}
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index d31f87e..b8dcd84 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -215,7 +215,7 @@
This simulates how apexd activates APEXes.
1. create {inp}/APEX which is treated as a "/apex" on device.
- 2. invoke apexd_host with vendor APEXes.
+ 2. invoke apexd_host with APEXes.
"""
apex_dir = common.MakeTempDir('APEX')
@@ -225,12 +225,13 @@
# Always create /apex directory for dirmap
os.makedirs(apex_dir, exist_ok=True)
- # Invoke apexd_host to activate vendor APEXes for checkvintf
+ # Invoke apexd_host to activate APEXes for checkvintf
apex_host = os.path.join(OPTIONS.search_path, 'bin', 'apexd_host')
cmd = [apex_host, '--tool_path', OPTIONS.search_path]
cmd += ['--apex_path', dirmap['/apex']]
- if '/vendor' in dirmap:
- cmd += ['--vendor_path', dirmap['/vendor']]
+ for p in ['system', 'system_ext', 'product', 'vendor']:
+ if '/' + p in dirmap:
+ cmd += ['--' + p + '_path', dirmap['/' + p]]
common.RunAndCheckOutput(cmd)
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 1990377..8a8a613 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -480,6 +480,10 @@
return self.get("virtual_ab_compression_method", "")
@property
+ def vabc_cow_version(self):
+ return self.get("virtual_ab_cow_version", "")
+
+ @property
def vendor_api_level(self):
vendor_prop = self.info_dict.get("vendor.build.prop")
if not vendor_prop:
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index dbbbca2..c0ff5d2 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -908,6 +908,16 @@
source_info.vabc_compression_param, target_info.vabc_compression_param, source_info.vabc_compression_param))
vabc_compression_param = source_info.vabc_compression_param
+ # Virtual AB Cow version 3 is introduced in Android U with improved memory
+ # and install time performance. All OTA's with
+ # both the source build and target build with VIRTUAL_AB_COW_VERSION = 3
+ # can support the new format. Otherwise, fallback on older versions
+ if not source_info.vabc_cow_version or not target_info.vabc_cow_version:
+ logger.info("Source or Target doesn't have VABC_COW_VERSION specified, default to version 2")
+ OPTIONS.vabc_cow_version = 2
+ elif source_info.vabc_cow_version != target_info.vabc_cow_version:
+ logger.info("Source and Target have different cow VABC_COW_VERSION specified, default to minimum version")
+ OPTIONS.vabc_cow_version = min(source_info.vabc_cow_version, target_info.vabc_cow_version)
# Virtual AB Compression was introduced in Androd S.
# Later, we backported VABC to Android R. But verity support was not
# backported, so if VABC is used and we are on Android R, disable
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index bf69dec..5d92ede 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -795,6 +795,16 @@
# Copy it verbatim if we allow the file to exist.
common.ZipWriteStr(output_tf_zip, out_info, data)
+ # Sign microdroid_vendor.img.
+ elif filename == "VENDOR/etc/avf/microdroid/microdroid_vendor.img":
+ vendor_key = OPTIONS.avb_keys.get("vendor")
+ vendor_algorithm = OPTIONS.avb_algorithms.get("vendor")
+ with tempfile.NamedTemporaryFile() as image:
+ image.write(data)
+ image.flush()
+ ReplaceKeyInAvbHashtreeFooter(image, vendor_key, vendor_algorithm,
+ misc_info)
+ common.ZipWrite(output_tf_zip, image.name, filename)
# A non-APK file; copy it verbatim.
else:
common.ZipWriteStr(output_tf_zip, out_info, data)
@@ -812,6 +822,108 @@
# Write back misc_info with the latest values.
ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
+# Parse string output of `avbtool info_image`.
+def ParseAvbInfo(info_raw):
+ # line_matcher is for parsing each output line of `avbtool info_image`.
+ # example string input: " Hash Algorithm: sha1"
+ # example matched input: (" ", "Hash Algorithm", "sha1")
+ line_matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
+ # prop_matcher is for parsing value part of 'Prop' in `avbtool info_image`.
+ # example string input: "example_prop_key -> 'example_prop_value'"
+ # example matched output: ("example_prop_key", "example_prop_value")
+ prop_matcher = re.compile(r"(.+)\s->\s'(.+)'")
+ info = {}
+ indent_stack = [[-1, info]]
+ for line_info_raw in info_raw.split('\n'):
+ # Parse the line
+ line_info_parsed = line_matcher.match(line_info_raw)
+ if not line_info_parsed:
+ continue
+ indent = len(line_info_parsed.group(1))
+ key = line_info_parsed.group(2).strip()
+ value = line_info_parsed.group(3).strip()
+
+ # Pop indentation stack
+ while indent <= indent_stack[-1][0]:
+ del indent_stack[-1]
+
+ # Insert information into 'info'.
+ cur_info = indent_stack[-1][1]
+ if value == "":
+ if key == "Descriptors":
+ empty_list = []
+ cur_info[key] = empty_list
+ indent_stack.append([indent, empty_list])
+ else:
+ empty_dict = {}
+ cur_info.append({key:empty_dict})
+ indent_stack.append([indent, empty_dict])
+ elif key == "Prop":
+ prop_parsed = prop_matcher.match(value)
+ if not prop_parsed:
+ raise ValueError(
+ "Failed to parse prop while getting avb information.")
+ cur_info.append({key:{prop_parsed.group(1):prop_parsed.group(2)}})
+ else:
+ cur_info[key] = value
+ return info
+
+def ReplaceKeyInAvbHashtreeFooter(image, new_key, new_algorithm, misc_info):
+ # Get avb information about the image by parsing avbtool info_image.
+ def GetAvbInfo(avbtool, image_name):
+ # Get information with raw string by `avbtool info_image`.
+ info_raw = common.RunAndCheckOutput([
+ avbtool, 'info_image',
+ '--image', image_name
+ ])
+ return ParseAvbInfo(info_raw)
+
+ # Get hashtree descriptor from info
+ def GetAvbHashtreeDescriptor(avb_info):
+ hashtree_descriptors = tuple(filter(lambda x: "Hashtree descriptor" in x,
+ info.get('Descriptors')))
+ if len(hashtree_descriptors) != 1:
+ raise ValueError("The number of hashtree descriptor is not 1.")
+ return hashtree_descriptors[0]["Hashtree descriptor"]
+
+ # Get avb info
+ avbtool = misc_info['avb_avbtool']
+ info = GetAvbInfo(avbtool, image.name)
+ hashtree_descriptor = GetAvbHashtreeDescriptor(info)
+
+ # Generate command
+ cmd = [avbtool, 'add_hashtree_footer',
+ '--key', new_key,
+ '--algorithm', new_algorithm,
+ '--partition_name', hashtree_descriptor.get("Partition Name"),
+ '--partition_size', info.get("Image size").removesuffix(" bytes"),
+ '--hash_algorithm', hashtree_descriptor.get("Hash Algorithm"),
+ '--salt', hashtree_descriptor.get("Salt"),
+ '--do_not_generate_fec',
+ '--image', image.name
+ ]
+
+ # Append properties into command
+ props = map(lambda x: x.get("Prop"), filter(lambda x: "Prop" in x,
+ info.get('Descriptors')))
+ for prop_wrapped in props:
+ prop = tuple(prop_wrapped.items())
+ if len(prop) != 1:
+ raise ValueError("The number of property is not 1.")
+ cmd.append('--prop')
+ cmd.append(prop[0][0] + ':' + prop[0][1])
+
+ # Replace Hashtree Footer with new key
+ common.RunAndCheckOutput(cmd)
+
+ # Check root digest is not changed
+ new_info = GetAvbInfo(avbtool, image.name)
+ new_hashtree_descriptor = GetAvbHashtreeDescriptor(info)
+ root_digest = hashtree_descriptor.get("Root Digest")
+ new_root_digest = new_hashtree_descriptor.get("Root Digest")
+ assert root_digest == new_root_digest, \
+ ("Root digest in hashtree descriptor shouldn't be changed. Old: {}, New: "
+ "{}").format(root_digest, new_root_digest)
def ReplaceCerts(data):
"""Replaces all the occurences of X.509 certs with the new ones.
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 9cc6df4..7ac1cff 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -22,8 +22,9 @@
import common
import test_utils
from sign_target_files_apks import (
- CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ReadApexKeysInfo,
- ReplaceCerts, RewriteAvbProps, RewriteProps, WriteOtacerts)
+ CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ParseAvbInfo,
+ ReadApexKeysInfo, ReplaceCerts, RewriteAvbProps, RewriteProps,
+ WriteOtacerts)
class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
@@ -535,3 +536,86 @@
'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
'build/make/target/product/security/testkey', None),
}, keys_info)
+
+ def test_ParseAvbInfo(self):
+ avb_info_string = """
+ Footer version: 1.0
+ Image size: 9999999 bytes
+ Original image size: 8888888 bytes
+ VBMeta offset: 7777777
+ VBMeta size: 1111 bytes
+ --
+ Minimum libavb version: 1.0
+ Header Block: 222 bytes
+ Authentication Block: 333 bytes
+ Auxiliary Block: 888 bytes
+ Public key (sha1): abababababababababababababababababababab
+ Algorithm: SHA256_RSA2048
+ Rollback Index: 0
+ Flags: 0
+ Rollback Index Location: 0
+ Release String: 'avbtool 1.3.0'
+ Descriptors:
+ Hashtree descriptor:
+ Version of dm-verity: 1
+ Image Size: 8888888 bytes
+ Tree Offset: 8888888
+ Tree Size: 44444 bytes
+ Data Block Size: 4444 bytes
+ Hash Block Size: 4444 bytes
+ FEC num roots: 0
+ FEC offset: 0
+ FEC size: 0 bytes
+ Hash Algorithm: sha1
+ Partition Name: partition-name
+ Salt: cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
+ Root Digest: efefefefefefefefefefefefefefefefefef
+ Flags: 0
+ Prop: prop.key -> 'prop.value'
+ """
+
+ self.assertEqual(
+ {
+ 'Footer version': '1.0',
+ 'Image size': '9999999 bytes',
+ 'Original image size': '8888888 bytes',
+ 'VBMeta offset': '7777777',
+ 'VBMeta size': '1111 bytes',
+ 'Minimum libavb version': '1.0',
+ 'Header Block': '222 bytes',
+ 'Authentication Block': '333 bytes',
+ 'Auxiliary Block': '888 bytes',
+ 'Public key (sha1)': 'abababababababababababababababababababab',
+ 'Algorithm': 'SHA256_RSA2048',
+ 'Rollback Index': '0',
+ 'Flags': '0',
+ 'Rollback Index Location': '0',
+ 'Release String': "'avbtool 1.3.0'",
+ 'Descriptors': [
+ {
+ 'Hashtree descriptor': {
+ 'Version of dm-verity': '1',
+ 'Image Size': '8888888 bytes',
+ 'Tree Offset': '8888888',
+ 'Tree Size': '44444 bytes',
+ 'Data Block Size': '4444 bytes',
+ 'Hash Block Size': '4444 bytes',
+ 'FEC num roots': '0',
+ 'FEC offset': '0',
+ 'FEC size': '0 bytes',
+ 'Hash Algorithm': 'sha1',
+ 'Partition Name': 'partition-name',
+ 'Salt': 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',
+ 'Root Digest': 'efefefefefefefefefefefefefefefefefef',
+ 'Flags': '0',
+ }
+ },
+ {
+ 'Prop': {
+ 'prop.key': 'prop.value',
+ }
+ },
+ ],
+ },
+ ParseAvbInfo(avb_info_string),
+ )
\ No newline at end of file
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 2f2b833..6a9a5d3 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -986,15 +986,14 @@
}
private static class ZipSections {
- ByteBuffer beforeCentralDir;
+ DataSource beforeCentralDir;
ByteBuffer centralDir;
ByteBuffer eocd;
}
- private static ZipSections findMainZipSections(ByteBuffer apk)
+ private static ZipSections findMainZipSections(DataSource apk)
throws IOException, ZipFormatException {
- apk.slice();
- ApkUtils.ZipSections sections = ApkUtils.findZipSections(DataSources.asDataSource(apk));
+ ApkUtils.ZipSections sections = ApkUtils.findZipSections(apk);
long centralDirStartOffset = sections.getZipCentralDirectoryOffset();
long centralDirSizeBytes = sections.getZipCentralDirectorySizeBytes();
long centralDirEndOffset = centralDirStartOffset + centralDirSizeBytes;
@@ -1005,25 +1004,18 @@
+ ". CD end: " + centralDirEndOffset
+ ", EoCD start: " + eocdStartOffset);
}
- apk.position(0);
- apk.limit((int) centralDirStartOffset);
- ByteBuffer beforeCentralDir = apk.slice();
-
- apk.position((int) centralDirStartOffset);
- apk.limit((int) centralDirEndOffset);
- ByteBuffer centralDir = apk.slice();
-
- apk.position((int) eocdStartOffset);
- apk.limit(apk.capacity());
- ByteBuffer eocd = apk.slice();
-
- apk.position(0);
- apk.limit(apk.capacity());
ZipSections result = new ZipSections();
- result.beforeCentralDir = beforeCentralDir;
- result.centralDir = centralDir;
- result.eocd = eocd;
+ result.beforeCentralDir = apk.slice(0, centralDirStartOffset);
+
+ long centralDirSize = centralDirEndOffset - centralDirStartOffset;
+ if (centralDirSize >= Integer.MAX_VALUE) throw new IndexOutOfBoundsException();
+ result.centralDir = apk.getByteBuffer(centralDirStartOffset, (int)centralDirSize);
+
+ long eocdSize = apk.size() - eocdStartOffset;
+ if (eocdSize >= Integer.MAX_VALUE) throw new IndexOutOfBoundsException();
+ result.eocd = apk.getByteBuffer(eocdStartOffset, (int)eocdSize);
+
return result;
}
@@ -1300,7 +1292,8 @@
v1SignedApkBuf.reset();
ByteBuffer[] outputChunks = new ByteBuffer[] {v1SignedApk};
- ZipSections zipSections = findMainZipSections(v1SignedApk);
+ ZipSections zipSections = findMainZipSections(DataSources.asDataSource(
+ v1SignedApk));
ByteBuffer eocd = ByteBuffer.allocate(zipSections.eocd.remaining());
eocd.put(zipSections.eocd);
@@ -1312,7 +1305,7 @@
while (true) {
ApkSignerEngine.OutputApkSigningBlockRequest2 addV2SignatureRequest =
apkSigner.outputZipSections2(
- DataSources.asDataSource(zipSections.beforeCentralDir),
+ zipSections.beforeCentralDir,
DataSources.asDataSource(zipSections.centralDir),
DataSources.asDataSource(eocd));
if (addV2SignatureRequest == null) break;
@@ -1330,11 +1323,15 @@
modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
ApkUtils.setZipEocdCentralDirectoryOffset(
modifiedEocd,
- zipSections.beforeCentralDir.remaining() + padding +
+ zipSections.beforeCentralDir.size() + padding +
apkSigningBlock.length);
+ if (zipSections.beforeCentralDir.size() >= Integer.MAX_VALUE) {
+ throw new IndexOutOfBoundsException();
+ }
outputChunks =
new ByteBuffer[] {
- zipSections.beforeCentralDir,
+ zipSections.beforeCentralDir.getByteBuffer(0,
+ (int)zipSections.beforeCentralDir.size()),
ByteBuffer.allocate(padding),
ByteBuffer.wrap(apkSigningBlock),
zipSections.centralDir,