summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:37:38 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:37:38 +0000
commit70d7a402c973601eb2ef02d8fe106c179c3dc092 (patch)
treeaf0ac8b6efbbfc1f757d21c6ca571de5c9ab1f6d
parent0bf2df681092be4d4fd192c45ab730bcd7d1a919 (diff)
parent36eaa49ffd1383c74e08a04241f9a2d647d1d08d (diff)
downloadapex-70d7a402c973601eb2ef02d8fe106c179c3dc092.tar.gz
Snap for 7550844 from 36eaa49ffd1383c74e08a04241f9a2d647d1d08d to mainline-os-statsd-release
Change-Id: Ia0c365feeee34e58d169fc17d33091fd5356dbd9
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_not_pre_installed_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v1_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_file_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_rebootless_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_rebootless_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_not_pre_installed_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v1_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_file_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_rebootless_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_rebootless_apex.asciipb13
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb5
-rw-r--r--.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb5
-rw-r--r--Android.bp68
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--apexd/Android.bp220
-rw-r--r--apexd/ApexInfoList.xsd38
-rw-r--r--apexd/OWNERS1
-rw-r--r--apexd/aidl/android/apex/ApexSessionInfo.aidl1
-rw-r--r--apexd/aidl/android/apex/CompressedApexInfo.aidl (renamed from tests/src/com/android/tests/apex/ConscryptHostTest.java)17
-rw-r--r--apexd/aidl/android/apex/CompressedApexInfoList.aidl (renamed from tests/src/com/android/tests/apex/IpSecHostTest.java)13
-rw-r--r--apexd/aidl/android/apex/IApexService.aidl48
-rw-r--r--apexd/apex-info-list-api/current.txt45
-rw-r--r--apexd/apex-info-list-api/last_current.txt45
-rw-r--r--apexd/apex-info-list-api/last_removed.txt0
-rw-r--r--apexd/apex-info-list-api/removed.txt1
-rw-r--r--apexd/apex_constants.h22
-rw-r--r--apexd/apex_database.cpp106
-rw-r--r--apexd/apex_database.h174
-rw-r--r--apexd/apex_database_test.cpp32
-rw-r--r--apexd/apex_file.cpp303
-rw-r--r--apexd/apex_file.h45
-rw-r--r--apexd/apex_file_repository.cpp280
-rw-r--r--apexd/apex_file_repository.h139
-rw-r--r--apexd/apex_file_repository_test.cpp506
-rw-r--r--apexd/apex_file_test.cpp306
-rw-r--r--apexd/apex_manifest.cpp5
-rw-r--r--apexd/apex_manifest.h10
-rw-r--r--apexd/apex_manifest_test.cpp2
-rw-r--r--apexd/apex_preinstalled_data.cpp134
-rw-r--r--apexd/apex_preinstalled_data.h34
-rw-r--r--apexd/apex_shim.cpp2
-rw-r--r--apexd/apexd.cpp2647
-rw-r--r--apexd/apexd.h165
-rw-r--r--apexd/apexd_checkpoint.h2
-rw-r--r--apexd/apexd_checkpoint_vold.cpp11
-rw-r--r--apexd/apexd_checkpoint_vold.h2
-rw-r--r--apexd/apexd_lifecycle.cpp (renamed from apexd/apexd_prop.cpp)31
-rw-r--r--apexd/apexd_lifecycle.h48
-rw-r--r--apexd/apexd_loop.cpp285
-rw-r--r--apexd/apexd_loop.h14
-rw-r--r--apexd/apexd_main.cpp73
-rw-r--r--apexd/apexd_prepostinstall.cpp106
-rw-r--r--apexd/apexd_prepostinstall.h16
-rw-r--r--apexd/apexd_private.h15
-rw-r--r--apexd/apexd_prop.h32
-rw-r--r--apexd/apexd_rollback_utils.h28
-rw-r--r--apexd/apexd_session.cpp139
-rw-r--r--apexd/apexd_session.h21
-rw-r--r--apexd/apexd_session_test.cpp126
-rw-r--r--apexd/apexd_test.cpp3524
-rw-r--r--apexd/apexd_test_utils.h230
-rw-r--r--apexd/apexd_testdata/Android.bp348
-rw-r--r--apexd/apexd_testdata/AppInRebootlessApex_AndroidManifest.xml (renamed from tests/src/com/android/tests/apex/CellbroadcastHostTest.java)22
-rw-r--r--apexd/apexd_testdata/com.android.apex.compressed.avbpubkeybin0 -> 1032 bytes
-rw-r--r--apexd/apexd_testdata/com.android.apex.compressed.pem51
-rw-r--r--apexd/apexd_testdata/manifest_compressed.json4
-rw-r--r--apexd/apexd_testdata/manifest_compressed_sharedlibs.json5
-rw-r--r--apexd/apexd_testdata/manifest_compressed_v2.json4
-rw-r--r--apexd/apexd_testdata/manifest_rebootless.json9
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_add_native_lib.json10
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_jni_libs.json12
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_provides_native_libs.json12
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_provides_sharedlibs.json10
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_remove_native_lib.json8
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_requires_shared_apex_libs.json12
-rw-r--r--apexd/apexd_testdata/manifest_rebootless_v2.json9
-rw-r--r--apexd/apexd_testdata/sharedlibs.apexbin0 -> 313995 bytes
-rw-r--r--apexd/apexd_utils.h217
-rw-r--r--apexd/apexd_utils_test.cpp284
-rw-r--r--apexd/apexd_verity.cpp22
-rw-r--r--apexd/apexd_verity_test.cpp24
-rw-r--r--apexd/apexservice.cpp353
-rw-r--r--apexd/apexservice_test.cpp445
-rw-r--r--apexd/sysprop/Android.bp4
-rw-r--r--apexd/sysprop/ApexProperties.sysprop16
-rw-r--r--apexd/sysprop/api/com.android.sysprop.apex-current.txt8
-rw-r--r--apexer/Android.bp79
-rw-r--r--apexer/TEST_MAPPING5
-rw-r--r--apexer/apexer.py185
-rw-r--r--apexer/apexer_test.py71
-rwxr-xr-xapexer/runtests.sh16
-rw-r--r--apexer/testdata/Android.bp9
-rw-r--r--docs/README.md291
-rw-r--r--docs/compressed-apex-format.pngbin0 -> 26768 bytes
-rw-r--r--docs/howto.md556
-rw-r--r--library_linking_strategy.cc17
-rw-r--r--libs/libapexutil/Android.bp10
-rw-r--r--libs/libapexutil/apexutil.cpp3
-rw-r--r--proto/Android.bp24
-rw-r--r--proto/apex_build_info.proto3
-rw-r--r--proto/apex_manifest.proto31
-rw-r--r--proto/session_state.proto3
-rw-r--r--pylintrc447
-rw-r--r--shim/Android.bp44
-rw-r--r--shim/build/Android.bp81
-rw-r--r--shim/build/AndroidManifestNotPreInstalled.xml8
-rw-r--r--shim/build/manifest_v2_rebootless.json5
-rw-r--r--shim/build/manifest_v3_rebootless.json5
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v1.apexbin334278 -> 336249 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2.apexbin334283 -> 336248 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_file.apexbin310721 -> 314399 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_folder.apexbin310665 -> 314339 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apexbin314739 -> 318416 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apexbin312267 -> 315939 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_package_name.apexbin334237 -> 336205 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_no_hashtree.apexbin334269 -> 332136 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_rebootless.apexbin0 -> 314476 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_sdk_target_p.apexbin334274 -> 336237 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apexbin0 -> 314434 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob.apexbin337179 -> 339487 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apexbin341275 -> 343583 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apexbin341275 -> 343583 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_payload.apexbin326078 -> 323945 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apexbin310743 -> 314420 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apexbin310746 -> 314423 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apexbin310758 -> 314431 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v2_wrong_sha.apexbin310755 -> 314436 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v3.apexbin334277 -> 336253 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v3_rebootless.apexbin0 -> 314479 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob.apexbin337179 -> 339489 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apexbin341275 -> 343585 bytes
-rw-r--r--shim/prebuilts/arm/com.android.apex.cts.shim_not_pre_installed.apexbin310791 -> 314418 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v1.apexbin334280 -> 336251 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2.apexbin334281 -> 336245 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_file.apexbin310721 -> 314399 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_folder.apexbin310665 -> 314339 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apexbin314739 -> 318416 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apexbin312267 -> 315939 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_package_name.apexbin334236 -> 336205 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_no_hashtree.apexbin334272 -> 332136 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_rebootless.apexbin0 -> 314476 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_sdk_target_p.apexbin334275 -> 336239 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apexbin0 -> 314434 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob.apexbin337179 -> 339487 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apexbin341275 -> 343583 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apexbin341275 -> 343583 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_payload.apexbin326074 -> 323944 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apexbin310743 -> 314420 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apexbin310746 -> 314423 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apexbin310758 -> 314431 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v2_wrong_sha.apexbin310755 -> 314436 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v3.apexbin334279 -> 336250 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v3_rebootless.apexbin0 -> 314479 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob.apexbin337179 -> 339489 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apexbin341275 -> 343585 bytes
-rw-r--r--shim/prebuilts/x86/com.android.apex.cts.shim_not_pre_installed.apexbin310791 -> 314418 bytes
-rw-r--r--tests/Android.bp219
-rw-r--r--tests/TEST_MAPPING93
-rw-r--r--tests/adbd-e2e-tests.xml25
-rw-r--r--tests/apex_compression_platform_tests.xml32
-rw-r--r--tests/app/AndroidManifest.xml33
-rw-r--r--tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java164
-rw-r--r--tests/cellbroadcast-e2e-tests.xml25
-rw-r--r--tests/conscrypt-e2e-tests.xml26
-rw-r--r--tests/extservices-e2e-tests.xml24
-rw-r--r--tests/ipsec-e2e-tests.xml26
-rw-r--r--tests/mediaprovider-e2e-tests.xml25
-rw-r--r--tests/module-test-utils-tests.xml24
-rw-r--r--tests/native/.clang-format1
-rw-r--r--tests/native/Android.bp55
-rw-r--r--tests/native/AndroidTest.xml29
-rw-r--r--tests/native/apex_shared_libraries_test.cpp142
-rw-r--r--tests/neuralnetworks-e2e-tests.xml26
-rw-r--r--tests/permission-e2e-tests.xml26
-rw-r--r--tests/sample_prefer32_binary.cc1
-rw-r--r--tests/shared-libs-apex-tests.xml23
-rw-r--r--tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java37
-rw-r--r--tests/src/com/android/tests/apex/ApexRollbackTests.java198
-rw-r--r--tests/src/com/android/tests/apex/ApexdHostTest.java179
-rw-r--r--tests/src/com/android/tests/apex/MediaProviderHostTest.java28
-rw-r--r--tests/src/com/android/tests/apex/NeuralNetworksHostTest.java28
-rw-r--r--tests/src/com/android/tests/apex/PermissionHostTest.java30
-rw-r--r--tests/src/com/android/tests/apex/SharedLibsApexTest.java319
-rw-r--r--tests/src/com/android/tests/apex/StatsdHostTest.java30
-rw-r--r--tests/src/com/android/tests/apex/WifiHostTest.java29
-rw-r--r--tests/src/com/android/tests/apex/host/ApexCompressionTests.java438
-rw-r--r--tests/src/com/android/tests/util/ModuleTestUtilsTest.java71
-rw-r--r--tests/statsd-e2e-tests.xml25
-rw-r--r--tests/testdata/init.rc2
-rw-r--r--tests/testdata/sharedlibs/README.md13
-rw-r--r--tests/testdata/sharedlibs/build/Android.bp86
-rwxr-xr-xtests/testdata/sharedlibs/build/build_artifacts.sh206
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/Android.bp96
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/bar_test.cc13
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.avbpubkeybin0 -> 1032 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pem51
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pk8bin0 -> 2375 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.x509.pem34
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.bar/manifest.json4
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/Android.bp77
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/baz_test.cc13
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.avbpubkeybin0 -> 1032 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pem51
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pk8bin0 -> 2374 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.x509.pem34
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.baz/manifest.json4
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/Android.bp77
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.avbpubkeybin0 -> 1032 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pem51
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pk8bin0 -> 2375 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.x509.pem34
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/foo_test.cc13
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.foo/manifest.json4
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/Android.bp77
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.avbpubkeybin0 -> 1032 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pem51
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pk8bin0 -> 2375 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.x509.pem34
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/manifest.json4
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.pony/pony_test.cc13
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/Android.bp74
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.avbpubkeybin0 -> 1032 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pem51
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pk8bin0 -> 2374 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.x509.pem34
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/manifest.json4
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/Android.bp66
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.avbpubkeybin0 -> 1032 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pem51
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pk8bin0 -> 2374 bytes
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.x509.pem34
-rw-r--r--tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/manifest.json4
-rw-r--r--tests/testdata/sharedlibs/build/include/sharedlibstest.h (renamed from tests/src/com/android/tests/apex/AdbdHostTest.java)20
-rw-r--r--tests/testdata/sharedlibs/build/noop.cc9
-rw-r--r--tests/testdata/sharedlibs/build/shared_libs_repack.py421
-rw-r--r--tests/testdata/sharedlibs/build/sharedlibstest.cpp (renamed from tests/src/com/android/tests/apex/ExtServicesHostTest.java)18
-rw-r--r--tests/testdata/sharedlibs/prebuilts/Android.bp301
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v1.libvX.apexbin0 -> 653957 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v2.libvY.apexbin0 -> 653957 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v1.libvX.apexbin0 -> 334169 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v2.libvY.apexbin0 -> 334169 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.baz_stripped.v1.libvX.apexbin0 -> 334171 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v1.libvX.apexbin0 -> 653959 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v2.libvY.apexbin0 -> 653959 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v1.libvX.apexbin0 -> 334171 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v2.libvY.apexbin0 -> 334171 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony.v1.libvZ.apexbin0 -> 653955 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony_stripped.v1.libvZ.apexbin0 -> 334167 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v1.libvX.apexbin0 -> 661523 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v2.libvY.apexbin0 -> 661523 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apexbin0 -> 661495 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v1.libvX.apexbin0 -> 1387020 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v2.libvY.apexbin0 -> 1387020 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v1.libvX.apexbin0 -> 333744 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v2.libvY.apexbin0 -> 333744 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.baz_stripped.v1.libvX.apexbin0 -> 334161 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v1.libvX.apexbin0 -> 879233 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v2.libvY.apexbin0 -> 879233 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v1.libvX.apexbin0 -> 334161 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v2.libvY.apexbin0 -> 334161 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony.v1.libvZ.apexbin0 -> 879229 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony_stripped.v1.libvZ.apexbin0 -> 334157 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v1.libvX.apexbin0 -> 1398028 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v2.libvY.apexbin0 -> 1398028 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apexbin0 -> 882665 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v1.libvX.apexbin0 -> 883333 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v2.libvY.apexbin0 -> 883333 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v1.libvX.apexbin0 -> 334169 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v2.libvY.apexbin0 -> 334169 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.baz_stripped.v1.libvX.apexbin0 -> 334171 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v1.libvX.apexbin0 -> 883335 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v2.libvY.apexbin0 -> 883335 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v1.libvX.apexbin0 -> 334171 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v2.libvY.apexbin0 -> 334171 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony.v1.libvZ.apexbin0 -> 883331 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony_stripped.v1.libvZ.apexbin0 -> 334167 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v1.libvX.apexbin0 -> 890899 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v2.libvY.apexbin0 -> 890899 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apexbin0 -> 890871 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v1.libvX.apexbin0 -> 1636876 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v2.libvY.apexbin0 -> 1636876 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v1.libvX.apexbin0 -> 333744 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v2.libvY.apexbin0 -> 333744 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.baz_stripped.v1.libvX.apexbin0 -> 334161 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v1.libvX.apexbin0 -> 903809 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v2.libvY.apexbin0 -> 903809 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v1.libvX.apexbin0 -> 334161 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v2.libvY.apexbin0 -> 334161 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony.v1.libvZ.apexbin0 -> 903805 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony_stripped.v1.libvZ.apexbin0 -> 334157 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v1.libvX.apexbin0 -> 1651980 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v2.libvY.apexbin0 -> 1651980 bytes
-rw-r--r--tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apexbin0 -> 911337 bytes
-rw-r--r--tests/util/com/android/tests/util/ModuleTestUtils.java247
-rw-r--r--tests/wifi-e2e-tests.xml25
-rw-r--r--tools/Android.bp57
-rw-r--r--tools/apex_compression_test.py323
-rw-r--r--tools/apex_compression_tool.py199
-rw-r--r--tools/create_apex_skeleton.sh73
-rw-r--r--tools/deapexer.py168
336 files changed, 16736 insertions, 3117 deletions
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_not_pre_installed_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_not_pre_installed_apex.asciipb
new file mode 100644
index 00000000..c822b0bc
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_not_pre_installed_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_arm64/com.android.apex.cts.shim_not_pre_installed.apex"
+ }
+ dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim_not_pre_installed.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v1_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v1_apex.asciipb
index 5bef6b27..543d8c5f 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v1_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v1_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v1.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_file_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_file_apex.asciipb
index f2032412..d2dfb053 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_file_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_file_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_additional_file.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb
index d5b1515a..c889a0d3 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_additional_folder.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apex.asciipb
index 5c1ecd8d..1e50d3e2 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb
index a3e05b58..4b98849a 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb
index d4f42de6..80044ca9 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_different_certificate.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb
index 30c1658b..f5aa4a92 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_different_package_name.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb
index 7a4ba73a..c504e63f 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_no_hashtree.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_rebootless_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_rebootless_apex.asciipb
new file mode 100644
index 00000000..1d000697
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_rebootless_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_arm64/com.android.apex.cts.shim.v2_rebootless.apex"
+ }
+ dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_rebootless.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb
index e046e3ab..cc9b5ace 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_sdk_target_p.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb
new file mode 100644
index 00000000..94aa04c2
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_arm64/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
+ }
+ dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb
index 6ea9a6b5..011e61c6 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb
index fe76dce4..e9de8cdf 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb
index c90ffbbe..361a6509 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb
index 779f4993..be2c4d01 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_unsigned_payload.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb
index 0bf9288a..d141aba8 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb
index 5a1baf9b..b1be9fa1 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb
index 316c8d12..99d90dd7 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb
index 3d5c0e90..069a9a26 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_wrong_sha.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_apex.asciipb
index e03ee6e7..7aca1421 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_rebootless_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_rebootless_apex.asciipb
new file mode 100644
index 00000000..d959e8ca
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_rebootless_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_arm64/com.android.apex.cts.shim.v3_rebootless.apex"
+ }
+ dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3_rebootless.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb
index daecf3c3..1afd6a1e 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_signed_bob.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb
index 420522bb..db11b22e 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__arm_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_not_pre_installed_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_not_pre_installed_apex.asciipb
new file mode 100644
index 00000000..0fc57dc4
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_not_pre_installed_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_x86_64/com.android.apex.cts.shim_not_pre_installed.apex"
+ }
+ dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim_not_pre_installed.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v1_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v1_apex.asciipb
index 239132ac..48d0ec27 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v1_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v1_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v1.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_file_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_file_apex.asciipb
index f84e0c89..bedd9d44 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_file_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_file_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_additional_file.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb
index 635e2bbd..6d573350 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_additional_folder_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_additional_folder.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apex.asciipb
index cea53405..80483302 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb
index 2622af8f..ab706c27 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_apk_in_apex_sdk_target_p_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb
index c25509a2..bfcdbfb6 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_certificate_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_different_certificate.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb
index ab8601fc..ec85bbb4 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_different_package_name_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_different_package_name.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb
index f1ccd780..c917485e 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_no_hashtree_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_no_hashtree.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_rebootless_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_rebootless_apex.asciipb
new file mode 100644
index 00000000..ecaff669
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_rebootless_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_rebootless.apex"
+ }
+ dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_rebootless.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb
index 1f24dedd..508be286 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sdk_target_p_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_sdk_target_p.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb
new file mode 100644
index 00000000..8cce7ac6
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_sign_payload_with_different_key_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
+ }
+ dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb
index e8c48e1e..5b1ebcea 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb
index faf0e28b..de02bc9a 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb
index 5f1e6642..42a8c798 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_signed_bob_rot_rollback_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb
index aef234a7..89fc7b6f 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_unsigned_payload_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_unsigned_payload.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb
index 87b7c50f..0b378c1b 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_post_install_hook_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb
index 727bce0d..3a5a6a87 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_with_pre_install_hook_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb
index 23155ba5..5c7f1186 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_without_apk_in_apex_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb
index 2190954d..beedae5d 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v2_wrong_sha_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_wrong_sha.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_apex.asciipb
index e7006ae6..53ddf732 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_rebootless_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_rebootless_apex.asciipb
new file mode 100644
index 00000000..2bc42d2d
--- /dev/null
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_rebootless_apex.asciipb
@@ -0,0 +1,13 @@
+drops {
+ android_build_drop {
+ build_id: "7533747"
+ target: "CtsShim"
+ source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_rebootless.apex"
+ }
+ dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3_rebootless.apex"
+ version: ""
+ version_group: ""
+ git_project: "platform/system/apex"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
+}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb
index bdcb62fb..0e4cbdf0 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_signed_bob.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb
index 4f258589..9faf7dfd 100644
--- a/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb
+++ b/.prebuilt_info/prebuilt_info_shim_prebuilts__x86_com_android_apex_cts_shim_v3_signed_bob_rot_apex.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "6508977"
+ build_id: "7533747"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
}
@@ -8,5 +8,6 @@ drops {
version: ""
version_group: ""
git_project: "platform/system/apex"
- git_branch: "rvc-dev"
+ git_branch: "sc-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 00000000..b198f250
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,68 @@
+// This introduces the module type library_linking_strategy_cc_defaults
+// To use in other Android.bp files, add the following lines:
+// soong_config_module_type_import {
+// from: "system/apex/Android.bp",
+// module_types: ["library_linking_strategy_cc_defaults"],
+// }
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+soong_config_string_variable {
+ name: "library_linking_strategy",
+ values: [
+ "prefer_static",
+ ],
+}
+
+soong_config_module_type {
+ name: "library_linking_strategy_cc_defaults",
+ module_type: "cc_defaults",
+ config_namespace: "ANDROID",
+ variables: ["library_linking_strategy"],
+ properties: [
+ "shared_libs",
+ "static_libs",
+ "stl",
+ ],
+}
+
+// TODO(b/178585590): delete this after testing linking strategy
+soong_config_module_type {
+ name: "library_linking_strategy_apex_defaults",
+ module_type: "apex_defaults",
+ config_namespace: "ANDROID",
+ variables: ["library_linking_strategy"],
+ properties: [
+ "manifest",
+ "min_sdk_version",
+ ],
+}
+
+library_linking_strategy_cc_defaults {
+ name: "library_linking_strategy_sample_defaults",
+ soong_config_variables: {
+ library_linking_strategy: {
+ prefer_static: {
+ static_libs: [
+ "libbase",
+ "liblog",
+ ],
+ stl: "c++_static",
+ },
+ conditions_default: {
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ },
+ },
+ },
+}
+
+cc_binary {
+ name: "library_linking_strategy_sample_binary",
+ srcs: ["library_linking_strategy.cc"],
+ defaults: ["library_linking_strategy_sample_defaults"],
+}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 4c6fbd61..ccaf5feb 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,6 +6,7 @@ clang_format = true
commit_msg_changeid_field = true
commit_msg_test_field = true
gofmt = true
+pylint3 = true
[Builtin Hooks Options]
-clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp \ No newline at end of file
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/apexd/Android.bp b/apexd/Android.bp
index e8d221d5..ba2de033 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -1,25 +1,46 @@
// List of clang-tidy checks that are reported as errors.
// Please keep this list ordered lexicographically.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
tidy_errors = [
+ "android-*",
+ "bugprone-infinite-loop",
+ "bugprone-macro-parentheses",
+ "bugprone-misplaced-widening-cast",
+ "bugprone-move-forwarding-reference",
+ "bugprone-sizeof-container",
+ "bugprone-sizeof-expression",
+ "bugprone-string-constructor",
+ "bugprone-terminating-continue",
+ "bugprone-undefined-memory-manipulation",
+ "bugprone-undelegated-constructor",
+ // "bugprone-unhandled-self-assignment", // found in apex_manifest.proto
+ "bugprone-unused-raii",
"cert-err34-c",
"google-default-arguments",
- "google-explicit-constructor",
+ // "google-explicit-constructor", // found in com_android_apex.h
+ "google-readability-avoid-underscore-in-googletest-name",
+ "google-readability-todo",
"google-runtime-int",
"google-runtime-member-string-references",
"misc-move-const-arg",
"misc-move-forwarding-reference",
- "misc-unused-parameters",
+ // "misc-unused-parameters", // found in apexd_utils.h
"misc-unused-using-decls",
"misc-use-after-move",
- "modernize-pass-by-value",
+ // "modernize-pass-by-value", // found in apex_database.h
"performance-faster-string-find",
"performance-for-range-copy",
"performance-implicit-conversion-in-loop",
"performance-inefficient-vector-operation",
"performance-move-const-arg",
- "performance-move-constructor-init",
+ // "performance-move-constructor-init", // found in apexd_loop.h
"performance-noexcept-move-constructor",
+ "performance-unnecessary-copy-initialization",
"performance-unnecessary-value-param",
+ // "readability-avoid-const-params-in-decls", // found in apexd.h
]
cc_defaults {
@@ -47,8 +68,8 @@ cc_defaults {
tidy_checks: tidy_errors,
tidy_checks_as_errors: tidy_errors,
tidy_flags: [
- "-format-style='file'",
- "--header-filter='system/apex/'",
+ "-format-style=file",
+ "-header-filter=system/apex/",
],
}
@@ -58,7 +79,6 @@ cc_defaults {
defaults: ["libapex-deps"],
shared_libs: [
"libbinder",
- "libselinux",
"liblog",
"liblogwrap",
],
@@ -69,6 +89,7 @@ cc_defaults {
"libext2_uuid",
"libverity_tree",
"libvold_binder",
+ "libxml2",
],
whole_static_libs: ["com.android.sysprop.apex"],
}
@@ -81,6 +102,8 @@ aidl_interface {
"aidl/android/apex/ApexInfoList.aidl",
"aidl/android/apex/ApexSessionInfo.aidl",
"aidl/android/apex/ApexSessionParams.aidl",
+ "aidl/android/apex/CompressedApexInfo.aidl",
+ "aidl/android/apex/CompressedApexInfoList.aidl",
"aidl/android/apex/IApexService.aidl",
],
local_include_dir: "aidl",
@@ -127,14 +150,15 @@ cc_library_static {
srcs: [
"apex_database.cpp",
"apexd.cpp",
+ "apexd_lifecycle.cpp",
"apexd_loop.cpp",
"apexd_prepostinstall.cpp",
"apexd_private.cpp",
- "apexd_prop.cpp",
"apexd_session.cpp",
"apexd_verity.cpp",
],
export_include_dirs: ["."],
+ generated_sources: ["apex-info-list"],
// Don't add shared/static libs here; add to libapexd_defaults instead.
}
@@ -170,6 +194,9 @@ cc_library_static {
static_libs: [
"libapexd",
],
+ cflags: [
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+ ],
}
cc_defaults {
@@ -180,6 +207,7 @@ cc_defaults {
"libcutils",
"libprotobuf-cpp-full",
"libziparchive",
+ "libselinux",
],
static_libs: [
"lib_apex_session_state_proto",
@@ -189,6 +217,7 @@ cc_defaults {
static: {
whole_static_libs: ["libc++fs"],
},
+ cpp_std: "experimental",
shared: {
static_libs: ["libc++fs"],
},
@@ -202,8 +231,8 @@ cc_library_static {
],
srcs: [
"apex_file.cpp",
+ "apex_file_repository.cpp",
"apex_manifest.cpp",
- "apex_preinstalled_data.cpp",
"apex_shim.cpp",
],
host_supported: true,
@@ -223,7 +252,7 @@ cc_library_static {
genrule {
// Generates an apex which has a different manifest outside the filesystem
// image.
- name: "gen_bad_apexes",
+ name: "gen_manifest_mismatch_apex",
out: ["apex.apexd_test_manifest_mismatch.apex"],
srcs: [":apex.apexd_test"],
tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
@@ -237,6 +266,38 @@ genrule {
}
genrule {
+ // Generates an apex which has a different manifest outside the filesystem
+ // image.
+ name: "gen_manifest_mismatch_apex_no_hashtree",
+ out: ["apex.apexd_test_no_hashtree_manifest_mismatch.apex"],
+ srcs: [":apex.apexd_test_no_hashtree"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop version 137 $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/apex.apexd_test_no_hashtree_manifest_mismatch.apex"
+}
+
+genrule {
+ // Generates an apex with a corrupted filesystem superblock, which should cause
+ // Apex::Open to fail
+ name: "gen_corrupt_superblock_apex",
+ out: ["apex.apexd_test_corrupt_superblock_apex.apex"],
+ srcs: [":apex.apexd_test"],
+ tools: ["soong_zip", "zipalign"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "dd if=/dev/zero of=$(genDir)/apex_payload.img conv=notrunc bs=1024 seek=1 count=1 && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/apex.apexd_test_corrupt_superblock_apex.apex"
+}
+
+genrule {
// Generates an apex with a corrupted filesystem image, which should cause
// dm-verity verification to fail
name: "gen_corrupt_apex",
@@ -252,6 +313,93 @@ genrule {
"$(genDir)/apex.apexd_test_corrupt_apex.apex"
}
+genrule {
+ // Extract the root digest with avbtool
+ name: "apex.apexd_test_digest",
+ out: ["apex.apexd_test_digest.txt"],
+ srcs: [":apex.apexd_test"],
+ tools: ["avbtool"],
+ cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
+ "$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
+ "| cut -c 3-| tee $(out)"
+}
+
+genrule {
+ // Extract the root digest with avbtool
+ name: "apex.apexd_test_f2fs_digest",
+ out: ["apex.apexd_test_f2fs_digest.txt"],
+ srcs: [":apex.apexd_test_f2fs"],
+ tools: ["avbtool"],
+ cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
+ "$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
+ "| cut -c 3-| tee $(out)"
+}
+
+genrule {
+ // Generates an apex which has same module name as apex.apexd_test.apex, but
+ // is actually signed with a different key.
+ name: "gen_key_mismatch_apex",
+ out: ["apex.apexd_test_different_key.apex"],
+ srcs: [":apex.apexd_test_no_inst_key"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop name com.android.apex.test_package $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/apex.apexd_test_different_key.apex"
+}
+
+genrule {
+ // Generates an apex which has same module name as apex.apexd_test.apex, but
+ // is actually signed with a different key.
+ name: "gen_key_mismatch_apex_v2",
+ out: ["apex.apexd_test_different_key_v2.apex"],
+ srcs: [":apex.apexd_test_no_inst_key"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop name com.android.apex.test_package $(genDir)/apex_manifest.pb && " +
+ "$(location conv_apex_manifest) setprop version 2 $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/apex.apexd_test_different_key_v2.apex"
+}
+
+genrule {
+ // Generates an apex which has a different manifest outside the filesystem
+ // image.
+ name: "gen_manifest_mismatch_rebootless_apex",
+ out: ["test.rebootless_apex_manifest_mismatch.apex"],
+ srcs: [":test.rebootless_apex_v1"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop version 137 $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/test.rebootless_apex_manifest_mismatch.apex"
+}
+
+genrule {
+ // Generates an apex with a corrupted filesystem image, which should cause
+ // dm-verity verification to fail
+ name: "gen_corrupt_rebootless_apex",
+ out: ["test.rebootless_apex_corrupted.apex"],
+ srcs: [":test.rebootless_apex_v1"],
+ tools: ["soong_zip", "zipalign"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "dd if=/dev/zero of=$(genDir)/apex_payload.img conv=notrunc bs=1024 seek=16 count=1 && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/test.rebootless_apex_corrupted.apex"
+}
+
cc_test {
name: "ApexTestCases",
defaults: [
@@ -266,18 +414,32 @@ cc_test {
],
data: [
":apex.apexd_test",
+ ":apex.apexd_test_f2fs",
+ ":apex.apexd_test_digest",
+ ":apex.apexd_test_f2fs_digest",
":apex.apexd_test_different_app",
":apex.apexd_test_no_hashtree",
":apex.apexd_test_no_hashtree_2",
":apex.apexd_test_no_inst_key",
+ ":apex.apexd_test_f2fs_no_inst_key",
":apex.apexd_test_nocode",
":apex.apexd_test_postinstall",
":apex.apexd_test_preinstall",
":apex.apexd_test_prepostinstall.fail",
":apex.apexd_test_v2",
":apex.corrupted_b146895998",
- ":gen_bad_apexes",
+ ":apex.banned_name",
+ ":gen_key_mismatch_apex",
+ ":gen_key_mismatch_apex_v2",
+ ":gen_key_mismatch_capex",
+ ":gen_manifest_mismatch_apex",
+ ":gen_manifest_mismatch_apex_no_hashtree",
+ ":gen_corrupt_superblock_apex",
":gen_corrupt_apex",
+ ":gen_capex_not_decompressible",
+ ":gen_capex_without_apex",
+ ":gen_capex_with_v2_apex",
+ ":gen_key_mismatch_with_original_capex",
":com.android.apex.cts.shim.v1_prebuilt",
":com.android.apex.cts.shim.v2_prebuilt",
":com.android.apex.cts.shim.v2_wrong_sha_prebuilt",
@@ -285,13 +447,41 @@ cc_test {
":com.android.apex.cts.shim.v2_additional_folder_prebuilt",
":com.android.apex.cts.shim.v2_with_pre_install_hook_prebuilt",
":com.android.apex.cts.shim.v2_with_post_install_hook_prebuilt",
+ ":com.android.apex.compressed_sharedlibs",
+ ":com.android.apex.compressed.v1",
+ ":com.android.apex.compressed.v1_different_digest",
+ ":com.android.apex.compressed.v1_different_digest_original",
+ ":com.android.apex.compressed.v1_original",
+ ":com.android.apex.compressed.v2",
+ ":com.android.apex.compressed.v2_original",
+ ":gen_manifest_mismatch_compressed_apex_v2",
"apexd_testdata/com.android.apex.test_package.avbpubkey",
+ "apexd_testdata/com.android.apex.compressed.avbpubkey",
+ ":com.android.apex.test.sharedlibs_generated.v1.libvX_prebuilt",
+ ":com.android.apex.test.sharedlibs_generated.v2.libvY_prebuilt",
+ ":test.rebootless_apex_v1",
+ ":test.rebootless_apex_v2",
+ ":test.rebootless_apex_v2_no_hashtree",
+ ":gen_manifest_mismatch_rebootless_apex",
+ ":gen_corrupt_rebootless_apex",
+ ":test.rebootless_apex_provides_sharedlibs",
+ ":test.rebootless_apex_provides_native_libs",
+ ":test.rebootless_apex_requires_shared_apex_libs",
+ ":test.rebootless_apex_jni_libs",
+ ":test.rebootless_apex_add_native_lib",
+ ":test.rebootless_apex_remove_native_lib",
+ ":test.rebootless_apex_app_in_apex",
+ ":test.rebootless_apex_priv_app_in_apex",
],
srcs: [
"apex_database_test.cpp",
"apex_file_test.cpp",
+ "apex_file_repository_test.cpp",
"apex_manifest_test.cpp",
+ "apexd_test.cpp",
+ "apexd_session_test.cpp",
"apexd_verity_test.cpp",
+ "apexd_utils_test.cpp",
"apexservice_test.cpp",
],
host_supported: false,
@@ -308,6 +498,7 @@ cc_test {
"libfs_mgr",
"libutils",
],
+ generated_sources: ["apex-info-list"],
test_suites: ["device-tests"],
test_config: "AndroidTest.xml",
}
@@ -330,3 +521,10 @@ cc_test {
test_config: "flattened_apex_test_config.xml",
}
+xsd_config {
+ name: "apex-info-list",
+ srcs: ["ApexInfoList.xsd"],
+ package_name: "com.android.apex",
+ api_dir: "apex-info-list-api",
+ gen_writer: true,
+}
diff --git a/apexd/ApexInfoList.xsd b/apexd/ApexInfoList.xsd
new file mode 100644
index 00000000..440b975f
--- /dev/null
+++ b/apexd/ApexInfoList.xsd
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="apex-info-list">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="apex-info" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="apex-info">
+ <xs:complexType>
+ <xs:attribute name="moduleName" type="xs:string" use="required"/>
+ <xs:attribute name="modulePath" type="xs:string" use="required"/>
+ <xs:attribute name="preinstalledModulePath" type="xs:string"/>
+ <xs:attribute name="versionCode" type="xs:long" use="required"/>
+ <xs:attribute name="versionName" type="xs:string" use="required"/>
+ <xs:attribute name="isFactory" type="xs:boolean" use="required"/>
+ <xs:attribute name="isActive" type="xs:boolean" use="required"/>
+ <xs:attribute name="lastUpdateMillis" type="xs:long"/>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>
diff --git a/apexd/OWNERS b/apexd/OWNERS
deleted file mode 100644
index ed9813ee..00000000
--- a/apexd/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-agampe@google.com
diff --git a/apexd/aidl/android/apex/ApexSessionInfo.aidl b/apexd/aidl/android/apex/ApexSessionInfo.aidl
index a7a6daf2..65da1135 100644
--- a/apexd/aidl/android/apex/ApexSessionInfo.aidl
+++ b/apexd/aidl/android/apex/ApexSessionInfo.aidl
@@ -29,4 +29,5 @@ parcelable ApexSessionInfo {
boolean isReverted;
boolean isRevertFailed;
@utf8InCpp String crashingNativeProcess;
+ @utf8InCpp String errorMessage;
}
diff --git a/tests/src/com/android/tests/apex/ConscryptHostTest.java b/apexd/aidl/android/apex/CompressedApexInfo.aidl
index 01cb840e..48015bd0 100644
--- a/tests/src/com/android/tests/apex/ConscryptHostTest.java
+++ b/apexd/aidl/android/apex/CompressedApexInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 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,15 +14,10 @@
* limitations under the License.
*/
-package com.android.tests.apex;
+package android.apex;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class ConscryptHostTest extends ApexE2EBaseHostTest {
+parcelable CompressedApexInfo {
+ @utf8InCpp String moduleName;
+ long versionCode;
+ long decompressedSize;
}
diff --git a/tests/src/com/android/tests/apex/IpSecHostTest.java b/apexd/aidl/android/apex/CompressedApexInfoList.aidl
index 765f4355..e1620bd1 100644
--- a/tests/src/com/android/tests/apex/IpSecHostTest.java
+++ b/apexd/aidl/android/apex/CompressedApexInfoList.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 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,13 +14,10 @@
* limitations under the License.
*/
-package com.android.tests.apex;
+package android.apex;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import android.apex.CompressedApexInfo;
-import org.junit.runner.RunWith;
-
-/** Test to check if Apex can be staged, activated and uninstalled successfully. */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class IpSecHostTest extends ApexE2EBaseHostTest {
+parcelable CompressedApexInfoList {
+ CompressedApexInfo[] apexInfos;
}
diff --git a/apexd/aidl/android/apex/IApexService.aidl b/apexd/aidl/android/apex/IApexService.aidl
index 3626d9ac..2d77a822 100644
--- a/apexd/aidl/android/apex/IApexService.aidl
+++ b/apexd/aidl/android/apex/IApexService.aidl
@@ -20,6 +20,7 @@ import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
+import android.apex.CompressedApexInfoList;
interface IApexService {
void submitStagedSession(in ApexSessionParams params, out ApexInfoList packages);
@@ -36,9 +37,9 @@ interface IApexService {
/**
* Copies the CE apex data directory for the given user to the backup
- * location, and returns the inode of the snapshot directory.
+ * location.
*/
- long snapshotCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
+ void snapshotCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
/**
* Restores the snapshot of the CE apex data directory for the given user and
@@ -52,6 +53,11 @@ interface IApexService {
void destroyDeSnapshots(int rollback_id);
/**
+ * Deletes credential-encrypted snapshots for the given user, for the given rollback id.
+ */
+ void destroyCeSnapshots(int user_id, int rollback_id);
+
+ /**
* Deletes all credential-encrypted snapshots for the given user, except for
* those listed in retain_rollback_ids.
*/
@@ -115,4 +121,42 @@ interface IApexService {
* on user builds. Only root is allowed to call this method.
*/
void remountPackages();
+ /**
+ * Forces apexd to recollect pre-installed data from the given |paths|.
+ *
+ * Not meant for use outside of testing. This call will not be functional
+ * on user builds. Only root is allowed to call this method.
+ */
+ void recollectPreinstalledData(in @utf8InCpp List<String> paths);
+ /**
+ * Forces apexd to recollect data apex from the given |path|.
+ *
+ * Not meant for use outside of testing. This call will not be functional
+ * on user builds. Only root is allowed to call this method.
+ */
+ void recollectDataApex(in @utf8InCpp String path, in@utf8InCpp String decompression_dir);
+
+ /**
+ * Informs apexd that the boot has completed.
+ */
+ void markBootCompleted();
+
+ /**
+ * Assuming the provided compressed APEX will be installed on next boot,
+ * calculate how much space will be required for decompression
+ */
+ long calculateSizeForCompressedApex(in CompressedApexInfoList compressed_apex_info_list);
+
+ /**
+ * Reserve space on /data partition for compressed APEX decompression. Returns error if
+ * reservation fails. If empty list is passed, then reserved space is deallocated.
+ */
+ void reserveSpaceForCompressedApex(in CompressedApexInfoList compressed_apex_info_list);
+
+ /**
+ * Performs a non-staged install of the given APEX.
+ * Note: don't confuse this to preInstall and postInstall binder calls which are only used to
+ * test corresponding features of APEX packages.
+ */
+ ApexInfo installAndActivatePackage(in @utf8InCpp String packagePath);
}
diff --git a/apexd/apex-info-list-api/current.txt b/apexd/apex-info-list-api/current.txt
new file mode 100644
index 00000000..8db5a9a0
--- /dev/null
+++ b/apexd/apex-info-list-api/current.txt
@@ -0,0 +1,45 @@
+// Signature format: 2.0
+package com.android.apex {
+
+ public class ApexInfo {
+ ctor public ApexInfo();
+ method public boolean getIsActive();
+ method public boolean getIsFactory();
+ method public long getLastUpdateMillis();
+ method public String getModuleName();
+ method public String getModulePath();
+ method public String getPreinstalledModulePath();
+ method public long getVersionCode();
+ method public String getVersionName();
+ method public void setIsActive(boolean);
+ method public void setIsFactory(boolean);
+ method public void setLastUpdateMillis(long);
+ method public void setModuleName(String);
+ method public void setModulePath(String);
+ method public void setPreinstalledModulePath(String);
+ method public void setVersionCode(long);
+ method public void setVersionName(String);
+ }
+
+ public class ApexInfoList {
+ ctor public ApexInfoList();
+ method public java.util.List<com.android.apex.ApexInfo> getApexInfo();
+ }
+
+ public class XmlParser {
+ ctor public XmlParser();
+ method public static com.android.apex.ApexInfo readApexInfo(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static com.android.apex.ApexInfoList readApexInfoList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public class XmlWriter implements java.io.Closeable {
+ ctor public XmlWriter(java.io.PrintWriter);
+ method public void close();
+ method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfoList) throws java.io.IOException;
+ method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfo) throws java.io.IOException;
+ }
+
+}
+
diff --git a/apexd/apex-info-list-api/last_current.txt b/apexd/apex-info-list-api/last_current.txt
new file mode 100644
index 00000000..8db5a9a0
--- /dev/null
+++ b/apexd/apex-info-list-api/last_current.txt
@@ -0,0 +1,45 @@
+// Signature format: 2.0
+package com.android.apex {
+
+ public class ApexInfo {
+ ctor public ApexInfo();
+ method public boolean getIsActive();
+ method public boolean getIsFactory();
+ method public long getLastUpdateMillis();
+ method public String getModuleName();
+ method public String getModulePath();
+ method public String getPreinstalledModulePath();
+ method public long getVersionCode();
+ method public String getVersionName();
+ method public void setIsActive(boolean);
+ method public void setIsFactory(boolean);
+ method public void setLastUpdateMillis(long);
+ method public void setModuleName(String);
+ method public void setModulePath(String);
+ method public void setPreinstalledModulePath(String);
+ method public void setVersionCode(long);
+ method public void setVersionName(String);
+ }
+
+ public class ApexInfoList {
+ ctor public ApexInfoList();
+ method public java.util.List<com.android.apex.ApexInfo> getApexInfo();
+ }
+
+ public class XmlParser {
+ ctor public XmlParser();
+ method public static com.android.apex.ApexInfo readApexInfo(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static com.android.apex.ApexInfoList readApexInfoList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ }
+
+ public class XmlWriter implements java.io.Closeable {
+ ctor public XmlWriter(java.io.PrintWriter);
+ method public void close();
+ method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfoList) throws java.io.IOException;
+ method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfo) throws java.io.IOException;
+ }
+
+}
+
diff --git a/apexd/apex-info-list-api/last_removed.txt b/apexd/apex-info-list-api/last_removed.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/apexd/apex-info-list-api/last_removed.txt
diff --git a/apexd/apex-info-list-api/removed.txt b/apexd/apex-info-list-api/removed.txt
new file mode 100644
index 00000000..d802177e
--- /dev/null
+++ b/apexd/apex-info-list-api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apexd/apex_constants.h b/apexd/apex_constants.h
index 4e989431..798afd5a 100644
--- a/apexd/apex_constants.h
+++ b/apexd/apex_constants.h
@@ -17,6 +17,7 @@
#pragma once
#include <string>
+#include <unordered_set>
#include <vector>
namespace android {
@@ -26,6 +27,8 @@ static constexpr const char* kApexDataDir = "/data/apex";
static constexpr const char* kActiveApexPackagesDataDir = "/data/apex/active";
static constexpr const char* kApexBackupDir = "/data/apex/backup";
static constexpr const char* kApexHashTreeDir = "/data/apex/hashtree";
+static constexpr const char* kApexDecompressedDir = "/data/apex/decompressed";
+static constexpr const char* kOtaReservedDir = "/data/apex/ota_reserved";
static constexpr const char* kApexPackageSystemDir = "/system/apex";
static constexpr const char* kApexPackageSystemExtDir = "/system_ext/apex";
static constexpr const char* kApexPackageVendorDir = "/vendor/apex";
@@ -39,6 +42,7 @@ static constexpr const char* kApexRoot = "/apex";
static constexpr const char* kStagedSessionsDir = "/data/app-staging";
static constexpr const char* kApexDataSubDir = "apexdata";
+static constexpr const char* kApexSharedLibsSubDir = "sharedlibs";
static constexpr const char* kApexSnapshotSubDir = "apexrollback";
static constexpr const char* kPreRestoreSuffix = "-prerestore";
@@ -47,8 +51,26 @@ static constexpr const char* kDeNDataDir = "/data/misc_de";
static constexpr const char* kCeDataDir = "/data/misc_ce";
static constexpr const char* kApexPackageSuffix = ".apex";
+static constexpr const char* kCompressedApexPackageSuffix = ".capex";
+static constexpr const char* kDecompressedApexPackageSuffix =
+ ".decompressed.apex";
+static constexpr const char* kOtaApexPackageSuffix = ".ota.apex";
static constexpr const char* kManifestFilenameJson = "apex_manifest.json";
static constexpr const char* kManifestFilenamePb = "apex_manifest.pb";
+
+static constexpr const char* kApexInfoList = "apex-info-list.xml";
+
+// These should be in-sync with system/sepolicy/private/property_contexts
+static constexpr const char* kApexStatusSysprop = "apexd.status";
+static constexpr const char* kApexStatusStarting = "starting";
+static constexpr const char* kApexStatusActivated = "activated";
+static constexpr const char* kApexStatusReady = "ready";
+
+// Banned APEX names
+static const std::unordered_set<std::string> kBannedApexName = {
+ kApexSharedLibsSubDir, // To avoid conflicts with predefined
+ // /apex/sharedlibs directory
+};
} // namespace apex
} // namespace android
diff --git a/apexd/apex_database.cpp b/apexd/apex_database.cpp
index d027f95c..7a1ee360 100644
--- a/apexd/apex_database.cpp
+++ b/apexd/apex_database.cpp
@@ -35,6 +35,9 @@
#include <utility>
using android::base::ConsumeSuffix;
+using android::base::EndsWith;
+using android::base::ErrnoError;
+using android::base::Error;
using android::base::ParseInt;
using android::base::ReadFileToString;
using android::base::Result;
@@ -76,12 +79,12 @@ class BlockDevice {
fs::path DevPath() const { return kDevBlock / name; }
Result<std::string> GetProperty(const std::string& property) const {
- auto propertyFile = SysPath() / property;
- std::string propertyValue;
- if (!ReadFileToString(propertyFile, &propertyValue)) {
+ auto property_file = SysPath() / property;
+ std::string property_value;
+ if (!ReadFileToString(property_file, &property_value)) {
return ErrnoError() << "Fail to read";
}
- return Trim(propertyValue);
+ return Trim(property_value);
}
std::vector<BlockDevice> GetSlaves() const {
@@ -100,17 +103,17 @@ class BlockDevice {
}
};
-std::pair<fs::path, fs::path> parseMountInfo(const std::string& mountInfo) {
- const auto& tokens = Split(mountInfo, " ");
+std::pair<fs::path, fs::path> ParseMountInfo(const std::string& mount_info) {
+ const auto& tokens = Split(mount_info, " ");
if (tokens.size() < 2) {
return std::make_pair("", "");
}
return std::make_pair(tokens[0], tokens[1]);
}
-std::pair<std::string, int> parseMountPoint(const std::string& mountPoint) {
- auto packageId = fs::path(mountPoint).filename();
- auto split = Split(packageId, "@");
+std::pair<std::string, int> ParseMountPoint(const std::string& mount_point) {
+ auto package_id = fs::path(mount_point).filename();
+ auto split = Split(package_id, "@");
if (split.size() == 2) {
int version;
if (!ParseInt(split[1], &version)) {
@@ -118,14 +121,17 @@ std::pair<std::string, int> parseMountPoint(const std::string& mountPoint) {
}
return std::make_pair(split[0], version);
}
- return std::make_pair(packageId, -1);
+ return std::make_pair(package_id, -1);
}
-bool isActiveMountPoint(const std::string& mountPoint) {
- return (mountPoint.find('@') == std::string::npos);
+bool IsActiveMountPoint(const std::string& mount_point) {
+ return (mount_point.find('@') == std::string::npos);
}
Result<void> PopulateLoopInfo(const BlockDevice& top_device,
+ const std::string& active_apex_dir,
+ const std::string& decompression_dir,
+ const std::string& apex_hash_tree_dir,
MountedApexData* apex_data) {
std::vector<BlockDevice> slaves = top_device.GetSlaves();
if (slaves.size() != 1 && slaves.size() != 2) {
@@ -147,18 +153,22 @@ Result<void> PopulateLoopInfo(const BlockDevice& top_device,
// Enforce following invariant:
// * slaves[0] always represents a data loop device
// * if size = 2 then slaves[1] represents an external hashtree loop device
+ auto is_data_loop_device = [&](const std::string& backing_file) {
+ return StartsWith(backing_file, active_apex_dir) ||
+ StartsWith(backing_file, decompression_dir);
+ };
if (slaves.size() == 2) {
- if (!StartsWith(backing_files[0], kActiveApexPackagesDataDir)) {
+ if (!is_data_loop_device(backing_files[0])) {
std::swap(slaves[0], slaves[1]);
std::swap(backing_files[0], backing_files[1]);
}
}
- if (!StartsWith(backing_files[0], kActiveApexPackagesDataDir)) {
+ if (!is_data_loop_device(backing_files[0])) {
return Error() << "Data loop device " << slaves[0].DevPath()
<< " has unexpected backing file " << backing_files[0];
}
if (slaves.size() == 2) {
- if (!StartsWith(backing_files[1], kApexHashTreeDir)) {
+ if (!StartsWith(backing_files[1], apex_hash_tree_dir)) {
return Error() << "Hashtree loop device " << slaves[1].DevPath()
<< " has unexpected backing file " << backing_files[1];
}
@@ -171,7 +181,7 @@ Result<void> PopulateLoopInfo(const BlockDevice& top_device,
// This is not the right place to do this normalization, but proper solution
// will require some refactoring first. :(
-// TODO(ioffe): introduce MountedApexDataBuilder and delegate all
+// TODO(b/158469911): introduce MountedApexDataBuilder and delegate all
// building/normalization logic to it.
void NormalizeIfDeleted(MountedApexData* apex_data) {
std::string_view full_path = apex_data->full_path;
@@ -188,18 +198,22 @@ void NormalizeIfDeleted(MountedApexData* apex_data) {
apex_data->full_path = full_path;
}
-Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
- const std::string& mountPoint) {
+Result<MountedApexData> ResolveMountInfo(
+ const BlockDevice& block, const std::string& mount_point,
+ const std::string& active_apex_dir, const std::string& decompression_dir,
+ const std::string& apex_hash_tree_dir) {
+ bool temp_mount = EndsWith(mount_point, ".tmp");
// Now, see if it is dm-verity or loop mounted
switch (block.GetType()) {
case LoopDevice: {
- auto backingFile = block.GetProperty("loop/backing_file");
- if (!backingFile.ok()) {
- return backingFile.error();
+ auto backing_file = block.GetProperty("loop/backing_file");
+ if (!backing_file.ok()) {
+ return backing_file.error();
}
- auto result = MountedApexData(block.DevPath(), *backingFile, mountPoint,
+ auto result = MountedApexData(block.DevPath(), *backing_file, mount_point,
/* device_name= */ "",
- /* hashtree_loop_name= */ "");
+ /* hashtree_loop_name= */ "",
+ /* is_temp_mount */ temp_mount);
NormalizeIfDeleted(&result);
return result;
}
@@ -209,9 +223,12 @@ Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
return name.error();
}
MountedApexData result;
- result.mount_point = mountPoint;
+ result.mount_point = mount_point;
result.device_name = *name;
- if (auto status = PopulateLoopInfo(block, &result); !status.ok()) {
+ result.is_temp_mount = temp_mount;
+ auto status = PopulateLoopInfo(block, active_apex_dir, decompression_dir,
+ apex_hash_tree_dir, &result);
+ if (!status.ok()) {
return status.error();
}
NormalizeIfDeleted(&result);
@@ -246,40 +263,45 @@ Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
// By synchronizing the mounts info with Database on startup,
// Apexd serves the correct package list even on the devices
// which are not ro.apex.updatable.
-void MountedApexDatabase::PopulateFromMounts() {
+void MountedApexDatabase::PopulateFromMounts(
+ const std::string& active_apex_dir, const std::string& decompression_dir,
+ const std::string& apex_hash_tree_dir) REQUIRES(!mounted_apexes_mutex_) {
LOG(INFO) << "Populating APEX database from mounts...";
- std::unordered_map<std::string, int> activeVersions;
+ std::unordered_map<std::string, int> active_versions;
std::ifstream mounts("/proc/mounts");
std::string line;
+ std::lock_guard lock(mounted_apexes_mutex_);
while (std::getline(mounts, line)) {
- auto [block, mountPoint] = parseMountInfo(line);
- // TODO(jooyung): ignore tmp mount?
- if (fs::path(mountPoint).parent_path() != kApexRoot) {
+ auto [block, mount_point] = ParseMountInfo(line);
+ // TODO(b/158469914): distinguish between temp and non-temp mounts
+ if (fs::path(mount_point).parent_path() != kApexRoot) {
continue;
}
- if (isActiveMountPoint(mountPoint)) {
+ if (IsActiveMountPoint(mount_point)) {
continue;
}
- auto mountData = resolveMountInfo(BlockDevice(block), mountPoint);
- if (!mountData.ok()) {
- LOG(WARNING) << "Can't resolve mount info " << mountData.error();
+ auto mount_data =
+ ResolveMountInfo(BlockDevice(block), mount_point, active_apex_dir,
+ decompression_dir, apex_hash_tree_dir);
+ if (!mount_data.ok()) {
+ LOG(WARNING) << "Can't resolve mount info " << mount_data.error();
continue;
}
- auto [package, version] = parseMountPoint(mountPoint);
- AddMountedApex(package, false, *mountData);
+ auto [package, version] = ParseMountPoint(mount_point);
+ AddMountedApexLocked(package, false, *mount_data);
- auto active = activeVersions[package] < version;
+ auto active = active_versions[package] < version;
if (active) {
- activeVersions[package] = version;
- SetLatest(package, mountData->full_path);
+ active_versions[package] = version;
+ SetLatestLocked(package, mount_data->full_path);
}
- LOG(INFO) << "Found " << mountPoint << " backed by"
- << (mountData->deleted ? " deleted " : " ") << "file "
- << mountData->full_path;
+ LOG(INFO) << "Found " << mount_point << " backed by"
+ << (mount_data->deleted ? " deleted " : " ") << "file "
+ << mount_data->full_path;
}
LOG(INFO) << mounted_apexes_.size() << " packages restored.";
diff --git a/apexd/apex_database.h b/apexd/apex_database.h
index 1fb5bd3a..113b88ec 100644
--- a/apexd/apex_database.h
+++ b/apexd/apex_database.h
@@ -18,10 +18,14 @@
#define ANDROID_APEXD_APEX_DATABASE_H_
#include <map>
+#include <mutex>
+#include <optional>
#include <string>
#include <unordered_set>
#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/thread_annotations.h>
namespace android {
namespace apex {
@@ -40,17 +44,21 @@ class MountedApexDatabase {
std::string hashtree_loop_name;
// Whenever apex file specified in full_path was deleted.
bool deleted;
+ // Whether the mount is a temp mount or not.
+ bool is_temp_mount;
MountedApexData() {}
MountedApexData(const std::string& loop_name, const std::string& full_path,
const std::string& mount_point,
const std::string& device_name,
- const std::string& hashtree_loop_name)
+ const std::string& hashtree_loop_name,
+ bool is_temp_mount = false)
: loop_name(loop_name),
full_path(full_path),
mount_point(mount_point),
device_name(device_name),
- hashtree_loop_name(hashtree_loop_name) {}
+ hashtree_loop_name(hashtree_loop_name),
+ is_temp_mount(is_temp_mount) {}
inline bool operator<(const MountedApexData& rhs) const {
int compare_val = loop_name.compare(rhs.loop_name);
@@ -81,42 +89,10 @@ class MountedApexDatabase {
}
};
- inline void CheckAtMostOneLatest() {
- for (const auto& apex_set : mounted_apexes_) {
- size_t count = 0;
- for (const auto& pair : apex_set.second) {
- if (pair.second) {
- count++;
- }
- }
- CHECK_LE(count, 1u) << apex_set.first;
- }
- }
-
- inline void CheckUniqueLoopDm() {
- std::unordered_set<std::string> loop_devices;
- std::unordered_set<std::string> dm_devices;
- for (const auto& apex_set : mounted_apexes_) {
- for (const auto& pair : apex_set.second) {
- if (pair.first.loop_name != "") {
- CHECK(loop_devices.insert(pair.first.loop_name).second)
- << "Duplicate loop device: " << pair.first.loop_name;
- }
- if (pair.first.device_name != "") {
- CHECK(dm_devices.insert(pair.first.device_name).second)
- << "Duplicate dm device: " << pair.first.device_name;
- }
- if (pair.first.hashtree_loop_name != "") {
- CHECK(loop_devices.insert(pair.first.hashtree_loop_name).second)
- << "Duplicate loop device: " << pair.first.hashtree_loop_name;
- }
- }
- }
- }
-
template <typename... Args>
- inline void AddMountedApex(const std::string& package, bool latest,
- Args&&... args) {
+ inline void AddMountedApexLocked(const std::string& package, bool latest,
+ Args&&... args)
+ REQUIRES(mounted_apexes_mutex_) {
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
auto insert_it =
@@ -133,8 +109,18 @@ class MountedApexDatabase {
CheckUniqueLoopDm();
}
+ template <typename... Args>
+ inline void AddMountedApex(const std::string& package, bool latest,
+ Args&&... args) REQUIRES(!mounted_apexes_mutex_) {
+ std::lock_guard lock(mounted_apexes_mutex_);
+ AddMountedApexLocked(package, latest, args...);
+ }
+
inline void RemoveMountedApex(const std::string& package,
- const std::string& full_path) {
+ const std::string& full_path,
+ bool match_temp_mounts = false)
+ REQUIRES(!mounted_apexes_mutex_) {
+ std::lock_guard lock(mounted_apexes_mutex_);
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
return;
@@ -143,7 +129,8 @@ class MountedApexDatabase {
auto& pkg_map = it->second;
for (auto pkg_it = pkg_map.begin(); pkg_it != pkg_map.end(); ++pkg_it) {
- if (pkg_it->first.full_path == full_path) {
+ if (pkg_it->first.full_path == full_path &&
+ pkg_it->first.is_temp_mount == match_temp_mounts) {
pkg_map.erase(pkg_it);
return;
}
@@ -151,7 +138,15 @@ class MountedApexDatabase {
}
inline void SetLatest(const std::string& package,
- const std::string& full_path) {
+ const std::string& full_path)
+ REQUIRES(!mounted_apexes_mutex_) {
+ std::lock_guard lock(mounted_apexes_mutex_);
+ SetLatestLocked(package, full_path);
+ }
+
+ inline void SetLatestLocked(const std::string& package,
+ const std::string& full_path)
+ REQUIRES(mounted_apexes_mutex_) {
auto it = mounted_apexes_.find(package);
CHECK(it != mounted_apexes_.end());
@@ -173,47 +168,108 @@ class MountedApexDatabase {
LOG(FATAL) << "Did not find " << package << " " << full_path;
}
- inline void UnsetLatestForall(const std::string& package) {
- auto it = mounted_apexes_.find(package);
- if (it == mounted_apexes_.end()) {
- return;
- }
- for (auto& data : it->second) {
- data.second = false;
- }
- }
-
template <typename T>
- inline void ForallMountedApexes(const std::string& package,
- const T& handler) const {
+ inline void ForallMountedApexes(const std::string& package, const T& handler,
+ bool match_temp_mounts = false) const
+ REQUIRES(!mounted_apexes_mutex_) {
+ std::lock_guard lock(mounted_apexes_mutex_);
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
return;
}
for (auto& pair : it->second) {
- handler(pair.first, pair.second);
+ if (pair.first.is_temp_mount == match_temp_mounts) {
+ handler(pair.first, pair.second);
+ }
}
}
template <typename T>
- inline void ForallMountedApexes(const T& handler) const {
+ inline void ForallMountedApexes(const T& handler,
+ bool match_temp_mounts = false) const
+ REQUIRES(!mounted_apexes_mutex_) {
+ std::lock_guard lock(mounted_apexes_mutex_);
for (const auto& pkg : mounted_apexes_) {
for (const auto& pair : pkg.second) {
- handler(pkg.first, pair.first, pair.second);
+ if (pair.first.is_temp_mount == match_temp_mounts) {
+ handler(pkg.first, pair.first, pair.second);
+ }
}
}
}
- void PopulateFromMounts();
+ inline std::optional<MountedApexData> GetLatestMountedApex(
+ const std::string& package) REQUIRES(!mounted_apexes_mutex_) {
+ std::optional<MountedApexData> ret;
+ ForallMountedApexes(package,
+ [&ret](const MountedApexData& data, bool latest) {
+ if (latest) {
+ ret.emplace(data);
+ }
+ });
+ return ret;
+ }
+
+ void PopulateFromMounts(const std::string& active_apex_dir,
+ const std::string& decompression_dir,
+ const std::string& apex_hash_tree_dir);
+
+ // Resets state of the database. Should only be used in testing.
+ inline void Reset() REQUIRES(!mounted_apexes_mutex_) {
+ std::lock_guard lock(mounted_apexes_mutex_);
+ mounted_apexes_.clear();
+ }
private:
// A map from package name to mounted apexes.
// Note: using std::maps to
// a) so we do not have to worry about iterator invalidation.
// b) do not have to const_cast (over std::set)
- // TODO: Eventually this structure (and functions) need to be guarded by
- // locks.
- std::map<std::string, std::map<MountedApexData, bool>> mounted_apexes_;
+ // TODO(b/158467745): This structure (and functions) need to be guarded by
+ // locks.
+ std::map<std::string, std::map<MountedApexData, bool>> mounted_apexes_
+ GUARDED_BY(mounted_apexes_mutex_);
+
+ // To fix thread safety negative capability warning
+ class Mutex : public std::mutex {
+ public:
+ // for negative capabilities
+ const Mutex& operator!() const { return *this; }
+ };
+ mutable Mutex mounted_apexes_mutex_;
+
+ inline void CheckAtMostOneLatest() REQUIRES(mounted_apexes_mutex_) {
+ for (const auto& apex_set : mounted_apexes_) {
+ size_t count = 0;
+ for (const auto& pair : apex_set.second) {
+ if (pair.second) {
+ count++;
+ }
+ }
+ CHECK_LE(count, 1u) << apex_set.first;
+ }
+ }
+
+ inline void CheckUniqueLoopDm() REQUIRES(mounted_apexes_mutex_) {
+ std::unordered_set<std::string> loop_devices;
+ std::unordered_set<std::string> dm_devices;
+ for (const auto& apex_set : mounted_apexes_) {
+ for (const auto& pair : apex_set.second) {
+ if (pair.first.loop_name != "") {
+ CHECK(loop_devices.insert(pair.first.loop_name).second)
+ << "Duplicate loop device: " << pair.first.loop_name;
+ }
+ if (pair.first.device_name != "") {
+ CHECK(dm_devices.insert(pair.first.device_name).second)
+ << "Duplicate dm device: " << pair.first.device_name;
+ }
+ if (pair.first.hashtree_loop_name != "") {
+ CHECK(loop_devices.insert(pair.first.hashtree_loop_name).second)
+ << "Duplicate loop device: " << pair.first.hashtree_loop_name;
+ }
+ }
+ }
+ }
};
} // namespace apex
diff --git a/apexd/apex_database_test.cpp b/apexd/apex_database_test.cpp
index 522619fc..8c3a98d0 100644
--- a/apexd/apex_database_test.cpp
+++ b/apexd/apex_database_test.cpp
@@ -35,6 +35,7 @@ TEST(MountedApexDataTest, LinearOrder) {
constexpr const char* kDm[] = {"dm1", "dm2", "dm3"};
constexpr const char* kHashtreeLoopName[] = {"hash-loop1", "hash-loop2",
"hash-loop3"};
+ // NOLINTNEXTLINE(bugprone-sizeof-expression)
constexpr size_t kCount = arraysize(kLoopName) * arraysize(kPath) *
arraysize(kMount) * arraysize(kDm);
@@ -200,6 +201,37 @@ TEST(ApexDatabaseTest, MountMultiple) {
kDeviceName[3], kHashtreeLoopName[3]));
}
+TEST(ApexDatabaseTest, GetLatestMountedApex) {
+ constexpr const char* kPackage = "package";
+ constexpr const char* kLoopName = "loop";
+ constexpr const char* kPath = "path";
+ constexpr const char* kMountPoint = "mount";
+ constexpr const char* kDeviceName = "dev";
+ constexpr const char* kHashtreeLoopName = "hash-loop";
+
+ MountedApexDatabase db;
+ ASSERT_EQ(CountPackages(db), 0u);
+
+ db.AddMountedApex(kPackage, true, kLoopName, kPath, kMountPoint, kDeviceName,
+ kHashtreeLoopName);
+
+ auto ret = db.GetLatestMountedApex(kPackage);
+ MountedApexData expected(kLoopName, kPath, kMountPoint, kDeviceName,
+ kHashtreeLoopName);
+ ASSERT_TRUE(ret.has_value());
+ ASSERT_EQ(ret->loop_name, std::string(kLoopName));
+ ASSERT_EQ(ret->full_path, std::string(kPath));
+ ASSERT_EQ(ret->mount_point, std::string(kMountPoint));
+ ASSERT_EQ(ret->device_name, std::string(kDeviceName));
+ ASSERT_EQ(ret->hashtree_loop_name, std::string(kHashtreeLoopName));
+}
+
+TEST(ApexDatabaseTest, GetLatestMountedApexReturnsNullopt) {
+ MountedApexDatabase db;
+ auto ret = db.GetLatestMountedApex("no-such-name");
+ ASSERT_FALSE(ret.has_value());
+}
+
#pragma clang diagnostic push
// error: 'ReturnSentinel' was marked unused but was used
// [-Werror,-Wused-but-marked-unused]
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index f91b5d18..c043defa 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -23,61 +23,108 @@
#include <filesystem>
#include <fstream>
+#include <span>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
-#include <google/protobuf/util/message_differencer.h>
#include <libavb/libavb.h>
+#include <ziparchive/zip_archive.h>
#include "apex_constants.h"
-#include "apex_preinstalled_data.h"
#include "apexd_utils.h"
-#include "string_log.h"
-using android::base::EndsWith;
+using android::base::borrowed_fd;
+using android::base::ErrnoError;
using android::base::Error;
using android::base::ReadFullyAtOffset;
+using android::base::RemoveFileIfExists;
using android::base::Result;
-using android::base::StartsWith;
using android::base::unique_fd;
-using google::protobuf::util::MessageDifferencer;
+using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
namespace {
constexpr const char* kImageFilename = "apex_payload.img";
+constexpr const char* kCompressedApexFilename = "original_apex";
constexpr const char* kBundledPublicKeyFilename = "apex_pubkey";
+struct FsMagic {
+ const char* type;
+ int32_t offset;
+ int16_t len;
+ const char* magic;
+};
+constexpr const FsMagic kFsType[] = {{"f2fs", 1024, 4, "\x10\x20\xf5\xf2"},
+ {"ext4", 1024 + 0x38, 2, "\123\357"}};
+
+Result<std::string> RetrieveFsType(borrowed_fd fd, int32_t image_offset) {
+ for (const auto& fs : kFsType) {
+ char buf[fs.len];
+ if (!ReadFullyAtOffset(fd, buf, fs.len, image_offset + fs.offset)) {
+ return ErrnoError() << "Couldn't read filesystem magic";
+ }
+ if (memcmp(buf, fs.magic, fs.len) == 0) {
+ return std::string(fs.type);
+ }
+ }
+ return Error() << "Couldn't find filesystem magic";
+}
+
} // namespace
Result<ApexFile> ApexFile::Open(const std::string& path) {
- int32_t image_offset;
- size_t image_size;
+ std::optional<int32_t> image_offset;
+ std::optional<size_t> image_size;
std::string manifest_content;
std::string pubkey;
+ std::optional<std::string> fs_type;
+ ZipEntry entry;
+
+ unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
+ if (fd < 0) {
+ return Error() << "Failed to open package " << path << ": "
+ << "I/O error";
+ }
ZipArchiveHandle handle;
auto handle_guard =
android::base::make_scope_guard([&handle] { CloseArchive(handle); });
- int ret = OpenArchive(path.c_str(), &handle);
+ int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle, false);
if (ret < 0) {
return Error() << "Failed to open package " << path << ": "
<< ErrorCodeString(ret);
}
- // Locate the mountable image within the zipfile and store offset and size.
- ZipEntry entry;
- ret = FindEntry(handle, kImageFilename, &entry);
+ bool is_compressed = true;
+ ret = FindEntry(handle, kCompressedApexFilename, &entry);
if (ret < 0) {
- return Error() << "Could not find entry \"" << kImageFilename
- << "\" in package " << path << ": " << ErrorCodeString(ret);
+ is_compressed = false;
+ }
+
+ if (!is_compressed) {
+ // Locate the mountable image within the zipfile and store offset and size.
+ ret = FindEntry(handle, kImageFilename, &entry);
+ if (ret < 0) {
+ return Error() << "Could not find entry \"" << kImageFilename
+ << "\" or \"" << kCompressedApexFilename
+ << "\" in package " << path << ": "
+ << ErrorCodeString(ret);
+ }
+ image_offset = entry.offset;
+ image_size = entry.uncompressed_length;
+
+ auto fs_type_result = RetrieveFsType(fd, image_offset.value());
+ if (!fs_type_result.ok()) {
+ return Error() << "Failed to retrieve filesystem type for " << path
+ << ": " << fs_type_result.error();
+ }
+ fs_type = std::move(*fs_type_result);
}
- image_offset = entry.offset;
- image_size = entry.uncompressed_length;
ret = FindEntry(handle, kManifestFilenamePb, &entry);
if (ret < 0) {
@@ -112,8 +159,20 @@ Result<ApexFile> ApexFile::Open(const std::string& path) {
return manifest.error();
}
- return ApexFile(path, image_offset, image_size, std::move(*manifest), pubkey,
- isPathForBuiltinApexes(path));
+ if (is_compressed && manifest->providesharedapexlibs()) {
+ return Error() << "Apex providing sharedlibs shouldn't be compressed";
+ }
+
+ // b/179211712 the stored path should be the realpath, otherwise the path we
+ // get by scanning the directory would be different from the path we get
+ // by reading /proc/mounts, if the apex file is on a symlink dir.
+ std::string realpath;
+ if (!android::base::Realpath(path, &realpath)) {
+ return ErrnoError() << "can't get realpath of " << path;
+ }
+
+ return ApexFile(realpath, image_offset, image_size, std::move(*manifest),
+ pubkey, fs_type, is_compressed);
}
// AVB-related code.
@@ -122,7 +181,7 @@ namespace {
static constexpr int kVbMetaMaxSize = 64 * 1024;
-std::string bytes_to_hex(const uint8_t* bytes, size_t bytes_len) {
+std::string BytesToHex(const uint8_t* bytes, size_t bytes_len) {
std::ostringstream s;
s << std::hex << std::setfill('0');
@@ -132,28 +191,32 @@ std::string bytes_to_hex(const uint8_t* bytes, size_t bytes_len) {
return s.str();
}
-std::string getSalt(const AvbHashtreeDescriptor& desc,
- const uint8_t* trailingData) {
- const uint8_t* desc_salt = trailingData + desc.partition_name_len;
+std::string GetSalt(const AvbHashtreeDescriptor& desc,
+ const uint8_t* trailing_data) {
+ const uint8_t* desc_salt = trailing_data + desc.partition_name_len;
- return bytes_to_hex(desc_salt, desc.salt_len);
+ return BytesToHex(desc_salt, desc.salt_len);
}
-std::string getDigest(const AvbHashtreeDescriptor& desc,
- const uint8_t* trailingData) {
+std::string GetDigest(const AvbHashtreeDescriptor& desc,
+ const uint8_t* trailing_data) {
const uint8_t* desc_digest =
- trailingData + desc.partition_name_len + desc.salt_len;
+ trailing_data + desc.partition_name_len + desc.salt_len;
- return bytes_to_hex(desc_digest, desc.root_digest_len);
+ return BytesToHex(desc_digest, desc.root_digest_len);
}
-Result<std::unique_ptr<AvbFooter>> getAvbFooter(const ApexFile& apex,
+Result<std::unique_ptr<AvbFooter>> GetAvbFooter(const ApexFile& apex,
const unique_fd& fd) {
std::array<uint8_t, AVB_FOOTER_SIZE> footer_data;
auto footer = std::make_unique<AvbFooter>();
// The AVB footer is located in the last part of the image
- off_t offset = apex.GetImageSize() + apex.GetImageOffset() - AVB_FOOTER_SIZE;
+ if (!apex.GetImageOffset() || !apex.GetImageSize()) {
+ return Error() << "Cannot check avb footer without image offset and size";
+ }
+ off_t offset = apex.GetImageSize().value() + apex.GetImageOffset().value() -
+ AVB_FOOTER_SIZE;
int ret = lseek(fd, offset, SEEK_SET);
if (ret == -1) {
return ErrnoError() << "Couldn't seek to AVB footer";
@@ -179,8 +242,10 @@ bool CompareKeys(const uint8_t* key, size_t length,
memcmp(&public_key_content[0], key, length) == 0;
}
-Result<void> verifyVbMetaSignature(const ApexFile& apex, const uint8_t* data,
- size_t length) {
+// Verifies correctness of vbmeta and returns public key it was signed with.
+Result<std::span<const uint8_t>> VerifyVbMetaSignature(const ApexFile& apex,
+ const uint8_t* data,
+ size_t length) {
const uint8_t* pk;
size_t pk_len;
AvbVBMetaVerifyResult res;
@@ -201,49 +266,46 @@ Result<void> verifyVbMetaSignature(const ApexFile& apex, const uint8_t* data,
return Error() << "Error verifying " << apex.GetPath() << ": "
<< "unsupported version";
default:
- return Errorf("Unknown vmbeta_image_verify return value");
+ return Error() << "Unknown vmbeta_image_verify return value : " << res;
}
- Result<const std::string> public_key = getApexKey(apex.GetManifest().name());
- if (public_key.ok()) {
- // TODO(b/115718846)
- // We need to decide whether we need rollback protection, and whether
- // we can use the rollback protection provided by libavb.
- if (!CompareKeys(pk, pk_len, *public_key)) {
- return Error() << "Error verifying " << apex.GetPath() << ": "
- << "public key doesn't match the pre-installed one";
- }
- } else {
- return public_key.error();
- }
- LOG(VERBOSE) << apex.GetPath() << ": public key matches.";
- return {};
+ return std::span<const uint8_t>(pk, pk_len);
}
-Result<std::unique_ptr<uint8_t[]>> verifyVbMeta(const ApexFile& apex,
+Result<std::unique_ptr<uint8_t[]>> VerifyVbMeta(const ApexFile& apex,
const unique_fd& fd,
- const AvbFooter& footer) {
+ const AvbFooter& footer,
+ const std::string& public_key) {
if (footer.vbmeta_size > kVbMetaMaxSize) {
return Errorf("VbMeta size in footer exceeds kVbMetaMaxSize.");
}
- off_t offset = apex.GetImageOffset() + footer.vbmeta_offset;
+ if (!apex.GetImageOffset()) {
+ return Error() << "Cannot check VbMeta size without image offset";
+ }
+
+ off_t offset = apex.GetImageOffset().value() + footer.vbmeta_offset;
std::unique_ptr<uint8_t[]> vbmeta_buf(new uint8_t[footer.vbmeta_size]);
if (!ReadFullyAtOffset(fd, vbmeta_buf.get(), footer.vbmeta_size, offset)) {
return ErrnoError() << "Couldn't read AVB meta-data";
}
- Result<void> st =
- verifyVbMetaSignature(apex, vbmeta_buf.get(), footer.vbmeta_size);
+ Result<std::span<const uint8_t>> st =
+ VerifyVbMetaSignature(apex, vbmeta_buf.get(), footer.vbmeta_size);
if (!st.ok()) {
return st.error();
}
+ if (!CompareKeys(st->data(), st->size(), public_key)) {
+ return Error() << "Error verifying " << apex.GetPath() << " : "
+ << "public key doesn't match the pre-installed one";
+ }
+
return vbmeta_buf;
}
-Result<const AvbHashtreeDescriptor*> findDescriptor(uint8_t* vbmeta_data,
+Result<const AvbHashtreeDescriptor*> FindDescriptor(uint8_t* vbmeta_data,
size_t vbmeta_size) {
const AvbDescriptor** descriptors;
size_t num_descriptors;
@@ -277,118 +339,127 @@ Result<const AvbHashtreeDescriptor*> findDescriptor(uint8_t* vbmeta_data,
return Errorf("Couldn't find any AVB hashtree descriptors.");
}
-Result<std::unique_ptr<AvbHashtreeDescriptor>> verifyDescriptor(
+Result<std::unique_ptr<AvbHashtreeDescriptor>> VerifyDescriptor(
const AvbHashtreeDescriptor* desc) {
- auto verifiedDesc = std::make_unique<AvbHashtreeDescriptor>();
+ auto verified_desc = std::make_unique<AvbHashtreeDescriptor>();
if (!avb_hashtree_descriptor_validate_and_byteswap(desc,
- verifiedDesc.get())) {
+ verified_desc.get())) {
return Errorf("Couldn't validate AvbDescriptor.");
}
- return verifiedDesc;
+ return verified_desc;
}
} // namespace
-Result<ApexVerityData> ApexFile::VerifyApexVerity() const {
- ApexVerityData verityData;
+Result<ApexVerityData> ApexFile::VerifyApexVerity(
+ const std::string& public_key) const {
+ if (IsCompressed()) {
+ return Error() << "Cannot verify ApexVerity of compressed APEX";
+ }
+
+ ApexVerityData verity_data;
unique_fd fd(open(GetPath().c_str(), O_RDONLY | O_CLOEXEC));
if (fd.get() == -1) {
return ErrnoError() << "Failed to open " << GetPath();
}
- Result<std::unique_ptr<AvbFooter>> footer = getAvbFooter(*this, fd);
+ Result<std::unique_ptr<AvbFooter>> footer = GetAvbFooter(*this, fd);
if (!footer.ok()) {
return footer.error();
}
Result<std::unique_ptr<uint8_t[]>> vbmeta_data =
- verifyVbMeta(*this, fd, **footer);
+ VerifyVbMeta(*this, fd, **footer, public_key);
if (!vbmeta_data.ok()) {
return vbmeta_data.error();
}
Result<const AvbHashtreeDescriptor*> descriptor =
- findDescriptor(vbmeta_data->get(), (*footer)->vbmeta_size);
+ FindDescriptor(vbmeta_data->get(), (*footer)->vbmeta_size);
if (!descriptor.ok()) {
return descriptor.error();
}
- Result<std::unique_ptr<AvbHashtreeDescriptor>> verifiedDescriptor =
- verifyDescriptor(*descriptor);
- if (!verifiedDescriptor.ok()) {
- return verifiedDescriptor.error();
+ Result<std::unique_ptr<AvbHashtreeDescriptor>> verified_descriptor =
+ VerifyDescriptor(*descriptor);
+ if (!verified_descriptor.ok()) {
+ return verified_descriptor.error();
}
- verityData.desc = std::move(*verifiedDescriptor);
+ verity_data.desc = std::move(*verified_descriptor);
// This area is now safe to access, because we just verified it
- const uint8_t* trailingData =
+ const uint8_t* trailing_data =
(const uint8_t*)*descriptor + sizeof(AvbHashtreeDescriptor);
- verityData.hash_algorithm =
+ verity_data.hash_algorithm =
reinterpret_cast<const char*>((*descriptor)->hash_algorithm);
- verityData.salt = getSalt(*verityData.desc, trailingData);
- verityData.root_digest = getDigest(*verityData.desc, trailingData);
+ verity_data.salt = GetSalt(*verity_data.desc, trailing_data);
+ verity_data.root_digest = GetDigest(*verity_data.desc, trailing_data);
- return verityData;
+ return verity_data;
}
-Result<void> ApexFile::VerifyManifestMatches(
- const std::string& mount_path) const {
- Result<ApexManifest> verifiedManifest =
- ReadManifest(mount_path + "/" + kManifestFilenamePb);
- if (!verifiedManifest.ok()) {
- return verifiedManifest.error();
- }
+Result<void> ApexFile::Decompress(const std::string& dest_path) const {
+ const std::string& src_path = GetPath();
+
+ LOG(INFO) << "Decompressing" << src_path << " to " << dest_path;
- if (!MessageDifferencer::Equals(manifest_, *verifiedManifest)) {
- return Errorf(
- "Manifest inside filesystem does not match manifest outside it");
+ // We should decompress compressed APEX files only
+ if (!IsCompressed()) {
+ return ErrnoError() << "Cannot decompress an uncompressed APEX";
}
- return {};
-}
+ // Get file descriptor of the compressed apex file
+ unique_fd src_fd(open(src_path.c_str(), O_RDONLY | O_CLOEXEC));
+ if (src_fd.get() == -1) {
+ return ErrnoError() << "Failed to open compressed APEX " << GetPath();
+ }
-Result<std::vector<std::string>> FindApexes(
- const std::vector<std::string>& paths) {
- std::vector<std::string> result;
- for (const auto& path : paths) {
- auto exist = PathExists(path);
- if (!exist.ok()) {
- return exist.error();
- }
- if (!*exist) continue;
+ // Open it as a zip file
+ ZipArchiveHandle handle;
+ int ret = OpenArchiveFd(src_fd.get(), src_path.c_str(), &handle, false);
+ if (ret < 0) {
+ return Error() << "Failed to open package " << src_path << ": "
+ << ErrorCodeString(ret);
+ }
+ auto handle_guard =
+ android::base::make_scope_guard([&handle] { CloseArchive(handle); });
- const auto& apexes = FindApexFilesByName(path);
- if (!apexes.ok()) {
- return apexes;
- }
+ // Find the original apex file inside the zip and extract to dest
+ ZipEntry entry;
+ ret = FindEntry(handle, kCompressedApexFilename, &entry);
+ if (ret < 0) {
+ return Error() << "Could not find entry \"" << kCompressedApexFilename
+ << "\" in package " << src_path << ": "
+ << ErrorCodeString(ret);
+ }
- result.insert(result.end(), apexes->begin(), apexes->end());
+ // Open destination file descriptor
+ unique_fd dest_fd(
+ open(dest_path.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0644));
+ if (dest_fd.get() == -1) {
+ return ErrnoError() << "Failed to open decompression destination "
+ << dest_path.c_str();
}
- return result;
-}
-Result<std::vector<std::string>> FindApexFilesByName(const std::string& path) {
- auto filter_fn = [](const std::filesystem::directory_entry& entry) {
- std::error_code ec;
- if (entry.is_regular_file(ec) &&
- EndsWith(entry.path().filename().string(), kApexPackageSuffix)) {
- return true; // APEX file, take.
- }
- return false;
- };
- return ReadDir(path, filter_fn);
-}
+ // Prepare a guard that deletes the extracted file if anything goes wrong
+ auto decompressed_guard = android::base::make_scope_guard(
+ [&dest_path] { RemoveFileIfExists(dest_path); });
-bool isPathForBuiltinApexes(const std::string& path) {
- for (const auto& dir : kApexPackageBuiltinDirs) {
- if (StartsWith(path, dir)) {
- return true;
- }
+ // Extract the original_apex to dest_path
+ ret = ExtractEntryToFile(handle, &entry, dest_fd.get());
+ if (ret < 0) {
+ return Error() << "Could not decompress to file " << dest_path << " "
+ << ErrorCodeString(ret);
}
- return false;
+
+ // Verification complete. Accept the decompressed file
+ decompressed_guard.Disable();
+ LOG(VERBOSE) << "Decompressed " << src_path << " to " << dest_path;
+
+ return {};
}
} // namespace apex
diff --git a/apexd/apex_file.h b/apexd/apex_file.h
index 0476911c..60d5feb2 100644
--- a/apexd/apex_file.h
+++ b/apexd/apex_file.h
@@ -23,9 +23,7 @@
#include <android-base/result.h>
#include <libavb/libavb.h>
-#include <ziparchive/zip_archive.h>
-#include "apex_constants.h"
#include "apex_manifest.h"
namespace android {
@@ -46,43 +44,42 @@ class ApexFile {
static android::base::Result<ApexFile> Open(const std::string& path);
ApexFile() = delete;
ApexFile(ApexFile&&) = default;
+ ApexFile& operator=(ApexFile&&) = default;
const std::string& GetPath() const { return apex_path_; }
- int32_t GetImageOffset() const { return image_offset_; }
- size_t GetImageSize() const { return image_size_; }
- const ApexManifest& GetManifest() const { return manifest_; }
+ const std::optional<int32_t>& GetImageOffset() const { return image_offset_; }
+ const std::optional<size_t>& GetImageSize() const { return image_size_; }
+ const ::apex::proto::ApexManifest& GetManifest() const { return manifest_; }
const std::string& GetBundledPublicKey() const { return apex_pubkey_; }
- bool IsBuiltin() const { return is_builtin_; }
- android::base::Result<ApexVerityData> VerifyApexVerity() const;
- android::base::Result<void> VerifyManifestMatches(
- const std::string& mount_path) const;
+ const std::optional<std::string>& GetFsType() const { return fs_type_; }
+ android::base::Result<ApexVerityData> VerifyApexVerity(
+ const std::string& public_key) const;
+ bool IsCompressed() const { return is_compressed_; }
+ android::base::Result<void> Decompress(const std::string& output_path) const;
private:
- ApexFile(const std::string& apex_path, int32_t image_offset,
- size_t image_size, ApexManifest manifest,
- const std::string& apex_pubkey, bool is_builtin)
+ ApexFile(const std::string& apex_path,
+ const std::optional<int32_t>& image_offset,
+ const std::optional<size_t>& image_size,
+ ::apex::proto::ApexManifest manifest, const std::string& apex_pubkey,
+ const std::optional<std::string>& fs_type, bool is_compressed)
: apex_path_(apex_path),
image_offset_(image_offset),
image_size_(image_size),
manifest_(std::move(manifest)),
apex_pubkey_(apex_pubkey),
- is_builtin_(is_builtin) {}
+ fs_type_(fs_type),
+ is_compressed_(is_compressed) {}
std::string apex_path_;
- int32_t image_offset_;
- size_t image_size_;
- ApexManifest manifest_;
+ std::optional<int32_t> image_offset_;
+ std::optional<size_t> image_size_;
+ ::apex::proto::ApexManifest manifest_;
std::string apex_pubkey_;
- bool is_builtin_;
+ std::optional<std::string> fs_type_;
+ bool is_compressed_;
};
-android::base::Result<std::vector<std::string>> FindApexes(
- const std::vector<std::string>& paths);
-android::base::Result<std::vector<std::string>> FindApexFilesByName(
- const std::string& path);
-
-bool isPathForBuiltinApexes(const std::string& path);
-
} // namespace apex
} // namespace android
diff --git a/apexd/apex_file_repository.cpp b/apexd/apex_file_repository.cpp
new file mode 100644
index 00000000..c50d136f
--- /dev/null
+++ b/apexd/apex_file_repository.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#define LOG_TAG "apexd"
+
+#include "apex_file_repository.h"
+
+#include <unordered_map>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "apex_constants.h"
+#include "apex_file.h"
+#include "apexd_utils.h"
+
+using android::base::EndsWith;
+using android::base::Error;
+using android::base::GetProperty;
+using android::base::Result;
+
+namespace android {
+namespace apex {
+
+Result<void> ApexFileRepository::ScanBuiltInDir(const std::string& dir) {
+ LOG(INFO) << "Scanning " << dir << " for pre-installed ApexFiles";
+ if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) {
+ LOG(WARNING) << dir << " does not exist. Skipping";
+ return {};
+ }
+
+ Result<std::vector<std::string>> all_apex_files = FindFilesBySuffix(
+ dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
+ if (!all_apex_files.ok()) {
+ return all_apex_files.error();
+ }
+
+ // TODO(b/179248390): scan parallelly if possible
+ for (const auto& file : *all_apex_files) {
+ LOG(INFO) << "Found pre-installed APEX " << file;
+ Result<ApexFile> apex_file = ApexFile::Open(file);
+ if (!apex_file.ok()) {
+ return Error() << "Failed to open " << file << " : " << apex_file.error();
+ }
+
+ const std::string& name = apex_file->GetManifest().name();
+ auto it = pre_installed_store_.find(name);
+ if (it == pre_installed_store_.end()) {
+ pre_installed_store_.emplace(name, std::move(*apex_file));
+ } else if (it->second.GetPath() != apex_file->GetPath()) {
+ auto level = base::FATAL;
+ // On some development (non-REL) builds the VNDK apex could be in /vendor.
+ // When testing CTS-on-GSI on these builds, there would be two VNDK apexes
+ // in the system, one in /system and one in /vendor.
+ static constexpr char kVndkApexModuleNamePrefix[] = "com.android.vndk.";
+ static constexpr char kPlatformVersionCodenameProperty[] =
+ "ro.build.version.codename";
+ if (android::base::StartsWith(name, kVndkApexModuleNamePrefix) &&
+ GetProperty(kPlatformVersionCodenameProperty, "REL") != "REL") {
+ level = android::base::INFO;
+ }
+ LOG(level) << "Found two apex packages " << it->second.GetPath()
+ << " and " << apex_file->GetPath()
+ << " with the same module name " << name;
+ } else if (it->second.GetBundledPublicKey() !=
+ apex_file->GetBundledPublicKey()) {
+ LOG(FATAL) << "Public key of apex package " << it->second.GetPath()
+ << " (" << name << ") has unexpectedly changed";
+ }
+ }
+ return {};
+}
+
+ApexFileRepository& ApexFileRepository::GetInstance() {
+ static ApexFileRepository instance;
+ return instance;
+}
+
+android::base::Result<void> ApexFileRepository::AddPreInstalledApex(
+ const std::vector<std::string>& prebuilt_dirs) {
+ for (const auto& dir : prebuilt_dirs) {
+ if (auto result = ScanBuiltInDir(dir); !result.ok()) {
+ return result.error();
+ }
+ }
+ return {};
+}
+
+// TODO(b/179497746): AddDataApex should not concern with filtering out invalid
+// apex.
+Result<void> ApexFileRepository::AddDataApex(const std::string& data_dir) {
+ LOG(INFO) << "Scanning " << data_dir << " for data ApexFiles";
+ if (access(data_dir.c_str(), F_OK) != 0 && errno == ENOENT) {
+ LOG(WARNING) << data_dir << " does not exist. Skipping";
+ return {};
+ }
+
+ Result<std::vector<std::string>> active_apex =
+ FindFilesBySuffix(data_dir, {kApexPackageSuffix});
+ if (!active_apex.ok()) {
+ return active_apex.error();
+ }
+
+ // TODO(b/179248390): scan parallelly if possible
+ for (const auto& file : *active_apex) {
+ LOG(INFO) << "Found updated apex " << file;
+ Result<ApexFile> apex_file = ApexFile::Open(file);
+ if (!apex_file.ok()) {
+ LOG(ERROR) << "Failed to open " << file << " : " << apex_file.error();
+ continue;
+ }
+
+ const std::string& name = apex_file->GetManifest().name();
+ if (!HasPreInstalledVersion(name)) {
+ LOG(ERROR) << "Skipping " << file << " : no preisntalled apex";
+ // Ignore data apex without corresponding pre-installed apex
+ continue;
+ }
+ auto pre_installed_public_key = GetPublicKey(name);
+ if (!pre_installed_public_key.ok() ||
+ apex_file->GetBundledPublicKey() != *pre_installed_public_key) {
+ // Ignore data apex if public key doesn't match with pre-installed apex
+ LOG(ERROR) << "Skipping " << file
+ << " : public key doesn't match pre-installed one";
+ continue;
+ }
+
+ if (EndsWith(apex_file->GetPath(), kDecompressedApexPackageSuffix)) {
+ LOG(WARNING) << "Skipping " << file
+ << " : Non-decompressed APEX should not have "
+ << kDecompressedApexPackageSuffix << " suffix";
+ continue;
+ }
+
+ auto it = data_store_.find(name);
+ if (it == data_store_.end()) {
+ data_store_.emplace(name, std::move(*apex_file));
+ continue;
+ }
+
+ const auto& existing_version = it->second.GetManifest().version();
+ const auto new_version = apex_file->GetManifest().version();
+ // If multiple data apexs are preset, select the one with highest version
+ bool prioritize_higher_version = new_version > existing_version;
+ // For same version, non-decompressed apex gets priority
+ if (prioritize_higher_version) {
+ it->second = std::move(*apex_file);
+ }
+ }
+ return {};
+}
+
+// TODO(b/179497746): remove this method when we add api for fetching ApexFile
+// by name
+Result<const std::string> ApexFileRepository::GetPublicKey(
+ const std::string& name) const {
+ auto it = pre_installed_store_.find(name);
+ if (it == pre_installed_store_.end()) {
+ return Error() << "No preinstalled apex found for package " << name;
+ }
+ return it->second.GetBundledPublicKey();
+}
+
+// TODO(b/179497746): remove this method when we add api for fetching ApexFile
+// by name
+Result<const std::string> ApexFileRepository::GetPreinstalledPath(
+ const std::string& name) const {
+ auto it = pre_installed_store_.find(name);
+ if (it == pre_installed_store_.end()) {
+ return Error() << "No preinstalled data found for package " << name;
+ }
+ return it->second.GetPath();
+}
+
+// TODO(b/179497746): remove this method when we add api for fetching ApexFile
+// by name
+Result<const std::string> ApexFileRepository::GetDataPath(
+ const std::string& name) const {
+ auto it = data_store_.find(name);
+ if (it == data_store_.end()) {
+ return Error() << "No data apex found for package " << name;
+ }
+ return it->second.GetPath();
+}
+
+bool ApexFileRepository::HasPreInstalledVersion(const std::string& name) const {
+ return pre_installed_store_.find(name) != pre_installed_store_.end();
+}
+
+bool ApexFileRepository::HasDataVersion(const std::string& name) const {
+ return data_store_.find(name) != data_store_.end();
+}
+
+// ApexFile is considered a decompressed APEX if it is located in decompression
+// dir
+bool ApexFileRepository::IsDecompressedApex(const ApexFile& apex) const {
+ return apex.GetPath().starts_with(decompression_dir_);
+}
+
+bool ApexFileRepository::IsPreInstalledApex(const ApexFile& apex) const {
+ auto it = pre_installed_store_.find(apex.GetManifest().name());
+ if (it == pre_installed_store_.end()) {
+ return false;
+ }
+ return it->second.GetPath() == apex.GetPath() || IsDecompressedApex(apex);
+}
+
+std::vector<ApexFileRef> ApexFileRepository::GetPreInstalledApexFiles() const {
+ std::vector<ApexFileRef> result;
+ for (const auto& it : pre_installed_store_) {
+ result.emplace_back(std::cref(it.second));
+ }
+ return std::move(result);
+}
+
+std::vector<ApexFileRef> ApexFileRepository::GetDataApexFiles() const {
+ std::vector<ApexFileRef> result;
+ for (const auto& it : data_store_) {
+ result.emplace_back(std::cref(it.second));
+ }
+ return std::move(result);
+}
+
+// Group pre-installed APEX and data APEX by name
+std::unordered_map<std::string, std::vector<ApexFileRef>>
+ApexFileRepository::AllApexFilesByName() const {
+ // Collect all apex files
+ std::vector<ApexFileRef> all_apex_files;
+ auto pre_installed_apexs = GetPreInstalledApexFiles();
+ auto data_apexs = GetDataApexFiles();
+ std::move(pre_installed_apexs.begin(), pre_installed_apexs.end(),
+ std::back_inserter(all_apex_files));
+ std::move(data_apexs.begin(), data_apexs.end(),
+ std::back_inserter(all_apex_files));
+
+ // Group them by name
+ std::unordered_map<std::string, std::vector<ApexFileRef>> result;
+ for (const auto& apex_file_ref : all_apex_files) {
+ const ApexFile& apex_file = apex_file_ref.get();
+ const std::string& package_name = apex_file.GetManifest().name();
+ if (result.find(package_name) == result.end()) {
+ result[package_name] = std::vector<ApexFileRef>{};
+ }
+ result[package_name].emplace_back(apex_file_ref);
+ }
+
+ return std::move(result);
+}
+
+ApexFileRef ApexFileRepository::GetDataApex(const std::string& name) const {
+ auto it = data_store_.find(name);
+ CHECK(it != data_store_.end());
+ return std::cref(it->second);
+}
+
+ApexFileRef ApexFileRepository::GetPreInstalledApex(
+ const std::string& name) const {
+ auto it = pre_installed_store_.find(name);
+ CHECK(it != pre_installed_store_.end());
+ return std::cref(it->second);
+}
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apex_file_repository.h b/apexd/apex_file_repository.h
new file mode 100644
index 00000000..e9ccf7ee
--- /dev/null
+++ b/apexd/apex_file_repository.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "apex_constants.h"
+#include "apex_file.h"
+
+#include <android-base/result.h>
+
+namespace android {
+namespace apex {
+
+using ApexFileRef = std::reference_wrapper<const android::apex::ApexFile>;
+
+// This class serves as a ApexFile repository for all apexes on device. It also
+// provides information about the ApexFiles it hosts, such as which are
+// pre-installed and which are data. Such information can be used, for example,
+// to verify validity of an apex before trying to mount it.
+//
+// It's expected to have a single instance of this class in a process that
+// mounts apexes (e.g. apexd, otapreopt_chroot).
+class ApexFileRepository final {
+ public:
+ // c-tor and d-tor are exposed for testing.
+ explicit ApexFileRepository(
+ const std::string& decompression_dir = kApexDecompressedDir)
+ : decompression_dir_(decompression_dir){};
+
+ ~ApexFileRepository() {
+ pre_installed_store_.clear();
+ data_store_.clear();
+ };
+
+ // Returns a singletone instance of this class.
+ static ApexFileRepository& GetInstance();
+
+ // Populate instance by collecting pre-installed apex files from the given
+ // |prebuilt_dirs|.
+ // Note: this call is **not thread safe** and is expected to be performed in a
+ // single thread during initialization of apexd. After initialization is
+ // finished, all queries to the instance are thread safe.
+ android::base::Result<void> AddPreInstalledApex(
+ const std::vector<std::string>& prebuilt_dirs);
+
+ // Populate instance by collecting data apex files from the given |data_dir|.
+ // Note: this call is **not thread safe** and is expected to be performed in a
+ // single thread during initialization of apexd. After initialization is
+ // finished, all queries to the instance are thread safe.
+ android::base::Result<void> AddDataApex(const std::string& data_dir);
+
+ // Returns trusted public key for an apex with the given |name|.
+ android::base::Result<const std::string> GetPublicKey(
+ const std::string& name) const;
+
+ // Returns path to the pre-installed version of an apex with the given |name|.
+ android::base::Result<const std::string> GetPreinstalledPath(
+ const std::string& name) const;
+
+ // Returns path to the data version of an apex with the given |name|.
+ android::base::Result<const std::string> GetDataPath(
+ const std::string& name) const;
+
+ // Checks whether there is a pre-installed version of an apex with the given
+ // |name|.
+ bool HasPreInstalledVersion(const std::string& name) const;
+
+ // Checks whether there is a data version of an apex with the given |name|.
+ bool HasDataVersion(const std::string& name) const;
+
+ // Checks if given |apex| is pre-installed.
+ bool IsPreInstalledApex(const ApexFile& apex) const;
+
+ // Checks if given |apex| is decompressed from a pre-installed APEX
+ bool IsDecompressedApex(const ApexFile& apex) const;
+
+ // Returns reference to all pre-installed APEX on device
+ std::vector<ApexFileRef> GetPreInstalledApexFiles() const;
+
+ // Returns reference to all data APEX on device
+ std::vector<ApexFileRef> GetDataApexFiles() const;
+
+ // Group all ApexFiles on device by their package name
+ std::unordered_map<std::string, std::vector<ApexFileRef>> AllApexFilesByName()
+ const;
+
+ // Returns a pre-installed version of apex with the given name. Caller is
+ // expected to check if there is a pre-installed apex with the given name
+ // using |HasPreinstalledVersion| function.
+ ApexFileRef GetPreInstalledApex(const std::string& name) const;
+ // Returns a data version of apex with the given name. Caller is
+ // expected to check if there is a data apex with the given name
+ // using |HasDataVersion| function.
+ ApexFileRef GetDataApex(const std::string& name) const;
+
+ // Clears ApexFileRepostiry.
+ // Only use in tests.
+ void Reset(const std::string& decompression_dir = kApexDecompressedDir) {
+ pre_installed_store_.clear();
+ data_store_.clear();
+ decompression_dir_ = decompression_dir;
+ }
+
+ private:
+ // Non-copyable && non-moveable.
+ ApexFileRepository(const ApexFileRepository&) = delete;
+ ApexFileRepository& operator=(const ApexFileRepository&) = delete;
+ ApexFileRepository& operator=(ApexFileRepository&&) = delete;
+ ApexFileRepository(ApexFileRepository&&) = delete;
+
+ // Scans apexes in the given directory and adds collected data into
+ // |pre_installed_store_|.
+ android::base::Result<void> ScanBuiltInDir(const std::string& dir);
+
+ std::unordered_map<std::string, ApexFile> pre_installed_store_, data_store_;
+ // Decompression directory which will be used to determine if apex is
+ // decompressed or not
+ std::string decompression_dir_;
+};
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apex_file_repository_test.cpp b/apexd/apex_file_repository_test.cpp
new file mode 100644
index 00000000..c1f5d533
--- /dev/null
+++ b/apexd/apex_file_repository_test.cpp
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2020 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 <filesystem>
+#include <string>
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "apex_file.h"
+#include "apex_file_repository.h"
+#include "apexd_test_utils.h"
+#include "apexd_verity.h"
+
+namespace android {
+namespace apex {
+
+using namespace std::literals;
+
+namespace fs = std::filesystem;
+
+using android::apex::testing::ApexFileEq;
+using android::apex::testing::IsOk;
+using android::base::GetExecutableDirectory;
+using android::base::StringPrintf;
+using ::testing::ByRef;
+using ::testing::UnorderedElementsAre;
+
+static std::string GetTestDataDir() { return GetExecutableDirectory(); }
+static std::string GetTestFile(const std::string& name) {
+ return GetTestDataDir() + "/" + name;
+}
+
+namespace {
+// Copies the compressed apex to |built_in_dir| and decompresses it to
+// |decompression_dir
+void PrepareCompressedApex(const std::string& name,
+ const std::string& built_in_dir,
+ const std::string& decompression_dir) {
+ fs::copy(GetTestFile(name), built_in_dir);
+ auto compressed_apex =
+ ApexFile::Open(StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str()));
+
+ const auto& pkg_name = compressed_apex->GetManifest().name();
+ const int version = compressed_apex->GetManifest().version();
+
+ auto decompression_path =
+ StringPrintf("%s/%s@%d%s", decompression_dir.c_str(), pkg_name.c_str(),
+ version, kDecompressedApexPackageSuffix);
+ compressed_apex->Decompress(decompression_path);
+}
+} // namespace
+
+TEST(ApexFileRepositoryTest, InitializeSuccess) {
+ // Prepare test data.
+ TemporaryDir built_in_dir, data_dir, decompression_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test_different_app.apex"),
+ built_in_dir.path);
+
+ fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), data_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ // Now test that apexes were scanned correctly;
+ auto test_fn = [&](const std::string& apex_name) {
+ auto apex = ApexFile::Open(GetTestFile(apex_name));
+ ASSERT_TRUE(IsOk(apex));
+
+ {
+ auto ret = instance.GetPublicKey(apex->GetManifest().name());
+ ASSERT_TRUE(IsOk(ret));
+ ASSERT_EQ(apex->GetBundledPublicKey(), *ret);
+ }
+
+ {
+ auto ret = instance.GetPreinstalledPath(apex->GetManifest().name());
+ ASSERT_TRUE(IsOk(ret));
+ ASSERT_EQ(StringPrintf("%s/%s", built_in_dir.path, apex_name.c_str()),
+ *ret);
+ }
+
+ {
+ auto ret = instance.GetDataPath(apex->GetManifest().name());
+ ASSERT_TRUE(IsOk(ret));
+ ASSERT_EQ(StringPrintf("%s/%s", data_dir.path, apex_name.c_str()), *ret);
+ }
+
+ ASSERT_TRUE(instance.HasPreInstalledVersion(apex->GetManifest().name()));
+ ASSERT_TRUE(instance.HasDataVersion(apex->GetManifest().name()));
+ };
+
+ test_fn("apex.apexd_test.apex");
+ test_fn("apex.apexd_test_different_app.apex");
+
+ // Check that second call will succeed as well.
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ test_fn("apex.apexd_test.apex");
+ test_fn("apex.apexd_test_different_app.apex");
+}
+
+TEST(ApexFileRepositoryTest, InitializeFailureCorruptApex) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("apex.apexd_test_corrupt_superblock_apex.apex"),
+ td.path);
+
+ ApexFileRepository instance;
+ ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path})));
+}
+
+TEST(ApexFileRepositoryTest, InitializeCompressedApexWithoutApex) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("com.android.apex.compressed.v1_without_apex.capex"),
+ td.path);
+
+ ApexFileRepository instance;
+ // Compressed APEX without APEX cannot be opened
+ ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path})));
+}
+
+TEST(ApexFileRepositoryTest, InitializeSameNameDifferentPathAborts) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("apex.apexd_test.apex"),
+ StringPrintf("%s/other.apex", td.path));
+
+ ASSERT_DEATH(
+ {
+ ApexFileRepository instance;
+ instance.AddPreInstalledApex({td.path});
+ },
+ "");
+}
+
+TEST(ApexFileRepositoryTest,
+ InitializeSameNameDifferentPathAbortsCompressedApex) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
+ StringPrintf("%s/other.capex", td.path));
+
+ ASSERT_DEATH(
+ {
+ ApexFileRepository instance;
+ instance.AddPreInstalledApex({td.path});
+ },
+ "");
+}
+
+TEST(ApexFileRepositoryTest, InitializePublicKeyUnexpectdlyChangedAborts) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
+
+ // Check that apex was loaded.
+ auto path = instance.GetPreinstalledPath("com.android.apex.test_package");
+ ASSERT_TRUE(IsOk(path));
+ ASSERT_EQ(StringPrintf("%s/apex.apexd_test.apex", td.path), *path);
+
+ auto public_key = instance.GetPublicKey("com.android.apex.test_package");
+ ASSERT_TRUE(IsOk(public_key));
+
+ // Substitute it with another apex with the same name, but different public
+ // key.
+ fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), *path,
+ fs::copy_options::overwrite_existing);
+
+ {
+ auto apex = ApexFile::Open(*path);
+ ASSERT_TRUE(IsOk(apex));
+ // Check module name hasn't changed.
+ ASSERT_EQ("com.android.apex.test_package", apex->GetManifest().name());
+ // Check public key has changed.
+ ASSERT_NE(*public_key, apex->GetBundledPublicKey());
+ }
+
+ ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, "");
+}
+
+TEST(ApexFileRepositoryTest,
+ InitializePublicKeyUnexpectdlyChangedAbortsCompressedApex) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
+
+ // Check that apex was loaded.
+ auto path = instance.GetPreinstalledPath("com.android.apex.compressed");
+ ASSERT_TRUE(IsOk(path));
+ ASSERT_EQ(StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path),
+ *path);
+
+ auto public_key = instance.GetPublicKey("com.android.apex.compressed");
+ ASSERT_TRUE(IsOk(public_key));
+
+ // Substitute it with another apex with the same name, but different public
+ // key.
+ fs::copy(GetTestFile("com.android.apex.compressed_different_key.capex"),
+ *path, fs::copy_options::overwrite_existing);
+
+ {
+ auto apex = ApexFile::Open(*path);
+ ASSERT_TRUE(IsOk(apex));
+ // Check module name hasn't changed.
+ ASSERT_EQ("com.android.apex.compressed", apex->GetManifest().name());
+ // Check public key has changed.
+ ASSERT_NE(*public_key, apex->GetBundledPublicKey());
+ }
+
+ ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, "");
+}
+
+TEST(ApexFileRepositoryTest, IsPreInstalledApex) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
+
+ auto compressed_apex = ApexFile::Open(
+ StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path));
+ ASSERT_TRUE(IsOk(compressed_apex));
+ ASSERT_TRUE(instance.IsPreInstalledApex(*compressed_apex));
+
+ auto apex1 = ApexFile::Open(StringPrintf("%s/apex.apexd_test.apex", td.path));
+ ASSERT_TRUE(IsOk(apex1));
+ ASSERT_TRUE(instance.IsPreInstalledApex(*apex1));
+
+ // It's same apex, but path is different. Shouldn't be treated as
+ // pre-installed.
+ auto apex2 = ApexFile::Open(GetTestFile("apex.apexd_test.apex"));
+ ASSERT_TRUE(IsOk(apex2));
+ ASSERT_FALSE(instance.IsPreInstalledApex(*apex2));
+
+ auto apex3 =
+ ApexFile::Open(GetTestFile("apex.apexd_test_different_app.apex"));
+ ASSERT_TRUE(IsOk(apex3));
+ ASSERT_FALSE(instance.IsPreInstalledApex(*apex3));
+}
+
+TEST(ApexFileRepositoryTest, IsDecompressedApex) {
+ // Prepare instance
+ TemporaryDir decompression_dir;
+ ApexFileRepository instance(decompression_dir.path);
+
+ // Prepare decompressed apex
+ std::string filename = "com.android.apex.compressed.v1_original.apex";
+ fs::copy(GetTestFile(filename), decompression_dir.path);
+ auto decompressed_path =
+ StringPrintf("%s/%s", decompression_dir.path, filename.c_str());
+ auto decompressed_apex = ApexFile::Open(decompressed_path);
+
+ // Any file which is already located in |decompression_dir| should be
+ // considered decompressed
+ ASSERT_TRUE(instance.IsDecompressedApex(*decompressed_apex));
+
+ // Hard links with same file name is not considered decompressed
+ TemporaryDir active_dir;
+ auto active_path = StringPrintf("%s/%s", active_dir.path, filename.c_str());
+ std::error_code ec;
+ fs::create_hard_link(decompressed_path, active_path, ec);
+ ASSERT_FALSE(ec) << "Failed to create hardlink";
+ auto active_apex = ApexFile::Open(active_path);
+ ASSERT_FALSE(instance.IsDecompressedApex(*active_apex));
+}
+
+TEST(ApexFileRepositoryTest, AddAndGetDataApex) {
+ // Prepare test data.
+ TemporaryDir built_in_dir, data_dir, decompression_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex",
+ built_in_dir.path, decompression_dir.path);
+ // Add a data apex that has kDecompressedApexPackageSuffix
+ fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
+ StringPrintf("%s/com.android.apex.compressed@1%s", data_dir.path,
+ kDecompressedApexPackageSuffix));
+
+ ApexFileRepository instance(decompression_dir.path);
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ // ApexFileRepository should only deal with APEX in /data/apex/active.
+ // Decompressed APEX should not be included
+ auto data_apexs = instance.GetDataApexFiles();
+ auto normal_apex =
+ ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
+ ASSERT_THAT(data_apexs,
+ UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex))));
+}
+
+TEST(ApexFileRepositoryTest, AddDataApexIgnoreCompressedApex) {
+ // Prepare test data.
+ TemporaryDir data_dir, decompression_dir;
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), data_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto data_apexs = instance.GetDataApexFiles();
+ ASSERT_EQ(data_apexs.size(), 0u);
+}
+
+TEST(ApexFileRepositoryTest, AddDataApexIgnoreIfNotPreInstalled) {
+ // Prepare test data.
+ TemporaryDir data_dir, decompression_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto data_apexs = instance.GetDataApexFiles();
+ ASSERT_EQ(data_apexs.size(), 0u);
+}
+
+TEST(ApexFileRepositoryTest, AddDataApexPrioritizeHigherVersionApex) {
+ // Prepare test data.
+ TemporaryDir built_in_dir, data_dir, decompression_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto data_apexs = instance.GetDataApexFiles();
+ auto normal_apex =
+ ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
+ ASSERT_THAT(data_apexs,
+ UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex))));
+}
+
+TEST(ApexFileRepositoryTest, AddDataApexDoesNotScanDecompressedApex) {
+ // Prepare test data.
+ TemporaryDir built_in_dir, data_dir, decompression_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex",
+ built_in_dir.path, decompression_dir.path);
+
+ ApexFileRepository instance(decompression_dir.path);
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto data_apexs = instance.GetDataApexFiles();
+ ASSERT_EQ(data_apexs.size(), 0u);
+}
+
+TEST(ApexFileRepositoryTest, AddDataApexIgnoreWrongPublicKey) {
+ // Prepare test data.
+ TemporaryDir built_in_dir, data_dir, decompression_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), data_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto data_apexs = instance.GetDataApexFiles();
+ ASSERT_EQ(data_apexs.size(), 0u);
+}
+
+TEST(ApexFileRepositoryTest, GetPreInstalledApexFiles) {
+ // Prepare test data.
+ TemporaryDir built_in_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
+ built_in_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+
+ auto pre_installed_apexs = instance.GetPreInstalledApexFiles();
+ auto pre_apex_1 = ApexFile::Open(
+ StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
+ auto pre_apex_2 = ApexFile::Open(StringPrintf(
+ "%s/com.android.apex.compressed.v1.capex", built_in_dir.path));
+ ASSERT_THAT(pre_installed_apexs,
+ UnorderedElementsAre(ApexFileEq(ByRef(*pre_apex_1)),
+ ApexFileEq(ByRef(*pre_apex_2))));
+}
+
+TEST(ApexFileRepositoryTest, AllApexFilesByName) {
+ TemporaryDir built_in_dir, decompression_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("com.android.apex.cts.shim.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
+ built_in_dir.path);
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+
+ TemporaryDir data_dir;
+ fs::copy(GetTestFile("com.android.apex.cts.shim.v2.apex"), data_dir.path);
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto result = instance.AllApexFilesByName();
+
+ // Verify the contents of result
+ auto apexd_test_file = ApexFile::Open(
+ StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
+ auto shim_v1 = ApexFile::Open(
+ StringPrintf("%s/com.android.apex.cts.shim.apex", built_in_dir.path));
+ auto compressed_apex = ApexFile::Open(StringPrintf(
+ "%s/com.android.apex.compressed.v1.capex", built_in_dir.path));
+ auto shim_v2 = ApexFile::Open(
+ StringPrintf("%s/com.android.apex.cts.shim.v2.apex", data_dir.path));
+
+ ASSERT_EQ(result.size(), 3u);
+ ASSERT_THAT(result[apexd_test_file->GetManifest().name()],
+ UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file))));
+ ASSERT_THAT(result[shim_v1->GetManifest().name()],
+ UnorderedElementsAre(ApexFileEq(ByRef(*shim_v1)),
+ ApexFileEq(ByRef(*shim_v2))));
+ ASSERT_THAT(result[compressed_apex->GetManifest().name()],
+ UnorderedElementsAre(ApexFileEq(ByRef(*compressed_apex))));
+}
+
+TEST(ApexFileRepositoryTest, GetDataApex) {
+ // Prepare test data.
+ TemporaryDir built_in_dir, data_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+ fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
+
+ auto apex =
+ ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
+ ASSERT_RESULT_OK(apex);
+
+ auto ret = instance.GetDataApex("com.android.apex.test_package");
+ ASSERT_THAT(ret, ApexFileEq(ByRef(*apex)));
+}
+
+TEST(ApexFileRepositoryTest, GetDataApexNoSuchApexAborts) {
+ ASSERT_DEATH(
+ {
+ ApexFileRepository instance;
+ instance.GetDataApex("whatever");
+ },
+ "");
+}
+
+TEST(ApexFileRepositoryTest, GetPreInstalledApex) {
+ // Prepare test data.
+ TemporaryDir built_in_dir;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
+
+ ApexFileRepository instance;
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
+
+ auto apex = ApexFile::Open(
+ StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
+ ASSERT_RESULT_OK(apex);
+
+ auto ret = instance.GetPreInstalledApex("com.android.apex.test_package");
+ ASSERT_THAT(ret, ApexFileEq(ByRef(*apex)));
+}
+
+TEST(ApexFileRepositoryTest, GetPreInstalledApexNoSuchApexAborts) {
+ ASSERT_DEATH(
+ {
+ ApexFileRepository instance;
+ instance.GetPreInstalledApex("whatever");
+ },
+ "");
+}
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index 711d0246..3da74484 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -19,31 +19,47 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libavb/libavb.h>
#include <ziparchive/zip_archive.h>
#include "apex_file.h"
-#include "apex_preinstalled_data.h"
+#include "apexd_test_utils.h"
+#include "apexd_utils.h"
+using android::base::GetExecutableDirectory;
using android::base::Result;
-static std::string testDataDir = android::base::GetExecutableDirectory() + "/";
+static const std::string kTestDataDir = GetExecutableDirectory() + "/";
namespace android {
namespace apex {
namespace {
-TEST(ApexFileTest, GetOffsetOfSimplePackage) {
- const std::string filePath = testDataDir + "apex.apexd_test.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_TRUE(apexFile.ok());
+struct ApexFileTestParam {
+ const char* type;
+ const char* prefix;
+};
+
+constexpr const ApexFileTestParam kParameters[] = {
+ {"ext4", "apex.apexd_test"}, {"f2fs", "apex.apexd_test_f2fs"}};
+
+class ApexFileTest : public ::testing::TestWithParam<ApexFileTestParam> {};
+
+INSTANTIATE_TEST_SUITE_P(Apex, ApexFileTest, ::testing::ValuesIn(kParameters));
+
+TEST_P(ApexFileTest, GetOffsetOfSimplePackage) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_TRUE(apex_file.ok());
int32_t zip_image_offset;
size_t zip_image_size;
{
ZipArchiveHandle handle;
- int32_t rc = OpenArchive(filePath.c_str(), &handle);
+ int32_t rc = OpenArchive(file_path.c_str(), &handle);
ASSERT_EQ(0, rc);
auto close_guard =
android::base::make_scope_guard([&handle]() { CloseArchive(handle); });
@@ -58,34 +74,33 @@ TEST(ApexFileTest, GetOffsetOfSimplePackage) {
EXPECT_EQ(zip_image_size, entry.compressed_length);
}
- EXPECT_EQ(zip_image_offset, apexFile->GetImageOffset());
- EXPECT_EQ(zip_image_size, apexFile->GetImageSize());
+ EXPECT_EQ(zip_image_offset, apex_file->GetImageOffset().value());
+ EXPECT_EQ(zip_image_size, apex_file->GetImageSize().value());
}
TEST(ApexFileTest, GetOffsetMissingFile) {
- const std::string filePath = testDataDir + "missing.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_FALSE(apexFile.ok());
- EXPECT_NE(std::string::npos,
- apexFile.error().message().find("Failed to open package"))
- << apexFile.error();
+ const std::string file_path = kTestDataDir + "missing.apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_FALSE(apex_file.ok());
+ ASSERT_THAT(apex_file.error().message(),
+ ::testing::HasSubstr("Failed to open package"));
}
-TEST(ApexFileTest, GetApexManifest) {
- const std::string filePath = testDataDir + "apex.apexd_test.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_RESULT_OK(apexFile);
- EXPECT_EQ("com.android.apex.test_package", apexFile->GetManifest().name());
- EXPECT_EQ(1u, apexFile->GetManifest().version());
+TEST_P(ApexFileTest, GetApexManifest) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+ EXPECT_EQ("com.android.apex.test_package", apex_file->GetManifest().name());
+ EXPECT_EQ(1u, apex_file->GetManifest().version());
}
-TEST(ApexFileTest, VerifyApexVerity) {
- ASSERT_RESULT_OK(collectPreinstalledData({"/system_ext/apex"}));
- const std::string filePath = testDataDir + "apex.apexd_test.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_RESULT_OK(apexFile);
+TEST_P(ApexFileTest, VerifyApexVerity) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
- auto verity_or = apexFile->VerifyApexVerity();
+ auto verity_or =
+ apex_file->VerifyApexVerity(apex_file->GetBundledPublicKey());
ASSERT_RESULT_OK(verity_or);
const ApexVerityData& data = *verity_or;
@@ -93,52 +108,229 @@ TEST(ApexFileTest, VerifyApexVerity) {
EXPECT_EQ(std::string("368a22e64858647bc45498e92f749f85482ac468"
"50ca7ec8071f49dfa47a243c"),
data.salt);
- EXPECT_EQ(
- std::string(
- "8e841019e41e8c40bca6dd6304cbf163ea257ba0a268304832c4105eba1c2747"),
- data.root_digest);
-}
-// TODO: May consider packaging a debug key in debug builds (again).
-TEST(ApexFileTest, DISABLED_VerifyApexVerityNoKeyDir) {
- const std::string filePath = testDataDir + "apex.apexd_test.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_RESULT_OK(apexFile);
+ const std::string digest_path =
+ kTestDataDir + GetParam().prefix + "_digest.txt";
+ std::string root_digest;
+ ASSERT_TRUE(android::base::ReadFileToString(digest_path, &root_digest))
+ << "Failed to read " << digest_path;
+ root_digest = android::base::Trim(root_digest);
- auto verity_or = apexFile->VerifyApexVerity();
- ASSERT_FALSE(verity_or.ok());
+ EXPECT_EQ(std::string(root_digest), data.root_digest);
}
-TEST(ApexFileTest, VerifyApexVerityNoKeyInst) {
- const std::string filePath = testDataDir + "apex.apexd_test_no_inst_key.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_RESULT_OK(apexFile);
+TEST_P(ApexFileTest, VerifyApexVerityWrongKey) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
- auto verity_or = apexFile->VerifyApexVerity();
+ auto verity_or = apex_file->VerifyApexVerity("wrong-key");
ASSERT_FALSE(verity_or.ok());
}
-TEST(ApexFileTest, GetBundledPublicKey) {
- const std::string filePath = testDataDir + "apex.apexd_test.apex";
- Result<ApexFile> apexFile = ApexFile::Open(filePath);
- ASSERT_RESULT_OK(apexFile);
+TEST_P(ApexFileTest, GetBundledPublicKey) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
- const std::string keyPath =
- testDataDir + "apexd_testdata/com.android.apex.test_package.avbpubkey";
- std::string keyContent;
- ASSERT_TRUE(android::base::ReadFileToString(keyPath, &keyContent))
- << "Failed to read " << keyPath;
+ const std::string key_path =
+ kTestDataDir + "apexd_testdata/com.android.apex.test_package.avbpubkey";
+ std::string key_content;
+ ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content))
+ << "Failed to read " << key_path;
- EXPECT_EQ(keyContent, apexFile->GetBundledPublicKey());
+ EXPECT_EQ(key_content, apex_file->GetBundledPublicKey());
}
-TEST(ApexFileTest, CorrutedApex_b146895998) {
- const std::string apex_path = testDataDir + "corrupted_b146895998.apex";
+TEST(ApexFileTest, CorrutedApexB146895998) {
+ const std::string apex_path = kTestDataDir + "corrupted_b146895998.apex";
Result<ApexFile> apex = ApexFile::Open(apex_path);
ASSERT_RESULT_OK(apex);
- ASSERT_FALSE(apex->VerifyApexVerity());
+ ASSERT_FALSE(apex->VerifyApexVerity("ignored").ok());
+}
+
+TEST_P(ApexFileTest, RetrieveFsType) {
+ const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_TRUE(apex_file.ok());
+
+ EXPECT_EQ(std::string(GetParam().type), apex_file->GetFsType().value());
+}
+
+TEST(ApexFileTest, OpenInvalidFilesystem) {
+ const std::string file_path =
+ kTestDataDir + "apex.apexd_test_corrupt_superblock_apex.apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_FALSE(apex_file.ok());
+ ASSERT_THAT(apex_file.error().message(),
+ ::testing::HasSubstr("Failed to retrieve filesystem type"));
+}
+
+TEST(ApexFileTest, OpenCompressedApexFile) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_TRUE(apex_file.ok());
+
+ ASSERT_TRUE(apex_file->IsCompressed());
+ ASSERT_FALSE(apex_file->GetImageOffset().has_value());
+ ASSERT_FALSE(apex_file->GetImageSize().has_value());
+ ASSERT_FALSE(apex_file->GetFsType().has_value());
+}
+
+TEST(ApexFileTest, OpenFailureForCompressedApexWithoutApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1_without_apex.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_FALSE(apex_file.ok());
+ ASSERT_THAT(apex_file.error().message(),
+ ::testing::HasSubstr("Could not find entry"));
+}
+
+TEST(ApexFileTest, GetCompressedApexManifest) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+ EXPECT_EQ("com.android.apex.compressed", apex_file->GetManifest().name());
+ EXPECT_EQ(1u, apex_file->GetManifest().version());
+}
+
+TEST(ApexFileTest, GetBundledPublicKeyOfCompressedApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ const std::string key_path =
+ kTestDataDir + "apexd_testdata/com.android.apex.compressed.avbpubkey";
+ std::string key_content;
+ ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content))
+ << "Failed to read " << key_path;
+
+ EXPECT_EQ(key_content, apex_file->GetBundledPublicKey());
+}
+
+TEST(ApexFileTest, CannotVerifyApexVerityForCompressedApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ auto apex = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex);
+ auto result = apex->VerifyApexVerity(apex->GetBundledPublicKey());
+ ASSERT_FALSE(result.ok());
+ ASSERT_THAT(
+ result.error().message(),
+ ::testing::HasSubstr("Cannot verify ApexVerity of compressed APEX"));
}
+TEST(ApexFileTest, DecompressCompressedApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ // Create a temp dir for decompression
+ TemporaryDir tmp_dir;
+
+ const std::string package_name = apex_file->GetManifest().name();
+ const std::string decompression_file_path =
+ tmp_dir.path + package_name + ".capex";
+
+ auto result = apex_file->Decompress(decompression_file_path);
+ ASSERT_RESULT_OK(result);
+
+ // Assert output path is not empty
+ auto exists = PathExists(decompression_file_path);
+ ASSERT_RESULT_OK(exists);
+ ASSERT_TRUE(*exists) << decompression_file_path << " does not exist";
+
+ // Assert that decompressed apex is same as original apex
+ const std::string original_apex_file_path =
+ kTestDataDir + "com.android.apex.compressed.v1_original.apex";
+ auto comparison_result =
+ CompareFiles(original_apex_file_path, decompression_file_path);
+ ASSERT_RESULT_OK(comparison_result);
+ ASSERT_TRUE(*comparison_result);
+}
+
+TEST(ApexFileTest, DecompressFailForNormalApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1_original.apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ TemporaryFile decompression_file;
+
+ auto result = apex_file->Decompress(decompression_file.path);
+ ASSERT_FALSE(result.ok());
+ ASSERT_THAT(result.error().message(),
+ ::testing::HasSubstr("Cannot decompress an uncompressed APEX"));
+}
+
+TEST(ApexFileTest, DecompressFailIfDecompressionPathExists) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+
+ // Attempt to decompress in a path that already exists
+ TemporaryFile decompression_file;
+ auto exists = PathExists(decompression_file.path);
+ ASSERT_RESULT_OK(exists);
+ ASSERT_TRUE(*exists) << decompression_file.path << " does not exist";
+
+ auto result = apex_file->Decompress(decompression_file.path);
+ ASSERT_FALSE(result.ok());
+ ASSERT_THAT(result.error().message(),
+ ::testing::HasSubstr("Failed to open decompression destination"));
+}
+
+TEST(ApexFileTest, GetPathReturnsRealpath) {
+ const std::string real_path = kTestDataDir + "apex.apexd_test.apex";
+ const std::string symlink_path =
+ kTestDataDir + "apex.apexd_test.symlink.apex";
+
+ // In case the link already exists
+ int ret = unlink(symlink_path.c_str());
+ ASSERT_TRUE(ret == 0 || errno == ENOENT)
+ << "failed to unlink " << symlink_path;
+
+ ret = symlink(real_path.c_str(), symlink_path.c_str());
+ ASSERT_EQ(0, ret) << "failed to create symlink at " << symlink_path;
+
+ // Open with the symlink. Realpath is expected.
+ Result<ApexFile> apex_file = ApexFile::Open(symlink_path);
+ ASSERT_RESULT_OK(apex_file);
+ ASSERT_EQ(real_path, apex_file->GetPath());
+}
+
+TEST(ApexFileTest, CompressedSharedLibsApexIsRejected) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed_sharedlibs.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+
+ ASSERT_FALSE(apex_file.ok());
+ ASSERT_THAT(apex_file.error().message(),
+ ::testing::HasSubstr("Apex providing sharedlibs shouldn't "
+ "be compressed"));
+}
+
+// Check if CAPEX contains originalApexDigest in its manifest
+TEST(ApexFileTest, OriginalApexDigest) {
+ const std::string capex_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ auto capex = ApexFile::Open(capex_path);
+ ASSERT_TRUE(capex.ok());
+ const std::string decompressed_apex_path =
+ kTestDataDir + "com.android.apex.compressed.v1_original.apex";
+ auto decompressed_apex = ApexFile::Open(decompressed_apex_path);
+ ASSERT_TRUE(decompressed_apex.ok());
+ // Validate root digest
+ auto digest = decompressed_apex->VerifyApexVerity(
+ decompressed_apex->GetBundledPublicKey());
+ ASSERT_TRUE(digest.ok());
+ ASSERT_EQ(digest->root_digest,
+ capex->GetManifest().capexmetadata().originalapexdigest());
+}
} // namespace
} // namespace apex
} // namespace android
diff --git a/apexd/apex_manifest.cpp b/apexd/apex_manifest.cpp
index 1994633c..bfba216e 100644
--- a/apexd/apex_manifest.cpp
+++ b/apexd/apex_manifest.cpp
@@ -22,6 +22,7 @@
using android::base::Error;
using android::base::Result;
+using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
@@ -46,8 +47,8 @@ Result<ApexManifest> ParseManifest(const std::string& content) {
return apex_manifest;
}
-std::string GetPackageId(const ApexManifest& apexManifest) {
- return apexManifest.name() + "@" + std::to_string(apexManifest.version());
+std::string GetPackageId(const ApexManifest& apex_manifest) {
+ return apex_manifest.name() + "@" + std::to_string(apex_manifest.version());
}
Result<ApexManifest> ReadManifest(const std::string& path) {
diff --git a/apexd/apex_manifest.h b/apexd/apex_manifest.h
index f2996483..5bdd43bd 100644
--- a/apexd/apex_manifest.h
+++ b/apexd/apex_manifest.h
@@ -23,16 +23,16 @@
#include <string>
-using ::apex::proto::ApexManifest;
-
namespace android {
namespace apex {
// Parses and validates APEX manifest.
-android::base::Result<ApexManifest> ParseManifest(const std::string& content);
+android::base::Result<::apex::proto::ApexManifest> ParseManifest(
+ const std::string& content);
// Returns package id of an ApexManifest
-std::string GetPackageId(const ApexManifest& apex_manifest);
+std::string GetPackageId(const ::apex::proto::ApexManifest& apex_manifest);
// Reads and parses APEX manifest from the file on disk.
-android::base::Result<ApexManifest> ReadManifest(const std::string& path);
+android::base::Result<::apex::proto::ApexManifest> ReadManifest(
+ const std::string& path);
} // namespace apex
} // namespace android
diff --git a/apexd/apex_manifest_test.cpp b/apexd/apex_manifest_test.cpp
index 5a4e3da5..f5bc93ca 100644
--- a/apexd/apex_manifest_test.cpp
+++ b/apexd/apex_manifest_test.cpp
@@ -22,6 +22,8 @@
#include "apex_manifest.h"
+using ::apex::proto::ApexManifest;
+
namespace android {
namespace apex {
diff --git a/apexd/apex_preinstalled_data.cpp b/apexd/apex_preinstalled_data.cpp
deleted file mode 100644
index d62c28c5..00000000
--- a/apexd/apex_preinstalled_data.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#define LOG_TAG "apexd"
-
-#include "apex_preinstalled_data.h"
-
-#include <unordered_map>
-
-#include <android-base/file.h>
-#include <android-base/result.h>
-#include <android-base/strings.h>
-
-#include "apex_constants.h"
-#include "apex_file.h"
-#include "apexd_utils.h"
-#include "string_log.h"
-
-using android::base::Error;
-using android::base::Result;
-
-namespace android {
-namespace apex {
-
-namespace {
-
-struct ApexPreinstalledData {
- std::string name;
- std::string key;
- std::string path;
-};
-
-std::unordered_map<std::string, ApexPreinstalledData> gScannedPreinstalledData;
-
-Result<std::vector<ApexPreinstalledData>> collectPreinstalleDataFromDir(
- const std::string& dir) {
- LOG(INFO) << "Scanning " << dir << " for preinstalled data";
- std::vector<ApexPreinstalledData> ret;
- if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) {
- LOG(INFO) << "... does not exist. Skipping";
- return ret;
- }
- const bool scanBuiltinApexes = isPathForBuiltinApexes(dir);
- if (!scanBuiltinApexes) {
- return Error() << "Can't scan preinstalled APEX data from " << dir;
- }
- Result<std::vector<std::string>> apex_files = FindApexFilesByName(dir);
- if (!apex_files.ok()) {
- return apex_files.error();
- }
-
- for (const auto& file : *apex_files) {
- Result<ApexFile> apex_file = ApexFile::Open(file);
- if (!apex_file.ok()) {
- return Error() << "Failed to open " << file << " : " << apex_file.error();
- }
- ApexPreinstalledData apexPreInstalledData;
- apexPreInstalledData.name = apex_file->GetManifest().name();
- apexPreInstalledData.key = apex_file->GetBundledPublicKey();
- apexPreInstalledData.path = apex_file->GetPath();
- ret.push_back(apexPreInstalledData);
- }
- return ret;
-}
-
-Result<void> updatePreinstalledData(
- const std::vector<ApexPreinstalledData>& apexes) {
- for (const ApexPreinstalledData& apex : apexes) {
- if (gScannedPreinstalledData.find(apex.name) ==
- gScannedPreinstalledData.end()) {
- gScannedPreinstalledData.insert({apex.name, apex});
- } else {
- const std::string& existing_key =
- gScannedPreinstalledData.at(apex.name).key;
- if (existing_key != apex.key) {
- return Error() << "Key for package " << apex.name
- << " does not match with previously scanned key";
- }
- }
- }
- return {};
-}
-
-} // namespace
-
-Result<void> collectPreinstalledData(const std::vector<std::string>& dirs) {
- for (const auto& dir : dirs) {
- Result<std::vector<ApexPreinstalledData>> preinstalledData =
- collectPreinstalleDataFromDir(dir);
- if (!preinstalledData.ok()) {
- return Error() << "Failed to collect keys from " << dir << " : "
- << preinstalledData.error();
- }
- Result<void> st = updatePreinstalledData(*preinstalledData);
- if (!st.ok()) {
- return st;
- }
- }
- return {};
-}
-
-Result<const std::string> getApexKey(const std::string& name) {
- if (gScannedPreinstalledData.find(name) == gScannedPreinstalledData.end()) {
- return Error() << "No preinstalled data found for package " << name;
- }
- return gScannedPreinstalledData[name].key;
-}
-
-Result<const std::string> getApexPreinstalledPath(const std::string& name) {
- if (gScannedPreinstalledData.find(name) == gScannedPreinstalledData.end()) {
- return Error() << "No preinstalled data found for package " << name;
- }
- return gScannedPreinstalledData[name].path;
-}
-
-bool HasPreInstalledVersion(const std::string& name) {
- return gScannedPreinstalledData.find(name) != gScannedPreinstalledData.end();
-}
-
-} // namespace apex
-} // namespace android
diff --git a/apexd/apex_preinstalled_data.h b/apexd/apex_preinstalled_data.h
deleted file mode 100644
index 1b80aad5..00000000
--- a/apexd/apex_preinstalled_data.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <android-base/result.h>
-
-namespace android {
-namespace apex {
-
-android::base::Result<void> collectPreinstalledData(
- const std::vector<std::string>& apex_dirs);
-android::base::Result<const std::string> getApexKey(const std::string& name);
-android::base::Result<const std::string> getApexPreinstalledPath(
- const std::string& name);
-bool HasPreInstalledVersion(const std::string& name);
-} // namespace apex
-} // namespace android
diff --git a/apexd/apex_shim.cpp b/apexd/apex_shim.cpp
index d1ff90d9..0031c3f7 100644
--- a/apexd/apex_shim.cpp
+++ b/apexd/apex_shim.cpp
@@ -33,6 +33,7 @@
using android::base::ErrnoError;
using android::base::Error;
using android::base::Result;
+using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
@@ -126,7 +127,6 @@ Result<void> ValidateShimApex(const std::string& mount_point,
// Unfortunately fs::recursive_directory_iterator::operator++ can throw an
// exception, which means that it's impossible to use range-based for loop
// here.
- // TODO: wrap into a non-throwing iterator to support range-based for loop.
while (iter != fs::end(iter)) {
auto path = iter->path();
// Resolve the mount point to ensure any trailing slash is removed.
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 0010c42c..dc9bfb4b 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -15,25 +15,26 @@
*/
#include "apexd.h"
+#include "apex_file_repository.h"
#include "apexd_private.h"
#include "apex_constants.h"
#include "apex_database.h"
#include "apex_file.h"
#include "apex_manifest.h"
-#include "apex_preinstalled_data.h"
#include "apex_shim.h"
#include "apexd_checkpoint.h"
+#include "apexd_lifecycle.h"
#include "apexd_loop.h"
#include "apexd_prepostinstall.h"
-#include "apexd_prop.h"
#include "apexd_rollback_utils.h"
#include "apexd_session.h"
#include "apexd_utils.h"
#include "apexd_verity.h"
-#include "string_log.h"
+#include "com_android_apex.h"
#include <ApexProperties.sysprop.h>
+#include <android-base/chrono_utils.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
@@ -43,6 +44,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <google/protobuf/util/message_differencer.h>
#include <libavb/libavb.h>
#include <libdm/dm.h>
#include <libdm/dm_table.h>
@@ -51,14 +53,17 @@
#include <dirent.h>
#include <fcntl.h>
+#include <linux/f2fs.h>
#include <linux/loop.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
+#include <sys/sysinfo.h>
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
#include <algorithm>
#include <array>
@@ -66,31 +71,40 @@
#include <cstdlib>
#include <filesystem>
#include <fstream>
+#include <future>
#include <iomanip>
#include <iterator>
#include <memory>
+#include <mutex>
#include <optional>
+#include <queue>
+#include <sstream>
#include <string>
+#include <string_view>
#include <thread>
#include <unordered_map>
#include <unordered_set>
+using android::base::boot_clock;
+using android::base::ConsumePrefix;
using android::base::ErrnoError;
using android::base::Error;
using android::base::GetProperty;
using android::base::Join;
using android::base::ParseUint;
using android::base::ReadFully;
+using android::base::RemoveFileIfExists;
using android::base::Result;
-using android::base::StartsWith;
+using android::base::SetProperty;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
using android::dm::DmTable;
using android::dm::DmTargetVerity;
-
+using ::apex::proto::ApexManifest;
using apex::proto::SessionState;
+using google::protobuf::util::MessageDifferencer;
namespace android {
namespace apex {
@@ -99,12 +113,6 @@ using MountedApexData = MountedApexDatabase::MountedApexData;
namespace {
-// These should be in-sync with system/sepolicy/public/property_contexts
-static constexpr const char* kApexStatusSysprop = "apexd.status";
-static constexpr const char* kApexStatusStarting = "starting";
-static constexpr const char* kApexStatusActivated = "activated";
-static constexpr const char* kApexStatusReady = "ready";
-
static constexpr const char* kBuildFingerprintSysprop = "ro.build.fingerprint";
// This should be in UAPI, but it's not :-(
@@ -113,20 +121,20 @@ static constexpr const char* kDmVerityRestartOnCorruption =
MountedApexDatabase gMountedApexes;
+std::optional<ApexdConfig> gConfig;
+
CheckpointInterface* gVoldService;
bool gSupportsFsCheckpoints = false;
bool gInFsCheckpointMode = false;
static constexpr size_t kLoopDeviceSetupAttempts = 3u;
-bool gBootstrap = false;
+// Please DO NOT add new modules to this list without contacting mainline-modularization@ first.
static const std::vector<std::string> kBootstrapApexes = ([]() {
std::vector<std::string> ret = {
- "com.android.art",
"com.android.i18n",
"com.android.runtime",
"com.android.tzdata",
- "com.android.os.statsd",
};
auto vendor_vndk_ver = GetProperty("ro.vndk.version", "");
@@ -142,14 +150,39 @@ static const std::vector<std::string> kBootstrapApexes = ([]() {
static constexpr const int kNumRetriesWhenCheckpointingEnabled = 1;
-bool isBootstrapApex(const ApexFile& apex) {
+bool IsBootstrapApex(const ApexFile& apex) {
return std::find(kBootstrapApexes.begin(), kBootstrapApexes.end(),
apex.GetManifest().name()) != kBootstrapApexes.end();
}
+void ReleaseF2fsCompressedBlocks(const std::string& file_path) {
+ unique_fd fd(
+ TEMP_FAILURE_RETRY(open(file_path.c_str(), O_RDONLY | O_CLOEXEC, 0)));
+ if (fd.get() == -1) {
+ PLOG(ERROR) << "Failed to open " << file_path;
+ return;
+ }
+ unsigned int flags;
+ if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
+ PLOG(ERROR) << "Failed to call FS_IOC_GETFLAGS on " << file_path;
+ return;
+ }
+ if ((flags & FS_COMPR_FL) == 0) {
+ // Doesn't support f2fs-compression.
+ return;
+ }
+ uint64_t blk_cnt;
+ if (ioctl(fd, F2FS_IOC_RELEASE_COMPRESS_BLOCKS, &blk_cnt) == -1) {
+ PLOG(ERROR) << "Failed to call F2FS_IOC_RELEASE_COMPRESS_BLOCKS on "
+ << file_path;
+ }
+ LOG(INFO) << "Released " << blk_cnt << " compressed blocks from "
+ << file_path;
+}
+
// Pre-allocate loop devices so that we don't have to wait for them
// later when actually activating APEXes.
-Result<void> preAllocateLoopDevices() {
+Result<void> PreAllocateLoopDevices() {
auto scan = FindApexes(kApexPackageBuiltinDirs);
if (!scan.ok()) {
return scan.error();
@@ -157,28 +190,28 @@ Result<void> preAllocateLoopDevices() {
auto size = 0;
for (const auto& path : *scan) {
- auto apexFile = ApexFile::Open(path);
- if (!apexFile.ok()) {
+ auto apex_file = ApexFile::Open(path);
+ if (!apex_file.ok()) {
continue;
}
size++;
// bootstrap Apexes may be activated on separate namespaces.
- if (isBootstrapApex(*apexFile)) {
+ if (IsBootstrapApex(*apex_file)) {
size++;
}
}
- // note: do not call preAllocateLoopDevices() if size == 0.
+ // note: do not call PreAllocateLoopDevices() if size == 0.
// For devices (e.g. ARC) which doesn't support loop-control
- // preAllocateLoopDevices() can cause problem when it tries
+ // PreAllocateLoopDevices() can cause problem when it tries
// to access /dev/loop-control.
if (size == 0) {
return {};
}
- return loop::preAllocateLoopDevices(size);
+ return loop::PreAllocateLoopDevices(size);
}
-std::unique_ptr<DmTable> createVerityTable(const ApexVerityData& verity_data,
+std::unique_ptr<DmTable> CreateVerityTable(const ApexVerityData& verity_data,
const std::string& block_device,
const std::string& hash_device,
bool restart_on_corruption) {
@@ -209,9 +242,18 @@ std::unique_ptr<DmTable> createVerityTable(const ApexVerityData& verity_data,
// Deletes a dm-verity device with a given name and path
// Synchronizes on the device actually being deleted from userspace.
-Result<void> DeleteVerityDevice(const std::string& name) {
+Result<void> DeleteVerityDevice(const std::string& name, bool deferred) {
DeviceMapper& dm = DeviceMapper::Instance();
- if (!dm.DeleteDevice(name, 750ms)) {
+ if (deferred) {
+ if (!dm.DeleteDeviceDeferred(name)) {
+ return ErrnoError() << "Failed to issue deferred delete of verity device "
+ << name;
+ }
+ return {};
+ }
+ auto timeout = std::chrono::milliseconds(
+ android::sysprop::ApexProperties::dm_delete_timeout().value_or(750));
+ if (!dm.DeleteDevice(name, timeout)) {
return Error() << "Failed to delete dm-device " << name;
}
return {};
@@ -244,7 +286,7 @@ class DmVerityDevice {
~DmVerityDevice() {
if (!cleared_) {
- Result<void> ret = DeleteVerityDevice(name_);
+ Result<void> ret = DeleteVerityDevice(name_, /* deferred= */ false);
if (!ret.ok()) {
LOG(ERROR) << ret.error();
}
@@ -262,32 +304,46 @@ class DmVerityDevice {
bool cleared_;
};
-Result<DmVerityDevice> createVerityDevice(const std::string& name,
+Result<DmVerityDevice> CreateVerityDevice(const std::string& name,
const DmTable& table) {
DeviceMapper& dm = DeviceMapper::Instance();
if (dm.GetState(name) != DmDeviceState::INVALID) {
- // TODO: since apexd tears down devices during unmount, can this happen?
+ // Delete dangling dm-device. This can happen if apexd fails to delete it
+ // while unmounting an apex.
LOG(WARNING) << "Deleting existing dm device " << name;
- const Result<void>& result = DeleteVerityDevice(name);
+ auto result = DeleteVerityDevice(name, /* deferred= */ false);
if (!result.ok()) {
- // TODO: should we fail instead?
- LOG(ERROR) << "Failed to delete device " << name << " : "
- << result.error();
+ return result.error();
}
}
+ auto timeout = std::chrono::milliseconds(
+ android::sysprop::ApexProperties::dm_create_timeout().value_or(1000));
std::string dev_path;
- if (!dm.CreateDevice(name, table, &dev_path, 500ms)) {
+ if (!dm.CreateDevice(name, table, &dev_path, timeout)) {
return Errorf("Couldn't create verity device.");
}
return DmVerityDevice(name, dev_path);
}
+/**
+ * When we create hardlink for a new apex package in kActiveApexPackagesDataDir,
+ * there might be an older version of the same package already present in there.
+ * Since a new version of the same package is being installed on this boot, the
+ * old one needs to deleted so that we don't end up activating same package
+ * twice.
+ *
+ * @param affected_packages package names of the news apex that are being
+ * installed in this boot
+ * @param files_to_keep path to the new apex packages in
+ * kActiveApexPackagesDataDir
+ */
Result<void> RemovePreviouslyActiveApexFiles(
const std::unordered_set<std::string>& affected_packages,
const std::unordered_set<std::string>& files_to_keep) {
- auto all_active_apex_files = FindApexFilesByName(kActiveApexPackagesDataDir);
+ auto all_active_apex_files =
+ FindFilesBySuffix(gConfig->active_apex_data_dir, {kApexPackageSuffix});
if (!all_active_apex_files.ok()) {
return all_active_apex_files.error();
@@ -321,13 +377,14 @@ Result<void> RemovePreviouslyActiveApexFiles(
}
// Reads the entire device to verify the image is authenticatic
-Result<void> readVerityDevice(const std::string& verity_device,
+Result<void> ReadVerityDevice(const std::string& verity_device,
uint64_t device_size) {
static constexpr int kBlockSize = 4096;
static constexpr size_t kBufSize = 1024 * kBlockSize;
std::vector<uint8_t> buffer(kBufSize);
- unique_fd fd(TEMP_FAILURE_RETRY(open(verity_device.c_str(), O_RDONLY)));
+ unique_fd fd(
+ TEMP_FAILURE_RETRY(open(verity_device.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd.get() == -1) {
return ErrnoError() << "Can't open " << verity_device;
}
@@ -346,9 +403,16 @@ Result<void> readVerityDevice(const std::string& verity_device,
Result<void> VerifyMountedImage(const ApexFile& apex,
const std::string& mount_point) {
- auto result = apex.VerifyManifestMatches(mount_point);
- if (!result.ok()) {
- return result;
+ // Verify that apex_manifest.pb inside mounted image matches the one in the
+ // outer .apex container.
+ Result<ApexManifest> verified_manifest =
+ ReadManifest(mount_point + "/" + kManifestFilenamePb);
+ if (!verified_manifest.ok()) {
+ return verified_manifest.error();
+ }
+ if (!MessageDifferencer::Equals(*verified_manifest, apex.GetManifest())) {
+ return Errorf(
+ "Manifest inside filesystem does not match manifest outside it");
}
if (shim::IsShimApex(apex)) {
return shim::ValidateShimApex(mount_point, apex);
@@ -357,11 +421,18 @@ Result<void> VerifyMountedImage(const ApexFile& apex,
}
Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
- const std::string& mountPoint,
+ const std::string& mount_point,
const std::string& device_name,
const std::string& hashtree_file,
- bool verifyImage) {
- LOG(VERBOSE) << "Creating mount point: " << mountPoint;
+ bool verify_image,
+ bool temp_mount = false) {
+ if (apex.IsCompressed()) {
+ return Error() << "Cannot directly mount compressed APEX "
+ << apex.GetPath();
+ }
+
+ LOG(VERBOSE) << "Creating mount point: " << mount_point;
+ auto time_started = boot_clock::now();
// Note: the mount point could exist in case when the APEX was activated
// during the bootstrap phase (e.g., the runtime or tzdata APEX).
// Although we have separate mount namespaces to separate the early activated
@@ -370,31 +441,34 @@ Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
// mounted at / which is (and has to be) a shared mount. Therefore, if apexd
// finds an empty directory under /apex, it's not a problem and apexd can use
// it.
- auto exists = PathExists(mountPoint);
+ auto exists = PathExists(mount_point);
if (!exists.ok()) {
return exists.error();
}
- if (!*exists && mkdir(mountPoint.c_str(), kMkdirMode) != 0) {
- return ErrnoError() << "Could not create mount point " << mountPoint;
+ if (!*exists && mkdir(mount_point.c_str(), kMkdirMode) != 0) {
+ return ErrnoError() << "Could not create mount point " << mount_point;
}
- auto deleter = [&mountPoint]() {
- if (rmdir(mountPoint.c_str()) != 0) {
- PLOG(WARNING) << "Could not rmdir " << mountPoint;
+ auto deleter = [&mount_point]() {
+ if (rmdir(mount_point.c_str()) != 0) {
+ PLOG(WARNING) << "Could not rmdir " << mount_point;
}
};
auto scope_guard = android::base::make_scope_guard(deleter);
- if (!IsEmptyDirectory(mountPoint)) {
- return ErrnoError() << mountPoint << " is not empty";
+ if (!IsEmptyDirectory(mount_point)) {
+ return ErrnoError() << mount_point << " is not empty";
}
const std::string& full_path = apex.GetPath();
- loop::LoopbackDeviceUniqueFd loopbackDevice;
+ if (!apex.GetImageOffset() || !apex.GetImageSize()) {
+ return Error() << "Cannot create mount point without image offset and size";
+ }
+ loop::LoopbackDeviceUniqueFd loopback_device;
for (size_t attempts = 1;; ++attempts) {
- Result<loop::LoopbackDeviceUniqueFd> ret = loop::createLoopDevice(
- full_path, apex.GetImageOffset(), apex.GetImageSize());
+ Result<loop::LoopbackDeviceUniqueFd> ret = loop::CreateLoopDevice(
+ full_path, apex.GetImageOffset().value(), apex.GetImageSize().value());
if (ret.ok()) {
- loopbackDevice = std::move(*ret);
+ loopback_device = std::move(*ret);
break;
}
if (attempts >= kLoopDeviceSetupAttempts) {
@@ -402,33 +476,43 @@ Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
<< ret.error();
}
}
- LOG(VERBOSE) << "Loopback device created: " << loopbackDevice.name;
+ LOG(VERBOSE) << "Loopback device created: " << loopback_device.name;
- auto verityData = apex.VerifyApexVerity();
- if (!verityData.ok()) {
+ auto& instance = ApexFileRepository::GetInstance();
+
+ auto public_key = instance.GetPublicKey(apex.GetManifest().name());
+ if (!public_key.ok()) {
+ return public_key.error();
+ }
+
+ auto verity_data = apex.VerifyApexVerity(*public_key);
+ if (!verity_data.ok()) {
return Error() << "Failed to verify Apex Verity data for " << full_path
- << ": " << verityData.error();
+ << ": " << verity_data.error();
}
- std::string blockDevice = loopbackDevice.name;
- MountedApexData apex_data(loopbackDevice.name, apex.GetPath(), mountPoint,
+ std::string block_device = loopback_device.name;
+ MountedApexData apex_data(loopback_device.name, apex.GetPath(), mount_point,
/* device_name = */ "",
- /* hashtree_loop_name = */ "");
+ /* hashtree_loop_name = */ "",
+ /* is_temp_mount */ temp_mount);
// for APEXes in immutable partitions, we don't need to mount them on
// dm-verity because they are already in the dm-verity protected partition;
// system. However, note that we don't skip verification to ensure that APEXes
// are correctly signed.
- const bool mountOnVerity = !isPathForBuiltinApexes(full_path);
- DmVerityDevice verityDev;
+ const bool mount_on_verity =
+ !instance.IsPreInstalledApex(apex) || instance.IsDecompressedApex(apex);
+
+ DmVerityDevice verity_dev;
loop::LoopbackDeviceUniqueFd loop_for_hash;
- if (mountOnVerity) {
- std::string hash_device = loopbackDevice.name;
- if (verityData->desc->tree_size == 0) {
- if (auto st = PrepareHashTree(apex, *verityData, hashtree_file);
+ if (mount_on_verity) {
+ std::string hash_device = loopback_device.name;
+ if (verity_data->desc->tree_size == 0) {
+ if (auto st = PrepareHashTree(apex, *verity_data, hashtree_file);
!st.ok()) {
return st.error();
}
- auto create_loop_status = loop::createLoopDevice(hashtree_file, 0, 0);
+ auto create_loop_status = loop::CreateLoopDevice(hashtree_file, 0, 0);
if (!create_loop_status.ok()) {
return create_loop_status.error();
}
@@ -436,54 +520,59 @@ Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
hash_device = loop_for_hash.name;
apex_data.hashtree_loop_name = hash_device;
}
- auto verityTable =
- createVerityTable(*verityData, loopbackDevice.name, hash_device,
- /* restart_on_corruption = */ !verifyImage);
- Result<DmVerityDevice> verityDevRes =
- createVerityDevice(device_name, *verityTable);
- if (!verityDevRes.ok()) {
+ auto verity_table =
+ CreateVerityTable(*verity_data, loopback_device.name, hash_device,
+ /* restart_on_corruption = */ !verify_image);
+ Result<DmVerityDevice> verity_dev_res =
+ CreateVerityDevice(device_name, *verity_table);
+ if (!verity_dev_res.ok()) {
return Error() << "Failed to create Apex Verity device " << full_path
- << ": " << verityDevRes.error();
+ << ": " << verity_dev_res.error();
}
- verityDev = std::move(*verityDevRes);
+ verity_dev = std::move(*verity_dev_res);
apex_data.device_name = device_name;
- blockDevice = verityDev.GetDevPath();
+ block_device = verity_dev.GetDevPath();
- Result<void> readAheadStatus =
- loop::configureReadAhead(verityDev.GetDevPath());
- if (!readAheadStatus.ok()) {
- return readAheadStatus.error();
+ Result<void> read_ahead_status =
+ loop::ConfigureReadAhead(verity_dev.GetDevPath());
+ if (!read_ahead_status.ok()) {
+ return read_ahead_status.error();
}
}
- // TODO: consider moving this inside RunVerifyFnInsideTempMount.
- if (mountOnVerity && verifyImage) {
- Result<void> verityStatus =
- readVerityDevice(blockDevice, (*verityData).desc->image_size);
- if (!verityStatus.ok()) {
- return verityStatus.error();
+ // TODO(b/158467418): consider moving this inside RunVerifyFnInsideTempMount.
+ if (mount_on_verity && verify_image) {
+ Result<void> verity_status =
+ ReadVerityDevice(block_device, (*verity_data).desc->image_size);
+ if (!verity_status.ok()) {
+ return verity_status.error();
}
}
- uint32_t mountFlags = MS_NOATIME | MS_NODEV | MS_DIRSYNC | MS_RDONLY;
+ uint32_t mount_flags = MS_NOATIME | MS_NODEV | MS_DIRSYNC | MS_RDONLY;
if (apex.GetManifest().nocode()) {
- mountFlags |= MS_NOEXEC;
+ mount_flags |= MS_NOEXEC;
}
- if (mount(blockDevice.c_str(), mountPoint.c_str(), "ext4", mountFlags,
- nullptr) == 0) {
+ if (!apex.GetFsType()) {
+ return Error() << "Cannot mount package without FsType";
+ }
+ if (mount(block_device.c_str(), mount_point.c_str(),
+ apex.GetFsType().value().c_str(), mount_flags, nullptr) == 0) {
+ auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+ boot_clock::now() - time_started).count();
LOG(INFO) << "Successfully mounted package " << full_path << " on "
- << mountPoint;
- auto status = VerifyMountedImage(apex, mountPoint);
+ << mount_point << " duration=" << time_elapsed;
+ auto status = VerifyMountedImage(apex, mount_point);
if (!status.ok()) {
- if (umount2(mountPoint.c_str(), UMOUNT_NOFOLLOW) != 0) {
- PLOG(ERROR) << "Failed to umount " << mountPoint;
+ if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW) != 0) {
+ PLOG(ERROR) << "Failed to umount " << mount_point;
}
return Error() << "Failed to verify " << full_path << ": "
<< status.error();
}
// Time to accept the temporaries as good.
- verityDev.Release();
- loopbackDevice.CloseGood();
+ verity_dev.Release();
+ loopback_device.CloseGood();
loop_for_hash.CloseGood();
scope_guard.Disable(); // Accept the mount.
@@ -494,8 +583,9 @@ Result<MountedApexData> MountPackageImpl(const ApexFile& apex,
}
std::string GetHashTreeFileName(const ApexFile& apex, bool is_new) {
+ const std::string& id = GetPackageId(apex.GetManifest());
std::string ret =
- std::string(kApexHashTreeDir) + "/" + GetPackageId(apex.GetManifest());
+ StringPrintf("%s/%s", gConfig->apex_hash_tree_dir, id.c_str());
return is_new ? ret + ".new" : ret;
}
@@ -511,28 +601,40 @@ Result<MountedApexData> VerifyAndTempMountPackage(
return ErrnoError() << "Failed to unlink " << hashtree_file;
}
}
- return MountPackageImpl(apex, mount_point, temp_device_name,
- GetHashTreeFileName(apex, /* is_new = */ true),
- /* verifyImage = */ true);
+ auto ret =
+ MountPackageImpl(apex, mount_point, temp_device_name, hashtree_file,
+ /* verify_image = */ true, /* temp_mount = */ true);
+ if (!ret.ok()) {
+ LOG(DEBUG) << "Cleaning up " << hashtree_file;
+ if (TEMP_FAILURE_RETRY(unlink(hashtree_file.c_str())) != 0) {
+ PLOG(ERROR) << "Failed to unlink " << hashtree_file;
+ }
+ } else {
+ gMountedApexes.AddMountedApex(apex.GetManifest().name(), false, *ret);
+ }
+ return ret;
}
-Result<void> Unmount(const MountedApexData& data) {
+} // namespace
+
+Result<void> Unmount(const MountedApexData& data, bool deferred) {
LOG(DEBUG) << "Unmounting " << data.full_path << " from mount point "
- << data.mount_point;
+ << data.mount_point << " deferred = " << deferred;
// Lazily try to umount whatever is mounted.
if (umount2(data.mount_point.c_str(), UMOUNT_NOFOLLOW) != 0 &&
errno != EINVAL && errno != ENOENT) {
return ErrnoError() << "Failed to unmount directory " << data.mount_point;
}
- // Attempt to delete the folder. If the folder is retained, other
- // data may be incorrect.
- if (rmdir(data.mount_point.c_str()) != 0) {
- PLOG(ERROR) << "Failed to rmdir directory " << data.mount_point;
+
+ if (!deferred) {
+ if (rmdir(data.mount_point.c_str()) != 0) {
+ PLOG(ERROR) << "Failed to rmdir " << data.mount_point;
+ }
}
// Try to free up the device-mapper device.
if (!data.device_name.empty()) {
- const auto& result = DeleteVerityDevice(data.device_name);
+ const auto& result = DeleteVerityDevice(data.device_name, deferred);
if (!result.ok()) {
return result;
}
@@ -542,19 +644,28 @@ Result<void> Unmount(const MountedApexData& data) {
auto log_fn = [](const std::string& path, const std::string& /*id*/) {
LOG(VERBOSE) << "Freeing loop device " << path << " for unmount.";
};
- if (!data.loop_name.empty()) {
+
+ // Since we now use LO_FLAGS_AUTOCLEAR when configuring loop devices, in
+ // theory we don't need to manually call DestroyLoopDevice here even if
+ // |deferred| is false. However we prefer to call it to ensure the invariant
+ // of SubmitStagedSession (after it's done, loop devices created for temp
+ // mount are freed).
+ if (!data.loop_name.empty() && !deferred) {
loop::DestroyLoopDevice(data.loop_name, log_fn);
}
- if (!data.hashtree_loop_name.empty()) {
+ if (!data.hashtree_loop_name.empty() && !deferred) {
loop::DestroyLoopDevice(data.hashtree_loop_name, log_fn);
}
return {};
}
+namespace {
+
template <typename VerifyFn>
Result<void> RunVerifyFnInsideTempMount(const ApexFile& apex,
- const VerifyFn& verify_fn) {
+ const VerifyFn& verify_fn,
+ bool unmount_during_cleanup) {
// Temp mount image of this apex to validate it was properly signed;
// this will also read the entire block device through dm-verity, so
// we can be sure there is no corruption.
@@ -569,11 +680,15 @@ Result<void> RunVerifyFnInsideTempMount(const ApexFile& apex,
return mount_status.error();
}
auto cleaner = [&]() {
- LOG(DEBUG) << "Unmounting " << temp_mount_point;
- Result<void> result = Unmount(*mount_status);
- if (!result.ok()) {
- LOG(WARNING) << "Failed to unmount " << temp_mount_point << " : "
- << result.error();
+ if (unmount_during_cleanup) {
+ LOG(DEBUG) << "Unmounting " << temp_mount_point;
+ Result<void> result = Unmount(*mount_status, /* deferred= */ false);
+ if (!result.ok()) {
+ LOG(WARNING) << "Failed to unmount " << temp_mount_point << " : "
+ << result.error();
+ }
+ gMountedApexes.RemoveMountedApex(apex.GetManifest().name(),
+ apex.GetPath(), true);
}
};
auto scope_guard = android::base::make_scope_guard(cleaner);
@@ -583,6 +698,11 @@ Result<void> RunVerifyFnInsideTempMount(const ApexFile& apex,
template <typename HookFn, typename HookCall>
Result<void> PrePostinstallPackages(const std::vector<ApexFile>& apexes,
HookFn fn, HookCall call) {
+ auto scope_guard = android::base::make_scope_guard([&]() {
+ for (const ApexFile& apex_file : apexes) {
+ apexd_private::UnmountTempMount(apex_file);
+ }
+ });
if (apexes.empty()) {
return Errorf("Empty set of inputs");
}
@@ -596,9 +716,26 @@ Result<void> PrePostinstallPackages(const std::vector<ApexFile>& apexes,
}
}
- // 2) If we found hooks, run the pre/post-install.
+ // 2) If we found hooks, temp mount if required, and run the pre/post-install.
if (has_hooks) {
- Result<void> install_status = (*call)(apexes);
+ std::vector<std::string> mount_points;
+ for (const ApexFile& apex : apexes) {
+ // Retrieve the mount data if the apex is already temp mounted, temp
+ // mount it otherwise.
+ std::string mount_point =
+ apexd_private::GetPackageTempMountPoint(apex.GetManifest());
+ Result<MountedApexData> mount_data =
+ apexd_private::GetTempMountedApexData(apex.GetManifest().name());
+ if (!mount_data.ok()) {
+ mount_data = VerifyAndTempMountPackage(apex, mount_point);
+ if (!mount_data.ok()) {
+ return mount_data.error();
+ }
+ }
+ mount_points.push_back(mount_point);
+ }
+
+ Result<void> install_status = (*call)(apexes, mount_points);
if (!install_status.ok()) {
return install_status;
}
@@ -617,20 +754,23 @@ Result<void> PostinstallPackages(const std::vector<ApexFile>& apexes) {
&StagePostInstall);
}
-template <typename RetType, typename Fn>
-RetType HandlePackages(const std::vector<std::string>& paths, Fn fn) {
- // 1) Open all APEXes.
- std::vector<ApexFile> apex_files;
+// Converts a list of apex file paths into a list of ApexFile objects
+//
+// Returns error when trying to open empty set of inputs.
+Result<std::vector<ApexFile>> OpenApexFiles(
+ const std::vector<std::string>& paths) {
+ if (paths.empty()) {
+ return Errorf("Empty set of inputs");
+ }
+ std::vector<ApexFile> ret;
for (const std::string& path : paths) {
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
return apex_file.error();
}
- apex_files.emplace_back(std::move(*apex_file));
+ ret.emplace_back(std::move(*apex_file));
}
-
- // 2) Dispatch.
- return fn(apex_files);
+ return ret;
}
Result<void> ValidateStagingShimApex(const ApexFile& to) {
@@ -643,14 +783,20 @@ Result<void> ValidateStagingShimApex(const ApexFile& to) {
auto verify_fn = [&](const std::string& system_apex_path) {
return shim::ValidateUpdate(system_apex_path, to.GetPath());
};
- return RunVerifyFnInsideTempMount(*system_shim, verify_fn);
+ return RunVerifyFnInsideTempMount(*system_shim, verify_fn, true);
}
// A version of apex verification that happens during boot.
// This function should only verification checks that are necessary to run on
// each boot. Try to avoid putting expensive checks inside this function.
Result<void> VerifyPackageBoot(const ApexFile& apex_file) {
- Result<ApexVerityData> verity_or = apex_file.VerifyApexVerity();
+ // TODO(ioffe): why do we need this here?
+ auto& instance = ApexFileRepository::GetInstance();
+ auto public_key = instance.GetPublicKey(apex_file.GetManifest().name());
+ if (!public_key.ok()) {
+ return public_key.error();
+ }
+ Result<ApexVerityData> verity_or = apex_file.VerifyApexVerity(*public_key);
if (!verity_or.ok()) {
return verity_or.error();
}
@@ -667,49 +813,48 @@ Result<void> VerifyPackageBoot(const ApexFile& apex_file) {
return {};
}
-// A version of apex verification that happens on submitStagedSession.
+// A version of apex verification that happens on SubmitStagedSession.
// This function contains checks that might be expensive to perform, e.g. temp
// mounting a package and reading entire dm-verity device, and shouldn't be run
// during boot.
-Result<void> VerifyPackageInstall(const ApexFile& apex_file) {
+Result<void> VerifyPackageStagedInstall(const ApexFile& apex_file) {
const auto& verify_package_boot_status = VerifyPackageBoot(apex_file);
if (!verify_package_boot_status.ok()) {
return verify_package_boot_status;
}
- Result<ApexVerityData> verity_or = apex_file.VerifyApexVerity();
constexpr const auto kSuccessFn = [](const std::string& /*mount_point*/) {
return Result<void>{};
};
- return RunVerifyFnInsideTempMount(apex_file, kSuccessFn);
+ return RunVerifyFnInsideTempMount(apex_file, kSuccessFn, false);
}
template <typename VerifyApexFn>
-Result<std::vector<ApexFile>> verifyPackages(
+Result<std::vector<ApexFile>> VerifyPackages(
const std::vector<std::string>& paths, const VerifyApexFn& verify_apex_fn) {
- if (paths.empty()) {
- return Errorf("Empty set of inputs");
+ Result<std::vector<ApexFile>> apex_files = OpenApexFiles(paths);
+ if (!apex_files.ok()) {
+ return apex_files.error();
}
- LOG(DEBUG) << "verifyPackages() for " << Join(paths, ',');
- auto verify_fn = [&](std::vector<ApexFile>& apexes) {
- for (const ApexFile& apex_file : apexes) {
- Result<void> result = verify_apex_fn(apex_file);
- if (!result.ok()) {
- return Result<std::vector<ApexFile>>(result.error());
- }
+ LOG(DEBUG) << "VerifyPackages() for " << Join(paths, ',');
+
+ for (const ApexFile& apex_file : *apex_files) {
+ Result<void> result = verify_apex_fn(apex_file);
+ if (!result.ok()) {
+ return result.error();
}
- return Result<std::vector<ApexFile>>(std::move(apexes));
- };
- return HandlePackages<Result<std::vector<ApexFile>>>(paths, verify_fn);
+ }
+ return std::move(*apex_files);
}
-Result<ApexFile> verifySessionDir(const int session_id) {
- std::string sessionDirPath = std::string(kStagedSessionsDir) + "/session_" +
- std::to_string(session_id);
- LOG(INFO) << "Scanning " << sessionDirPath
+Result<ApexFile> VerifySessionDir(const int session_id) {
+ std::string session_dir_path = std::string(kStagedSessionsDir) + "/session_" +
+ std::to_string(session_id);
+ LOG(INFO) << "Scanning " << session_dir_path
<< " looking for packages to be validated";
- Result<std::vector<std::string>> scan = FindApexFilesByName(sessionDirPath);
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(session_dir_path, {kApexPackageSuffix});
if (!scan.ok()) {
LOG(WARNING) << scan.error();
return scan.error();
@@ -720,7 +865,7 @@ Result<ApexFile> verifySessionDir(const int session_id) {
"More than one APEX package found in the same session directory.");
}
- auto verified = verifyPackages(*scan, VerifyPackageInstall);
+ auto verified = VerifyPackages(*scan, VerifyPackageStagedInstall);
if (!verified.ok()) {
return verified.error();
}
@@ -741,25 +886,27 @@ Result<void> DeleteBackup() {
}
Result<void> BackupActivePackages() {
- LOG(DEBUG) << "Initializing backup of " << kActiveApexPackagesDataDir;
+ LOG(DEBUG) << "Initializing backup of " << gConfig->active_apex_data_dir;
// Previous restore might've delete backups folder.
- auto create_status = createDirIfNeeded(kApexBackupDir, 0700);
+ auto create_status = CreateDirIfNeeded(kApexBackupDir, 0700);
if (!create_status.ok()) {
return Error() << "Backup failed : " << create_status.error();
}
- auto apex_active_exists = PathExists(std::string(kActiveApexPackagesDataDir));
+ auto apex_active_exists =
+ PathExists(std::string(gConfig->active_apex_data_dir));
if (!apex_active_exists.ok()) {
return Error() << "Backup failed : " << apex_active_exists.error();
}
if (!*apex_active_exists) {
- LOG(DEBUG) << kActiveApexPackagesDataDir
+ LOG(DEBUG) << gConfig->active_apex_data_dir
<< " does not exist. Nothing to backup";
return {};
}
- auto active_packages = FindApexFilesByName(kActiveApexPackagesDataDir);
+ auto active_packages =
+ FindFilesBySuffix(gConfig->active_apex_data_dir, {kApexPackageSuffix});
if (!active_packages.ok()) {
return Error() << "Backup failed : " << active_packages.error();
}
@@ -800,7 +947,7 @@ Result<void> BackupActivePackages() {
}
Result<void> RestoreActivePackages() {
- LOG(DEBUG) << "Initializing restore of " << kActiveApexPackagesDataDir;
+ LOG(DEBUG) << "Initializing restore of " << gConfig->active_apex_data_dir;
auto backup_exists = PathExists(std::string(kApexBackupDir));
if (!backup_exists.ok()) {
@@ -811,37 +958,38 @@ Result<void> RestoreActivePackages() {
}
struct stat stat_data;
- if (stat(kActiveApexPackagesDataDir, &stat_data) != 0) {
- return ErrnoError() << "Failed to access " << kActiveApexPackagesDataDir;
+ if (stat(gConfig->active_apex_data_dir, &stat_data) != 0) {
+ return ErrnoError() << "Failed to access " << gConfig->active_apex_data_dir;
}
- LOG(DEBUG) << "Deleting existing packages in " << kActiveApexPackagesDataDir;
+ LOG(DEBUG) << "Deleting existing packages in "
+ << gConfig->active_apex_data_dir;
auto delete_status =
- DeleteDirContent(std::string(kActiveApexPackagesDataDir));
+ DeleteDirContent(std::string(gConfig->active_apex_data_dir));
if (!delete_status.ok()) {
return delete_status;
}
LOG(DEBUG) << "Renaming " << kApexBackupDir << " to "
- << kActiveApexPackagesDataDir;
- if (rename(kApexBackupDir, kActiveApexPackagesDataDir) != 0) {
+ << gConfig->active_apex_data_dir;
+ if (rename(kApexBackupDir, gConfig->active_apex_data_dir) != 0) {
return ErrnoError() << "Failed to rename " << kApexBackupDir << " to "
- << kActiveApexPackagesDataDir;
+ << gConfig->active_apex_data_dir;
}
LOG(DEBUG) << "Restoring original permissions for "
- << kActiveApexPackagesDataDir;
- if (chmod(kActiveApexPackagesDataDir, stat_data.st_mode & ALLPERMS) != 0) {
- // TODO: should we wipe out /data/apex/active if chmod fails?
+ << gConfig->active_apex_data_dir;
+ if (chmod(gConfig->active_apex_data_dir, stat_data.st_mode & ALLPERMS) != 0) {
return ErrnoError() << "Failed to restore original permissions for "
- << kActiveApexPackagesDataDir;
+ << gConfig->active_apex_data_dir;
}
return {};
}
-Result<void> UnmountPackage(const ApexFile& apex, bool allow_latest) {
- LOG(VERBOSE) << "Unmounting " << GetPackageId(apex.GetManifest());
+Result<void> UnmountPackage(const ApexFile& apex, bool allow_latest,
+ bool deferred) {
+ LOG(INFO) << "Unmounting " << GetPackageId(apex.GetManifest());
const ApexManifest& manifest = apex.GetManifest();
@@ -860,33 +1008,39 @@ Result<void> UnmountPackage(const ApexFile& apex, bool allow_latest) {
return Error() << "Did not find " << apex.GetPath();
}
- if (latest) {
+ // Concept of latest sharedlibs apex is somewhat blurred. Since this is only
+ // used in testing, it is ok to always allow unmounting sharedlibs apex.
+ if (latest && !manifest.providesharedapexlibs()) {
if (!allow_latest) {
return Error() << "Package " << apex.GetPath() << " is active";
}
std::string mount_point = apexd_private::GetActiveMountPoint(manifest);
- LOG(VERBOSE) << "Unmounting and deleting " << mount_point;
+ LOG(INFO) << "Unmounting " << mount_point;
if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW) != 0) {
return ErrnoError() << "Failed to unmount " << mount_point;
}
- if (rmdir(mount_point.c_str()) != 0) {
- PLOG(ERROR) << "Could not rmdir " << mount_point;
- // Continue here.
+
+ if (!deferred) {
+ if (rmdir(mount_point.c_str()) != 0) {
+ PLOG(ERROR) << "Failed to rmdir " << mount_point;
+ }
}
}
// Clean up gMountedApexes now, even though we're not fully done.
gMountedApexes.RemoveMountedApex(manifest.name(), apex.GetPath());
- return Unmount(*data);
+ return Unmount(*data, deferred);
}
} // namespace
-Result<void> MountPackage(const ApexFile& apex, const std::string& mountPoint) {
- auto ret =
- MountPackageImpl(apex, mountPoint, GetPackageId(apex.GetManifest()),
- GetHashTreeFileName(apex, /* is_new = */ false),
- /* verifyImage = */ false);
+void SetConfig(const ApexdConfig& config) { gConfig = config; }
+
+Result<void> MountPackage(const ApexFile& apex, const std::string& mount_point,
+ const std::string& device_name) {
+ auto ret = MountPackageImpl(apex, mount_point, device_name,
+ GetHashTreeFileName(apex, /* is_new= */ false),
+ /* verify_image = */ false);
if (!ret.ok()) {
return ret.error();
}
@@ -897,15 +1051,41 @@ Result<void> MountPackage(const ApexFile& apex, const std::string& mountPoint) {
namespace apexd_private {
-Result<MountedApexData> TempMountPackage(const ApexFile& apex,
- const std::string& mount_point) {
- // TODO(ioffe): consolidate these two methods.
- return android::apex::VerifyAndTempMountPackage(apex, mount_point);
+Result<void> UnmountTempMount(const ApexFile& apex) {
+ const ApexManifest& manifest = apex.GetManifest();
+ LOG(VERBOSE) << "Unmounting all temp mounts for package " << manifest.name();
+
+ bool finished_unmounting = false;
+ // If multiple temp mounts exist, ensure that all are unmounted.
+ while (!finished_unmounting) {
+ Result<MountedApexData> data =
+ apexd_private::GetTempMountedApexData(manifest.name());
+ if (!data.ok()) {
+ finished_unmounting = true;
+ } else {
+ gMountedApexes.RemoveMountedApex(manifest.name(), data->full_path, true);
+ Unmount(*data, /* deferred= */ false);
+ }
+ }
+ return {};
}
-Result<void> Unmount(const MountedApexData& data) {
- // TODO(ioffe): consolidate these two methods.
- return android::apex::Unmount(data);
+Result<MountedApexData> GetTempMountedApexData(const std::string& package) {
+ bool found = false;
+ Result<MountedApexData> mount_data;
+ gMountedApexes.ForallMountedApexes(
+ package,
+ [&](const MountedApexData& data, [[maybe_unused]] bool latest) {
+ if (!found) {
+ mount_data = data;
+ found = true;
+ }
+ },
+ true);
+ if (found) {
+ return mount_data;
+ }
+ return Error() << "No temp mount data found for " << package;
}
bool IsMounted(const std::string& full_path) {
@@ -934,77 +1114,219 @@ std::string GetActiveMountPoint(const ApexManifest& manifest) {
} // namespace apexd_private
-Result<void> resumeRevertIfNeeded() {
+Result<void> ResumeRevertIfNeeded() {
auto sessions =
ApexSession::GetSessionsInState(SessionState::REVERT_IN_PROGRESS);
if (sessions.empty()) {
return {};
}
- return revertActiveSessions("");
+ return RevertActiveSessions("", "");
+}
+
+Result<void> ActivateSharedLibsPackage(const std::string& mount_point) {
+ for (const auto& lib_path : {"lib", "lib64"}) {
+ std::string apex_lib_path = mount_point + "/" + lib_path;
+ auto lib_dir = PathExists(apex_lib_path);
+ if (!lib_dir.ok() || !*lib_dir) {
+ continue;
+ }
+
+ auto iter = std::filesystem::directory_iterator(apex_lib_path);
+ std::error_code ec;
+
+ while (iter != std::filesystem::end(iter)) {
+ const auto& lib_entry = *iter;
+ if (!lib_entry.is_directory()) {
+ iter = iter.increment(ec);
+ if (ec) {
+ return Error() << "Failed to scan " << apex_lib_path << " : "
+ << ec.message();
+ }
+ continue;
+ }
+
+ const auto library_name = lib_entry.path().filename();
+ const std::string library_symlink_dir =
+ StringPrintf("%s/%s/%s/%s", kApexRoot, kApexSharedLibsSubDir,
+ lib_path, library_name.c_str());
+
+ auto symlink_dir = PathExists(library_symlink_dir);
+ if (!symlink_dir.ok() || !*symlink_dir) {
+ std::filesystem::create_directory(library_symlink_dir, ec);
+ if (ec) {
+ return Error() << "Failed to create directory " << library_symlink_dir
+ << ": " << ec.message();
+ }
+ }
+
+ auto inner_iter =
+ std::filesystem::directory_iterator(lib_entry.path().string());
+
+ while (inner_iter != std::filesystem::end(inner_iter)) {
+ const auto& lib_items = *inner_iter;
+ const auto hash_value = lib_items.path().filename();
+ const std::string library_symlink_hash = StringPrintf(
+ "%s/%s", library_symlink_dir.c_str(), hash_value.c_str());
+
+ auto hash_dir = PathExists(library_symlink_hash);
+ if (hash_dir.ok() && *hash_dir) {
+ // Compare file size for two library files with same name and hash
+ // value
+ auto existing_file_path =
+ library_symlink_hash + "/" + library_name.string();
+ auto existing_file_size = GetFileSize(existing_file_path);
+ if (!existing_file_size.ok()) {
+ return existing_file_size.error();
+ }
+
+ auto new_file_path =
+ lib_items.path().string() + "/" + library_name.string();
+ auto new_file_size = GetFileSize(new_file_path);
+ if (!new_file_size.ok()) {
+ return new_file_size.error();
+ }
+
+ if (*existing_file_size != *new_file_size) {
+ return Error() << "There are two libraries with same hash and "
+ "different file size : "
+ << existing_file_path << " and " << new_file_path;
+ }
+
+ inner_iter = inner_iter.increment(ec);
+ if (ec) {
+ return Error() << "Failed to scan " << lib_entry.path().string()
+ << " : " << ec.message();
+ }
+ continue;
+ }
+ std::filesystem::create_directory_symlink(lib_items.path(),
+ library_symlink_hash, ec);
+ if (ec) {
+ return Error() << "Failed to create symlink from " << lib_items.path()
+ << " to " << library_symlink_hash << ec.message();
+ }
+
+ inner_iter = inner_iter.increment(ec);
+ if (ec) {
+ return Error() << "Failed to scan " << lib_entry.path().string()
+ << " : " << ec.message();
+ }
+ }
+
+ iter = iter.increment(ec);
+ if (ec) {
+ return Error() << "Failed to scan " << apex_lib_path << " : "
+ << ec.message();
+ }
+ }
+ }
+
+ return {};
}
-Result<void> activatePackageImpl(const ApexFile& apex_file) {
+bool IsValidPackageName(const std::string& package_name) {
+ return kBannedApexName.count(package_name) == 0;
+}
+
+Result<void> ActivatePackageImpl(const ApexFile& apex_file,
+ const std::string& device_name) {
const ApexManifest& manifest = apex_file.GetManifest();
- if (gBootstrap && !isBootstrapApex(apex_file)) {
- return {};
+ if (!IsValidPackageName(manifest.name())) {
+ return Errorf("Package name {} is not allowed.", manifest.name());
+ }
+
+ // Validate upgraded shim apex
+ if (shim::IsShimApex(apex_file) &&
+ !ApexFileRepository::GetInstance().IsPreInstalledApex(apex_file)) {
+ // This is not cheap for shim apex, but it is fine here since we have
+ // upgraded shim apex only during CTS tests.
+ Result<void> result = VerifyPackageBoot(apex_file);
+ if (!result.ok()) {
+ LOG(ERROR) << "Failed to validate shim apex: " << apex_file.GetPath();
+ return result;
+ }
}
// See whether we think it's active, and do not allow to activate the same
// version. Also detect whether this is the highest version.
// We roll this into a single check.
bool is_newest_version = true;
- bool found_other_version = false;
bool version_found_mounted = false;
{
uint64_t new_version = manifest.version();
bool version_found_active = false;
gMountedApexes.ForallMountedApexes(
manifest.name(), [&](const MountedApexData& data, bool latest) {
- Result<ApexFile> otherApex = ApexFile::Open(data.full_path);
- if (!otherApex.ok()) {
+ Result<ApexFile> other_apex = ApexFile::Open(data.full_path);
+ if (!other_apex.ok()) {
return;
}
- found_other_version = true;
- if (static_cast<uint64_t>(otherApex->GetManifest().version()) ==
+ if (static_cast<uint64_t>(other_apex->GetManifest().version()) ==
new_version) {
version_found_mounted = true;
version_found_active = latest;
}
- if (static_cast<uint64_t>(otherApex->GetManifest().version()) >
+ if (static_cast<uint64_t>(other_apex->GetManifest().version()) >
new_version) {
is_newest_version = false;
}
});
- if (version_found_active) {
+ // If the package provides shared libraries to other APEXs, we need to
+ // activate all versions available (i.e. preloaded on /system/apex and
+ // available on /data/apex/active). The reason is that there might be some
+ // APEXs loaded from /system/apex that reference the libraries contained on
+ // the preloaded version of the apex providing shared libraries.
+ if (version_found_active && !manifest.providesharedapexlibs()) {
LOG(DEBUG) << "Package " << manifest.name() << " with version "
<< manifest.version() << " already active";
return {};
}
}
- const std::string& mountPoint = apexd_private::GetPackageMountPoint(manifest);
+ const std::string& mount_point =
+ apexd_private::GetPackageMountPoint(manifest);
if (!version_found_mounted) {
- auto mountStatus = MountPackage(apex_file, mountPoint);
- if (!mountStatus.ok()) {
- return mountStatus;
+ auto mount_status = MountPackage(apex_file, mount_point, device_name);
+ if (!mount_status.ok()) {
+ return mount_status;
}
}
- bool mounted_latest = false;
- if (is_newest_version) {
- const Result<void>& update_st = apexd_private::BindMount(
- apexd_private::GetActiveMountPoint(manifest), mountPoint);
- mounted_latest = update_st.has_value();
- if (!update_st.ok()) {
- return Error() << "Failed to update package " << manifest.name()
- << " to version " << manifest.version() << " : "
- << update_st.error();
+ // For packages providing shared libraries, avoid creating a bindmount since
+ // there is no use for the /apex/<package_name> directory. However, mark the
+ // highest version as latest so that the latest version of the package can be
+ // properly reported to PackageManager.
+ if (manifest.providesharedapexlibs()) {
+ if (is_newest_version) {
+ gMountedApexes.SetLatest(manifest.name(), apex_file.GetPath());
+ }
+ } else {
+ bool mounted_latest = false;
+ // Bind mount the latest version to /apex/<package_name>, unless the
+ // package provides shared libraries to other APEXs.
+ if (is_newest_version) {
+ const Result<void>& update_st = apexd_private::BindMount(
+ apexd_private::GetActiveMountPoint(manifest), mount_point);
+ mounted_latest = update_st.has_value();
+ if (!update_st.ok()) {
+ return Error() << "Failed to update package " << manifest.name()
+ << " to version " << manifest.version() << " : "
+ << update_st.error();
+ }
+ }
+ if (mounted_latest) {
+ gMountedApexes.SetLatest(manifest.name(), apex_file.GetPath());
}
}
- if (mounted_latest) {
- gMountedApexes.SetLatest(manifest.name(), apex_file.GetPath());
+
+ if (manifest.providesharedapexlibs()) {
+ const auto& handle_shared_libs_apex =
+ ActivateSharedLibsPackage(mount_point);
+ if (!handle_shared_libs_apex.ok()) {
+ return handle_shared_libs_apex;
+ }
}
LOG(DEBUG) << "Successfully activated " << apex_file.GetPath()
@@ -1013,28 +1335,30 @@ Result<void> activatePackageImpl(const ApexFile& apex_file) {
return {};
}
-Result<void> activatePackage(const std::string& full_path) {
+Result<void> ActivatePackage(const std::string& full_path) {
LOG(INFO) << "Trying to activate " << full_path;
Result<ApexFile> apex_file = ApexFile::Open(full_path);
if (!apex_file.ok()) {
return apex_file.error();
}
- return activatePackageImpl(*apex_file);
+ return ActivatePackageImpl(*apex_file,
+ GetPackageId(apex_file->GetManifest()));
}
-Result<void> deactivatePackage(const std::string& full_path) {
+Result<void> DeactivatePackage(const std::string& full_path) {
LOG(INFO) << "Trying to deactivate " << full_path;
- Result<ApexFile> apexFile = ApexFile::Open(full_path);
- if (!apexFile.ok()) {
- return apexFile.error();
+ Result<ApexFile> apex_file = ApexFile::Open(full_path);
+ if (!apex_file.ok()) {
+ return apex_file.error();
}
- return UnmountPackage(*apexFile, /* allow_latest= */ true);
+ return UnmountPackage(*apex_file, /* allow_latest= */ true,
+ /* deferred= */ false);
}
-std::vector<ApexFile> getActivePackages() {
+std::vector<ApexFile> GetActivePackages() {
std::vector<ApexFile> ret;
gMountedApexes.ForallMountedApexes(
[&](const std::string&, const MountedApexData& data, bool latest) {
@@ -1042,20 +1366,84 @@ std::vector<ApexFile> getActivePackages() {
return;
}
- Result<ApexFile> apexFile = ApexFile::Open(data.full_path);
- if (!apexFile.ok()) {
- // TODO: Fail?
+ Result<ApexFile> apex_file = ApexFile::Open(data.full_path);
+ if (!apex_file.ok()) {
return;
}
- ret.emplace_back(std::move(*apexFile));
+ ret.emplace_back(std::move(*apex_file));
});
return ret;
}
+std::vector<ApexFile> CalculateInactivePackages(
+ const std::vector<ApexFile>& active) {
+ std::vector<ApexFile> inactive = GetFactoryPackages();
+ auto new_end = std::remove_if(
+ inactive.begin(), inactive.end(), [&active](const ApexFile& apex) {
+ return std::any_of(active.begin(), active.end(),
+ [&apex](const ApexFile& active_apex) {
+ return apex.GetPath() == active_apex.GetPath();
+ });
+ });
+ inactive.erase(new_end, inactive.end());
+ return std::move(inactive);
+}
+
+Result<void> EmitApexInfoList(bool is_bootstrap) {
+ // on a non-updatable device, we don't have APEX database to emit
+ if (!android::sysprop::ApexProperties::updatable().value_or(false)) {
+ return {};
+ }
+
+ // Apexd runs both in "bootstrap" and "default" mount namespace.
+ // To expose /apex/apex-info-list.xml separately in each mount namespaces,
+ // we write /apex/.<namespace>-apex-info-list .xml file first and then
+ // bind mount it to the canonical file (/apex/apex-info-list.xml).
+ const std::string file_name =
+ fmt::format("{}/.{}-{}", kApexRoot,
+ is_bootstrap ? "bootstrap" : "default", kApexInfoList);
+
+ unique_fd fd(TEMP_FAILURE_RETRY(
+ open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)));
+ if (fd.get() == -1) {
+ return ErrnoErrorf("Can't open {}", file_name);
+ }
+
+ const std::vector<ApexFile> active(GetActivePackages());
+
+ std::vector<ApexFile> inactive;
+ // we skip for non-activated built-in apexes in bootstrap mode
+ // in order to avoid boottime increase
+ if (!is_bootstrap) {
+ inactive = CalculateInactivePackages(active);
+ }
+
+ std::stringstream xml;
+ CollectApexInfoList(xml, active, inactive);
+
+ if (!android::base::WriteStringToFd(xml.str(), fd)) {
+ return ErrnoErrorf("Can't write to {}", file_name);
+ }
+
+ fd.reset();
+
+ const std::string mount_point =
+ fmt::format("{}/{}", kApexRoot, kApexInfoList);
+ if (access(mount_point.c_str(), F_OK) != 0) {
+ close(open(mount_point.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+ 0644));
+ }
+ if (mount(file_name.c_str(), mount_point.c_str(), nullptr, MS_BIND,
+ nullptr) == -1) {
+ return ErrnoErrorf("Can't bind mount {} to {}", file_name, mount_point);
+ }
+ return RestoreconPath(file_name);
+}
+
namespace {
std::unordered_map<std::string, uint64_t> GetActivePackagesMap() {
- std::vector<ApexFile> active_packages = getActivePackages();
+ std::vector<ApexFile> active_packages = GetActivePackages();
std::unordered_map<std::string, uint64_t> ret;
for (const auto& package : active_packages) {
const ApexManifest& manifest = package.GetManifest();
@@ -1066,28 +1454,50 @@ std::unordered_map<std::string, uint64_t> GetActivePackagesMap() {
} // namespace
-std::vector<ApexFile> getFactoryPackages() {
+std::vector<ApexFile> GetFactoryPackages() {
std::vector<ApexFile> ret;
- for (const auto& dir : kApexPackageBuiltinDirs) {
- auto apex_files = FindApexFilesByName(dir);
- if (!apex_files.ok()) {
- LOG(ERROR) << apex_files.error();
+
+ // Decompressed APEX is considered factory package
+ std::vector<std::string> decompressed_pkg_names;
+ auto active_pkgs = GetActivePackages();
+ for (ApexFile& apex : active_pkgs) {
+ if (ApexFileRepository::GetInstance().IsDecompressedApex(apex)) {
+ decompressed_pkg_names.push_back(apex.GetManifest().name());
+ ret.emplace_back(std::move(apex));
+ }
+ }
+
+ for (const auto& dir : gConfig->apex_built_in_dirs) {
+ auto all_apex_files = FindFilesBySuffix(
+ dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
+ if (!all_apex_files.ok()) {
+ LOG(ERROR) << all_apex_files.error();
continue;
}
- for (const std::string& path : *apex_files) {
+
+ for (const std::string& path : *all_apex_files) {
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
LOG(ERROR) << apex_file.error();
- } else {
- ret.emplace_back(std::move(*apex_file));
+ continue;
+ }
+ // Ignore compressed APEX if it has been decompressed already
+ if (apex_file->IsCompressed() &&
+ std::find(decompressed_pkg_names.begin(),
+ decompressed_pkg_names.end(),
+ apex_file->GetManifest().name()) !=
+ decompressed_pkg_names.end()) {
+ continue;
}
+
+ ret.emplace_back(std::move(*apex_file));
}
}
return ret;
}
-Result<ApexFile> getActivePackage(const std::string& packageName) {
- std::vector<ApexFile> packages = getActivePackages();
+Result<ApexFile> GetActivePackage(const std::string& packageName) {
+ std::vector<ApexFile> packages = GetActivePackages();
for (ApexFile& apex : packages) {
if (apex.GetManifest().name() == packageName) {
return std::move(apex);
@@ -1102,7 +1512,7 @@ Result<ApexFile> getActivePackage(const std::string& packageName) {
*
* Returns without error only if session was successfully aborted.
**/
-Result<void> abortStagedSession(int session_id) {
+Result<void> AbortStagedSession(int session_id) {
auto session = ApexSession::GetSession(session_id);
if (!session.ok()) {
return Error() << "No session found with id " << session_id;
@@ -1117,23 +1527,29 @@ Result<void> abortStagedSession(int session_id) {
}
}
-// TODO(ioffe): cleanup activation logic to avoid unnecessary scanning.
namespace {
-Result<std::vector<ApexFile>> ScanApexFiles(const char* apex_package_dir) {
+// TODO(b/179497746): Avoid scanning apex directly here
+// Only used in OnBootstrap. Should we remove this function?
+Result<std::vector<ApexFile>> ScanApexFiles(const char* apex_package_dir,
+ bool include_compressed = false) {
LOG(INFO) << "Scanning " << apex_package_dir << " looking for APEX packages.";
if (access(apex_package_dir, F_OK) != 0 && errno == ENOENT) {
LOG(INFO) << "... does not exist. Skipping";
return {};
}
- Result<std::vector<std::string>> scan = FindApexFilesByName(apex_package_dir);
+ std::vector<std::string> suffix_list = {kApexPackageSuffix};
+ if (include_compressed) {
+ suffix_list.push_back(kCompressedApexPackageSuffix);
+ }
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(apex_package_dir, suffix_list);
if (!scan.ok()) {
return Error() << "Failed to scan " << apex_package_dir << " : "
<< scan.error();
}
std::vector<ApexFile> ret;
for (const auto& name : *scan) {
- LOG(INFO) << "Found " << name;
Result<ApexFile> apex_file = ApexFile::Open(name);
if (!apex_file.ok()) {
LOG(ERROR) << "Failed to scan " << name << " : " << apex_file.error();
@@ -1144,64 +1560,152 @@ Result<std::vector<ApexFile>> ScanApexFiles(const char* apex_package_dir) {
return ret;
}
-Result<void> ActivateApexPackages(const std::vector<ApexFile>& apexes) {
- const auto& packages_with_code = GetActivePackagesMap();
- size_t failed_cnt = 0;
- size_t skipped_cnt = 0;
- size_t activated_cnt = 0;
- for (const auto& apex : apexes) {
- uint64_t new_version = static_cast<uint64_t>(apex.GetManifest().version());
- const auto& it = packages_with_code.find(apex.GetManifest().name());
- if (it != packages_with_code.end() && it->second >= new_version) {
- LOG(INFO) << "Skipping activation of " << apex.GetPath()
- << " same package with higher version " << it->second
- << " is already active";
- skipped_cnt++;
- continue;
+std::vector<Result<void>> ActivateApexWorker(
+ bool is_ota_chroot, std::queue<const ApexFile*>& apex_queue,
+ std::mutex& mutex) {
+ std::vector<Result<void>> ret;
+
+ while (true) {
+ const ApexFile* apex;
+ {
+ std::lock_guard lock(mutex);
+ if (apex_queue.empty()) break;
+ apex = apex_queue.front();
+ apex_queue.pop();
}
- if (auto res = activatePackageImpl(apex); !res.ok()) {
- LOG(ERROR) << "Failed to activate " << apex.GetPath() << " : "
- << res.error();
- failed_cnt++;
+ std::string device_name = GetPackageId(apex->GetManifest());
+ if (is_ota_chroot) {
+ device_name += ".chroot";
+ }
+ if (auto res = ActivatePackageImpl(*apex, device_name); !res.ok()) {
+ ret.push_back(Error() << "Failed to activate " << apex->GetPath() << " : "
+ << res.error());
} else {
- activated_cnt++;
+ ret.push_back({});
}
}
+
+ return ret;
+}
+
+Result<void> ActivateApexPackages(const std::vector<ApexFileRef>& apexes,
+ bool is_ota_chroot) {
+ std::queue<const ApexFile*> apex_queue;
+ std::mutex apex_queue_mutex;
+
+ for (const ApexFile& apex : apexes) {
+ apex_queue.emplace(&apex);
+ }
+
+ // Creates threads as many as half number of cores for the performance.
+ size_t worker_num = std::max(get_nprocs_conf() >> 1, 1);
+ worker_num = std::min(apex_queue.size(), worker_num);
+
+ // On -eng builds there might be two different pre-installed art apexes.
+ // Attempting to activate them in parallel will result in UB (e.g.
+ // apexd-bootstrap might crash). In order to avoid this, for the time being on
+ // -eng builds activate apexes sequentially.
+ // TODO(b/176497601): remove this.
+ if (GetProperty("ro.build.type", "") == "eng") {
+ worker_num = 1;
+ }
+
+ std::vector<std::future<std::vector<Result<void>>>> futures;
+ futures.reserve(worker_num);
+ for (size_t i = 0; i < worker_num; i++) {
+ futures.push_back(std::async(std::launch::async, ActivateApexWorker,
+ std::ref(is_ota_chroot), std::ref(apex_queue),
+ std::ref(apex_queue_mutex)));
+ }
+
+ size_t activated_cnt = 0;
+ size_t failed_cnt = 0;
+ std::string error_message;
+ for (size_t i = 0; i < futures.size(); i++) {
+ for (const auto& res : futures[i].get()) {
+ if (res.ok()) {
+ ++activated_cnt;
+ } else {
+ ++failed_cnt;
+ LOG(ERROR) << res.error();
+ if (failed_cnt == 1) {
+ error_message = res.error().message();
+ }
+ }
+ }
+ }
+
if (failed_cnt > 0) {
- return Error() << "Failed to activate " << failed_cnt << " APEX packages";
+ return Error() << "Failed to activate " << failed_cnt
+ << " APEX packages. One of the errors: " << error_message;
}
- LOG(INFO) << "Activated " << activated_cnt
- << " packages. Skipped: " << skipped_cnt;
+ LOG(INFO) << "Activated " << activated_cnt << " packages.";
return {};
}
-bool ShouldActivateApexOnData(const ApexFile& apex) {
- return HasPreInstalledVersion(apex.GetManifest().name());
-}
-
-} // namespace
+// A fallback function in case some of the apexes failed to activate. For all
+// such apexes that were coming from /data partition we will attempt to activate
+// their corresponding pre-installed copies.
+Result<void> ActivateMissingApexes(const std::vector<ApexFileRef>& apexes,
+ bool is_ota_chroot) {
+ LOG(INFO) << "Trying to activate pre-installed versions of missing apexes";
+ const auto& file_repository = ApexFileRepository::GetInstance();
+ const auto& activated_apexes = GetActivePackagesMap();
+ std::vector<ApexFileRef> fallback_apexes;
+ for (const auto& apex_ref : apexes) {
+ const auto& apex = apex_ref.get();
+ if (apex.GetManifest().providesharedapexlibs()) {
+ // We must mount both versions of sharedlibs apex anyway. Not much we can
+ // do here.
+ continue;
+ }
+ if (file_repository.IsPreInstalledApex(apex)) {
+ // We tried to activate pre-installed apex in the first place. No need to
+ // try again.
+ continue;
+ }
+ const std::string& name = apex.GetManifest().name();
+ if (activated_apexes.find(name) == activated_apexes.end()) {
+ fallback_apexes.push_back(file_repository.GetPreInstalledApex(name));
+ }
+ }
-Result<void> scanPackagesDirAndActivate(const char* apex_package_dir) {
- auto apexes = ScanApexFiles(apex_package_dir);
- if (!apexes) {
- return apexes.error();
+ // Process compressed APEX, if any
+ std::vector<ApexFileRef> compressed_apex;
+ for (auto it = fallback_apexes.begin(); it != fallback_apexes.end();) {
+ if (it->get().IsCompressed()) {
+ compressed_apex.emplace_back(*it);
+ it = fallback_apexes.erase(it);
+ } else {
+ it++;
+ }
+ }
+ std::vector<ApexFile> decompressed_apex;
+ if (!compressed_apex.empty()) {
+ decompressed_apex =
+ ProcessCompressedApex(compressed_apex, /* is_ota_chroot= */ false);
+ for (const ApexFile& apex_file : decompressed_apex) {
+ fallback_apexes.emplace_back(std::cref(apex_file));
+ }
}
- return ActivateApexPackages(*apexes);
+ return ActivateApexPackages(fallback_apexes, is_ota_chroot);
}
+} // namespace
+
/**
* Snapshots data from base_dir/apexdata/<apex name> to
* base_dir/apexrollback/<rollback id>/<apex name>.
*/
-Result<void> snapshotDataDirectory(const std::string& base_dir,
+Result<void> SnapshotDataDirectory(const std::string& base_dir,
const int rollback_id,
const std::string& apex_name,
bool pre_restore = false) {
auto rollback_path =
StringPrintf("%s/%s/%d%s", base_dir.c_str(), kApexSnapshotSubDir,
rollback_id, pre_restore ? kPreRestoreSuffix : "");
- const Result<void> result = createDirIfNeeded(rollback_path, 0700);
+ const Result<void> result = CreateDirIfNeeded(rollback_path, 0700);
if (!result.ok()) {
return Error() << "Failed to create snapshot directory for rollback "
<< rollback_id << " : " << result.error();
@@ -1219,7 +1723,7 @@ Result<void> snapshotDataDirectory(const std::string& base_dir,
* to base_dir/apexdata/<apex name>.
* Note the snapshot will be deleted after restoration succeeded.
*/
-Result<void> restoreDataDirectory(const std::string& base_dir,
+Result<void> RestoreDataDirectory(const std::string& base_dir,
const int rollback_id,
const std::string& apex_name,
bool pre_restore = false) {
@@ -1243,13 +1747,13 @@ Result<void> restoreDataDirectory(const std::string& base_dir,
return {};
}
-void snapshotOrRestoreDeIfNeeded(const std::string& base_dir,
+void SnapshotOrRestoreDeIfNeeded(const std::string& base_dir,
const ApexSession& session) {
if (session.HasRollbackEnabled()) {
for (const auto& apex_name : session.GetApexNames()) {
Result<void> result =
- snapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name);
- if (!result) {
+ SnapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name);
+ if (!result.ok()) {
LOG(ERROR) << "Snapshot failed for " << apex_name << ": "
<< result.error();
}
@@ -1258,11 +1762,11 @@ void snapshotOrRestoreDeIfNeeded(const std::string& base_dir,
for (const auto& apex_name : session.GetApexNames()) {
if (!gInFsCheckpointMode) {
// Snapshot before restore so this rollback can be reverted.
- snapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name,
+ SnapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name,
true /* pre_restore */);
}
Result<void> result =
- restoreDataDirectory(base_dir, session.GetRollbackId(), apex_name);
+ RestoreDataDirectory(base_dir, session.GetRollbackId(), apex_name);
if (!result.ok()) {
LOG(ERROR) << "Restore of data failed for " << apex_name << ": "
<< result.error();
@@ -1271,18 +1775,18 @@ void snapshotOrRestoreDeIfNeeded(const std::string& base_dir,
}
}
-void snapshotOrRestoreDeSysData() {
+void SnapshotOrRestoreDeSysData() {
auto sessions = ApexSession::GetSessionsInState(SessionState::ACTIVATED);
for (const ApexSession& session : sessions) {
- snapshotOrRestoreDeIfNeeded(kDeSysDataDir, session);
+ SnapshotOrRestoreDeIfNeeded(kDeSysDataDir, session);
}
}
-int snapshotOrRestoreDeUserData() {
+int SnapshotOrRestoreDeUserData() {
auto user_dirs = GetDeUserDirs();
- if (!user_dirs) {
+ if (!user_dirs.ok()) {
LOG(ERROR) << "Error reading dirs " << user_dirs.error();
return 1;
}
@@ -1291,93 +1795,69 @@ int snapshotOrRestoreDeUserData() {
for (const ApexSession& session : sessions) {
for (const auto& user_dir : *user_dirs) {
- snapshotOrRestoreDeIfNeeded(user_dir, session);
+ SnapshotOrRestoreDeIfNeeded(user_dir, session);
}
}
return 0;
}
-Result<ino_t> snapshotCeData(const int user_id, const int rollback_id,
- const std::string& apex_name) {
+Result<void> SnapshotCeData(const int user_id, const int rollback_id,
+ const std::string& apex_name) {
auto base_dir = StringPrintf("%s/%d", kCeDataDir, user_id);
- Result<void> result = snapshotDataDirectory(base_dir, rollback_id, apex_name);
- if (!result) {
- return result.error();
- }
- auto ce_snapshot_path =
- StringPrintf("%s/%s/%d/%s", base_dir.c_str(), kApexSnapshotSubDir,
- rollback_id, apex_name.c_str());
- return get_path_inode(ce_snapshot_path);
+ return SnapshotDataDirectory(base_dir, rollback_id, apex_name);
}
-Result<void> restoreCeData(const int user_id, const int rollback_id,
+Result<void> RestoreCeData(const int user_id, const int rollback_id,
const std::string& apex_name) {
auto base_dir = StringPrintf("%s/%d", kCeDataDir, user_id);
- return restoreDataDirectory(base_dir, rollback_id, apex_name);
+ return RestoreDataDirectory(base_dir, rollback_id, apex_name);
}
// Migrates sessions directory from /data/apex/sessions to
// /metadata/apex/sessions, if necessary.
-Result<void> migrateSessionsDirIfNeeded() {
- namespace fs = std::filesystem;
- auto from_path = std::string(kApexDataDir) + "/sessions";
- auto exists = PathExists(from_path);
- if (!exists) {
- return Error() << "Failed to access " << from_path << ": "
- << exists.error();
- }
- if (!*exists) {
- LOG(DEBUG) << from_path << " does not exist. Nothing to migrate.";
- return {};
- }
- auto to_path = kApexSessionsDir;
- std::error_code error_code;
- fs::copy(from_path, to_path, fs::copy_options::recursive, error_code);
- if (error_code) {
- return Error() << "Failed to copy old sessions directory"
- << error_code.message();
- }
- fs::remove_all(from_path, error_code);
- if (error_code) {
- return Error() << "Failed to delete old sessions directory "
- << error_code.message();
- }
- return {};
+Result<void> MigrateSessionsDirIfNeeded() {
+ return ApexSession::MigrateToMetadataSessionsDir();
}
-Result<void> destroySnapshots(const std::string& base_dir,
+Result<void> DestroySnapshots(const std::string& base_dir,
const int rollback_id) {
auto path = StringPrintf("%s/%s/%d", base_dir.c_str(), kApexSnapshotSubDir,
rollback_id);
return DeleteDir(path);
}
-Result<void> destroyDeSnapshots(const int rollback_id) {
- destroySnapshots(kDeSysDataDir, rollback_id);
+Result<void> DestroyDeSnapshots(const int rollback_id) {
+ DestroySnapshots(kDeSysDataDir, rollback_id);
auto user_dirs = GetDeUserDirs();
- if (!user_dirs) {
+ if (!user_dirs.ok()) {
return Error() << "Error reading user dirs " << user_dirs.error();
}
for (const auto& user_dir : *user_dirs) {
- destroySnapshots(user_dir, rollback_id);
+ DestroySnapshots(user_dir, rollback_id);
}
return {};
}
+Result<void> DestroyCeSnapshots(const int user_id, const int rollback_id) {
+ auto path = StringPrintf("%s/%d/%s/%d", kCeDataDir, user_id,
+ kApexSnapshotSubDir, rollback_id);
+ return DeleteDir(path);
+}
+
/**
* Deletes all credential-encrypted snapshots for the given user, except for
* those listed in retain_rollback_ids.
*/
-Result<void> destroyCeSnapshotsNotSpecified(
+Result<void> DestroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids) {
auto snapshot_root =
StringPrintf("%s/%d/%s", kCeDataDir, user_id, kApexSnapshotSubDir);
auto snapshot_dirs = GetSubdirs(snapshot_root);
- if (!snapshot_dirs) {
+ if (!snapshot_dirs.ok()) {
return Error() << "Error reading snapshot dirs " << snapshot_dirs.error();
}
@@ -1389,7 +1869,7 @@ Result<void> destroyCeSnapshotsNotSpecified(
std::find(retain_rollback_ids.begin(), retain_rollback_ids.end(),
snapshot_id) == retain_rollback_ids.end()) {
Result<void> result = DeleteDir(snapshot_dir);
- if (!result) {
+ if (!result.ok()) {
return Error() << "Destroy CE snapshot failed for " << snapshot_dir
<< " : " << result.error();
}
@@ -1398,16 +1878,16 @@ Result<void> destroyCeSnapshotsNotSpecified(
return {};
}
-void restorePreRestoreSnapshotsIfPresent(const std::string& base_dir,
+void RestorePreRestoreSnapshotsIfPresent(const std::string& base_dir,
const ApexSession& session) {
auto pre_restore_snapshot_path =
StringPrintf("%s/%s/%d%s", base_dir.c_str(), kApexSnapshotSubDir,
session.GetRollbackId(), kPreRestoreSuffix);
- if (PathExists(pre_restore_snapshot_path)) {
+ if (PathExists(pre_restore_snapshot_path).ok()) {
for (const auto& apex_name : session.GetApexNames()) {
- Result<void> result = restoreDataDirectory(
+ Result<void> result = RestoreDataDirectory(
base_dir, session.GetRollbackId(), apex_name, true /* pre_restore */);
- if (!result) {
+ if (!result.ok()) {
LOG(ERROR) << "Restore of pre-restore snapshot failed for " << apex_name
<< ": " << result.error();
}
@@ -1415,71 +1895,77 @@ void restorePreRestoreSnapshotsIfPresent(const std::string& base_dir,
}
}
-void restoreDePreRestoreSnapshotsIfPresent(const ApexSession& session) {
- restorePreRestoreSnapshotsIfPresent(kDeSysDataDir, session);
+void RestoreDePreRestoreSnapshotsIfPresent(const ApexSession& session) {
+ RestorePreRestoreSnapshotsIfPresent(kDeSysDataDir, session);
auto user_dirs = GetDeUserDirs();
- if (!user_dirs) {
+ if (!user_dirs.ok()) {
LOG(ERROR) << "Error reading user dirs to restore pre-restore snapshots"
<< user_dirs.error();
}
for (const auto& user_dir : *user_dirs) {
- restorePreRestoreSnapshotsIfPresent(user_dir, session);
+ RestorePreRestoreSnapshotsIfPresent(user_dir, session);
}
}
-void deleteDePreRestoreSnapshots(const std::string& base_dir,
+void DeleteDePreRestoreSnapshots(const std::string& base_dir,
const ApexSession& session) {
auto pre_restore_snapshot_path =
StringPrintf("%s/%s/%d%s", base_dir.c_str(), kApexSnapshotSubDir,
session.GetRollbackId(), kPreRestoreSuffix);
Result<void> result = DeleteDir(pre_restore_snapshot_path);
- if (!result) {
+ if (!result.ok()) {
LOG(ERROR) << "Deletion of pre-restore snapshot failed: " << result.error();
}
}
-void deleteDePreRestoreSnapshots(const ApexSession& session) {
- deleteDePreRestoreSnapshots(kDeSysDataDir, session);
+void DeleteDePreRestoreSnapshots(const ApexSession& session) {
+ DeleteDePreRestoreSnapshots(kDeSysDataDir, session);
auto user_dirs = GetDeUserDirs();
- if (!user_dirs) {
+ if (!user_dirs.ok()) {
LOG(ERROR) << "Error reading user dirs to delete pre-restore snapshots"
<< user_dirs.error();
}
for (const auto& user_dir : *user_dirs) {
- deleteDePreRestoreSnapshots(user_dir, session);
+ DeleteDePreRestoreSnapshots(user_dir, session);
}
}
-void scanStagedSessionsDirAndStage() {
- LOG(INFO) << "Scanning " << kApexSessionsDir
+void OnBootCompleted() {
+ ApexdLifecycle::GetInstance().MarkBootCompleted();
+ BootCompletedCleanup();
+}
+
+// Returns true if any session gets staged
+void ScanStagedSessionsDirAndStage() {
+ LOG(INFO) << "Scanning " << ApexSession::GetSessionsDir()
<< " looking for sessions to be activated.";
- auto sessionsToActivate =
+ auto sessions_to_activate =
ApexSession::GetSessionsInState(SessionState::STAGED);
if (gSupportsFsCheckpoints) {
// A session that is in the ACTIVATED state should still be re-activated if
// fs checkpointing is supported. In this case, a session may be in the
// ACTIVATED state yet the data/apex/active directory may have been
// reverted. The session should be reverted in this scenario.
- auto activatedSessions =
+ auto activated_sessions =
ApexSession::GetSessionsInState(SessionState::ACTIVATED);
- sessionsToActivate.insert(sessionsToActivate.end(),
- activatedSessions.begin(),
- activatedSessions.end());
+ sessions_to_activate.insert(sessions_to_activate.end(),
+ activated_sessions.begin(),
+ activated_sessions.end());
}
- for (auto& session : sessionsToActivate) {
- auto sessionId = session.GetId();
+ for (auto& session : sessions_to_activate) {
+ auto session_id = session.GetId();
auto session_failed_fn = [&]() {
- LOG(WARNING) << "Marking session " << sessionId << " as failed.";
+ LOG(WARNING) << "Marking session " << session_id << " as failed.";
auto st = session.UpdateStateAndCommit(SessionState::ACTIVATION_FAILED);
if (!st.ok()) {
- LOG(WARNING) << "Failed to mark session " << sessionId
+ LOG(WARNING) << "Failed to mark session " << session_id
<< " as failed : " << st.error();
}
};
@@ -1487,74 +1973,90 @@ void scanStagedSessionsDirAndStage() {
std::string build_fingerprint = GetProperty(kBuildFingerprintSysprop, "");
if (session.GetBuildFingerprint().compare(build_fingerprint) != 0) {
- LOG(ERROR) << "APEX build fingerprint has changed";
+ auto error_message = "APEX build fingerprint has changed";
+ LOG(ERROR) << error_message;
+ session.SetErrorMessage(error_message);
continue;
}
- std::vector<std::string> dirsToScan;
+ std::vector<std::string> dirs_to_scan;
if (session.GetChildSessionIds().empty()) {
- dirsToScan.push_back(std::string(kStagedSessionsDir) + "/session_" +
- std::to_string(sessionId));
+ dirs_to_scan.push_back(std::string(gConfig->staged_session_dir) +
+ "/session_" + std::to_string(session_id));
} else {
- for (auto childSessionId : session.GetChildSessionIds()) {
- dirsToScan.push_back(std::string(kStagedSessionsDir) + "/session_" +
- std::to_string(childSessionId));
+ for (auto child_session_id : session.GetChildSessionIds()) {
+ dirs_to_scan.push_back(std::string(gConfig->staged_session_dir) +
+ "/session_" + std::to_string(child_session_id));
}
}
std::vector<std::string> apexes;
- bool scanSuccessful = true;
- for (const auto& dirToScan : dirsToScan) {
- Result<std::vector<std::string>> scan = FindApexFilesByName(dirToScan);
+ bool scan_successful = true;
+ for (const auto& dir_to_scan : dirs_to_scan) {
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(dir_to_scan, {kApexPackageSuffix});
if (!scan.ok()) {
LOG(WARNING) << scan.error();
- scanSuccessful = false;
+ session.SetErrorMessage(scan.error().message());
+ scan_successful = false;
break;
}
if (scan->size() > 1) {
- LOG(WARNING) << "More than one APEX package found in the same session "
- << "directory " << dirToScan << ", skipping activation.";
- scanSuccessful = false;
+ std::string error_message = StringPrintf(
+ "More than one APEX package found in the same session directory %s "
+ ", skipping activation",
+ dir_to_scan.c_str());
+ LOG(WARNING) << error_message;
+ session.SetErrorMessage(error_message);
+ scan_successful = false;
break;
}
if (scan->empty()) {
- LOG(WARNING) << "No APEX packages found while scanning " << dirToScan
- << " session id: " << sessionId << ".";
- scanSuccessful = false;
+ std::string error_message = StringPrintf(
+ "No APEX packages found while scanning %s session id: %d.",
+ dir_to_scan.c_str(), session_id);
+ LOG(WARNING) << error_message;
+ session.SetErrorMessage(error_message);
+ scan_successful = false;
break;
}
apexes.push_back(std::move((*scan)[0]));
}
- if (!scanSuccessful) {
+ if (!scan_successful) {
continue;
}
// Run postinstall, if necessary.
- Result<void> postinstall_status = postinstallPackages(apexes);
+ Result<void> postinstall_status = PostinstallPackages(apexes);
if (!postinstall_status.ok()) {
- LOG(ERROR) << "Postinstall failed for session "
- << std::to_string(sessionId) << ": "
- << postinstall_status.error();
+ std::string error_message =
+ StringPrintf("Postinstall failed for session %d %s", session_id,
+ postinstall_status.error().message().c_str());
+ LOG(ERROR) << error_message;
+ session.SetErrorMessage(error_message);
continue;
}
for (const auto& apex : apexes) {
- // TODO: Avoid opening ApexFile repeatedly.
+ // TODO(b/158470836): Avoid opening ApexFile repeatedly.
Result<ApexFile> apex_file = ApexFile::Open(apex);
- if (!apex_file) {
+ if (!apex_file.ok()) {
LOG(ERROR) << "Cannot open apex file during staging: " << apex;
continue;
}
session.AddApexName(apex_file->GetManifest().name());
}
- const Result<void> result = stagePackages(apexes);
+ const Result<void> result = StagePackages(apexes);
if (!result.ok()) {
- LOG(ERROR) << "Activation failed for packages " << Join(apexes, ',')
- << ": " << result.error();
+ std::string error_message = StringPrintf(
+ "Activation failed for packages %s : %s", Join(apexes, ',').c_str(),
+ result.error().message().c_str());
+ LOG(ERROR) << error_message;
+ session.SetErrorMessage(error_message);
continue;
}
@@ -1569,49 +2071,61 @@ void scanStagedSessionsDirAndStage() {
}
}
-Result<void> preinstallPackages(const std::vector<std::string>& paths) {
- if (paths.empty()) {
- return Errorf("Empty set of inputs");
+Result<void> PreinstallPackages(const std::vector<std::string>& paths) {
+ Result<std::vector<ApexFile>> apex_files = OpenApexFiles(paths);
+ if (!apex_files.ok()) {
+ return apex_files.error();
}
- LOG(DEBUG) << "preinstallPackages() for " << Join(paths, ',');
- return HandlePackages<Result<void>>(paths, PreinstallPackages);
+ LOG(DEBUG) << "PreinstallPackages() for " << Join(paths, ',');
+ return PreinstallPackages(*apex_files);
}
-Result<void> postinstallPackages(const std::vector<std::string>& paths) {
- if (paths.empty()) {
- return Errorf("Empty set of inputs");
+Result<void> PostinstallPackages(const std::vector<std::string>& paths) {
+ Result<std::vector<ApexFile>> apex_files = OpenApexFiles(paths);
+ if (!apex_files.ok()) {
+ return apex_files.error();
}
- LOG(DEBUG) << "postinstallPackages() for " << Join(paths, ',');
- return HandlePackages<Result<void>>(paths, PostinstallPackages);
+ LOG(DEBUG) << "PostinstallPackages() for " << Join(paths, ',');
+ return PostinstallPackages(*apex_files);
}
namespace {
std::string StageDestPath(const ApexFile& apex_file) {
- return StringPrintf("%s/%s%s", kActiveApexPackagesDataDir,
+ return StringPrintf("%s/%s%s", gConfig->active_apex_data_dir,
GetPackageId(apex_file.GetManifest()).c_str(),
kApexPackageSuffix);
}
} // namespace
-Result<void> stagePackages(const std::vector<std::string>& tmpPaths) {
- if (tmpPaths.empty()) {
+Result<void> StagePackages(const std::vector<std::string>& tmp_paths) {
+ if (tmp_paths.empty()) {
return Errorf("Empty set of inputs");
}
- LOG(DEBUG) << "stagePackages() for " << Join(tmpPaths, ',');
+ LOG(DEBUG) << "StagePackages() for " << Join(tmp_paths, ',');
// Note: this function is temporary. As such the code is not optimized, e.g.,
// it will open ApexFiles multiple times.
// 1) Verify all packages.
- auto verify_status = verifyPackages(tmpPaths, VerifyPackageBoot);
- if (!verify_status.ok()) {
- return verify_status.error();
+ Result<std::vector<ApexFile>> apex_files = OpenApexFiles(tmp_paths);
+ if (!apex_files.ok()) {
+ return apex_files.error();
+ }
+ for (const ApexFile& apex_file : *apex_files) {
+ if (shim::IsShimApex(apex_file)) {
+ // Shim apex will be validated on every boot. No need to do it here.
+ continue;
+ }
+ Result<void> result = VerifyPackageBoot(apex_file);
+ if (!result.ok()) {
+ return result.error();
+ }
}
// Make sure that kActiveApexPackagesDataDir exists.
auto create_dir_status =
- createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755);
+ CreateDirIfNeeded(std::string(gConfig->active_apex_data_dir), 0755);
if (!create_dir_status.ok()) {
return create_dir_status.error();
}
@@ -1636,16 +2150,12 @@ Result<void> stagePackages(const std::vector<std::string>& tmpPaths) {
auto scope_guard = android::base::make_scope_guard(deleter);
std::unordered_set<std::string> staged_packages;
- for (const std::string& path : tmpPaths) {
- Result<ApexFile> apex_file = ApexFile::Open(path);
- if (!apex_file.ok()) {
- return apex_file.error();
- }
+ for (const ApexFile& apex_file : *apex_files) {
// First promote new hashtree file to the one that will be used when
// mounting apex.
- std::string new_hashtree_file = GetHashTreeFileName(*apex_file,
+ std::string new_hashtree_file = GetHashTreeFileName(apex_file,
/* is_new = */ true);
- std::string old_hashtree_file = GetHashTreeFileName(*apex_file,
+ std::string old_hashtree_file = GetHashTreeFileName(apex_file,
/* is_new = */ false);
if (access(new_hashtree_file.c_str(), F_OK) == 0) {
if (TEMP_FAILURE_RETRY(rename(new_hashtree_file.c_str(),
@@ -1656,7 +2166,7 @@ Result<void> stagePackages(const std::vector<std::string>& tmpPaths) {
changed_hashtree_files.emplace_back(std::move(old_hashtree_file));
}
// And only then move apex to /data/apex/active.
- std::string dest_path = StageDestPath(*apex_file);
+ std::string dest_path = StageDestPath(apex_file);
if (access(dest_path.c_str(), F_OK) == 0) {
LOG(DEBUG) << dest_path << " already exists. Deleting";
if (TEMP_FAILURE_RETRY(unlink(dest_path.c_str())) != 0) {
@@ -1664,15 +2174,14 @@ Result<void> stagePackages(const std::vector<std::string>& tmpPaths) {
}
}
- if (link(apex_file->GetPath().c_str(), dest_path.c_str()) != 0) {
- // TODO: Get correct binder error status.
- return ErrnoError() << "Unable to link " << apex_file->GetPath() << " to "
+ if (link(apex_file.GetPath().c_str(), dest_path.c_str()) != 0) {
+ return ErrnoError() << "Unable to link " << apex_file.GetPath() << " to "
<< dest_path;
}
staged_files.insert(dest_path);
- staged_packages.insert(apex_file->GetManifest().name());
+ staged_packages.insert(apex_file.GetManifest().name());
- LOG(DEBUG) << "Success linking " << apex_file->GetPath() << " to "
+ LOG(DEBUG) << "Success linking " << apex_file.GetPath() << " to "
<< dest_path;
}
@@ -1681,18 +2190,19 @@ Result<void> stagePackages(const std::vector<std::string>& tmpPaths) {
return RemovePreviouslyActiveApexFiles(staged_packages, staged_files);
}
-Result<void> unstagePackages(const std::vector<std::string>& paths) {
+Result<void> UnstagePackages(const std::vector<std::string>& paths) {
if (paths.empty()) {
return Errorf("Empty set of inputs");
}
- LOG(DEBUG) << "unstagePackages() for " << Join(paths, ',');
-
- // TODO: to make unstage safer, we can copy to be unstaged packages to a
- // temporary folder and restore state from it in case unstagePackages fails.
+ LOG(DEBUG) << "UnstagePackages() for " << Join(paths, ',');
for (const std::string& path : paths) {
- if (access(path.c_str(), F_OK) != 0) {
- return ErrnoError() << "Can't access " << path;
+ auto apex = ApexFile::Open(path);
+ if (!apex.ok()) {
+ return apex.error();
+ }
+ if (ApexFileRepository::GetInstance().IsPreInstalledApex(*apex)) {
+ return Error() << "Can't uninstall pre-installed apex " << path;
}
}
@@ -1714,54 +2224,57 @@ Result<void> unstagePackages(const std::vector<std::string>& paths) {
* Also, we need to put staged sessions in /data/apex/sessions in REVERTED state
* so that they do not get activated on next reboot.
*/
-Result<void> revertActiveSessions(const std::string& crashing_native_process) {
+Result<void> RevertActiveSessions(const std::string& crashing_native_process,
+ const std::string& error_message) {
// First check whenever there is anything to revert. If there is none, then
// fail. This prevents apexd from boot looping a device in case a native
// process is crashing and there are no apex updates.
- auto activeSessions = ApexSession::GetActiveSessions();
- if (activeSessions.empty()) {
+ auto active_sessions = ApexSession::GetActiveSessions();
+ if (active_sessions.empty()) {
return Error() << "Revert requested, when there are no active sessions.";
}
- for (auto& session : activeSessions) {
+ for (auto& session : active_sessions) {
if (!crashing_native_process.empty()) {
session.SetCrashingNativeProcess(crashing_native_process);
}
+ if (!error_message.empty()) {
+ session.SetErrorMessage(error_message);
+ }
auto status =
session.UpdateStateAndCommit(SessionState::REVERT_IN_PROGRESS);
- if (!status) {
- // TODO: should we continue with a revert?
+ if (!status.ok()) {
return Error() << "Revert of session " << session
<< " failed : " << status.error();
}
}
if (!gInFsCheckpointMode) {
- auto restoreStatus = RestoreActivePackages();
- if (!restoreStatus.ok()) {
- for (auto& session : activeSessions) {
+ auto restore_status = RestoreActivePackages();
+ if (!restore_status.ok()) {
+ for (auto& session : active_sessions) {
auto st = session.UpdateStateAndCommit(SessionState::REVERT_FAILED);
LOG(DEBUG) << "Marking " << session << " as failed to revert";
- if (!st) {
+ if (!st.ok()) {
LOG(WARNING) << "Failed to mark session " << session
<< " as failed to revert : " << st.error();
}
}
- return restoreStatus;
+ return restore_status;
}
} else {
LOG(INFO) << "Not restoring active packages in checkpoint mode.";
}
- for (auto& session : activeSessions) {
+ for (auto& session : active_sessions) {
if (!gInFsCheckpointMode && session.IsRollback()) {
// If snapshots have already been restored, undo that by restoring the
// pre-restore snapshot.
- restoreDePreRestoreSnapshotsIfPresent(session);
+ RestoreDePreRestoreSnapshotsIfPresent(session);
}
auto status = session.UpdateStateAndCommit(SessionState::REVERTED);
- if (!status) {
+ if (!status.ok()) {
LOG(WARNING) << "Failed to mark session " << session
<< " as reverted : " << status.error();
}
@@ -1770,9 +2283,10 @@ Result<void> revertActiveSessions(const std::string& crashing_native_process) {
return {};
}
-Result<void> revertActiveSessionsAndReboot(
- const std::string& crashing_native_process) {
- auto status = revertActiveSessions(crashing_native_process);
+Result<void> RevertActiveSessionsAndReboot(
+ const std::string& crashing_native_process,
+ const std::string& error_message) {
+ auto status = RevertActiveSessions(crashing_native_process, error_message);
if (!status.ok()) {
return status;
}
@@ -1788,49 +2302,101 @@ Result<void> revertActiveSessionsAndReboot(
return {};
}
-int onBootstrap() {
- gBootstrap = true;
+Result<void> CreateSharedLibsApexDir() {
+ // Creates /apex/sharedlibs/lib{,64} for SharedLibs APEXes.
+ std::string shared_libs_sub_dir =
+ StringPrintf("%s/%s", kApexRoot, kApexSharedLibsSubDir);
+ auto dir_exists = PathExists(shared_libs_sub_dir);
+ if (!dir_exists.ok() || !*dir_exists) {
+ std::error_code error_code;
+ std::filesystem::create_directory(shared_libs_sub_dir, error_code);
+ if (error_code) {
+ return Error() << "Failed to create directory " << shared_libs_sub_dir
+ << ": " << error_code.message();
+ }
+ }
+ for (const auto& lib_path : {"lib", "lib64"}) {
+ std::string apex_lib_path =
+ StringPrintf("%s/%s", shared_libs_sub_dir.c_str(), lib_path);
+ auto lib_dir_exists = PathExists(apex_lib_path);
+ if (!lib_dir_exists.ok() || !*lib_dir_exists) {
+ std::error_code error_code;
+ std::filesystem::create_directory(apex_lib_path, error_code);
+ if (error_code) {
+ return Error() << "Failed to create directory " << apex_lib_path << ": "
+ << error_code.message();
+ }
+ }
+ }
- Result<void> preAllocate = preAllocateLoopDevices();
- if (!preAllocate.ok()) {
+ return {};
+}
+
+int OnBootstrap() {
+ auto time_started = boot_clock::now();
+ Result<void> pre_allocate = PreAllocateLoopDevices();
+ if (!pre_allocate.ok()) {
LOG(ERROR) << "Failed to pre-allocate loop devices : "
- << preAllocate.error();
+ << pre_allocate.error();
}
- std::vector<std::string> bootstrap_apex_dirs{
+ ApexFileRepository& instance = ApexFileRepository::GetInstance();
+ static const std::vector<std::string> kBootstrapApexDirs{
kApexPackageSystemDir, kApexPackageSystemExtDir, kApexPackageVendorDir};
- Result<void> status = collectPreinstalledData(bootstrap_apex_dirs);
+ Result<void> status = instance.AddPreInstalledApex(kBootstrapApexDirs);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
return 1;
}
- // Activate built-in APEXes for processes launched before /data is mounted.
- for (const auto& dir : bootstrap_apex_dirs) {
- auto scan_status = ScanApexFiles(dir.c_str());
- if (!scan_status.ok()) {
+ // Create directories for APEX shared libraries.
+ auto sharedlibs_apex_dir = CreateSharedLibsApexDir();
+ if (!sharedlibs_apex_dir.ok()) {
+ LOG(ERROR) << sharedlibs_apex_dir.error();
+ return 1;
+ }
+
+ // Find all bootstrap apexes
+ std::vector<ApexFile> bootstrap_apexes;
+ for (const auto& dir : kBootstrapApexDirs) {
+ auto scan = ScanApexFiles(dir.c_str());
+ if (!scan.ok()) {
LOG(ERROR) << "Failed to scan APEX files in " << dir << " : "
- << scan_status.error();
- return 1;
- }
- if (auto ret = ActivateApexPackages(*scan_status); !ret.ok()) {
- LOG(ERROR) << "Failed to activate APEX files in " << dir << " : "
- << ret.error();
+ << scan.error();
return 1;
}
+ std::copy_if(std::make_move_iterator(scan->begin()),
+ std::make_move_iterator(scan->end()),
+ std::back_inserter(bootstrap_apexes), IsBootstrapApex);
+ }
+
+ // Now activate bootstrap apexes.
+ std::vector<ApexFileRef> bootstrap_apexes_ref;
+ std::transform(bootstrap_apexes.begin(), bootstrap_apexes.end(),
+ std::back_inserter(bootstrap_apexes_ref),
+ [](const auto& x) { return std::cref(x); });
+ auto ret = ActivateApexPackages(bootstrap_apexes_ref,
+ /* is_ota_chroot= */ false);
+ if (!ret.ok()) {
+ LOG(ERROR) << "Failed to activate bootstrap apex files : " << ret.error();
+ return 1;
}
- LOG(INFO) << "Bootstrapping done";
+
+ OnAllPackagesActivated(/*is_bootstrap=*/true);
+ auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+ boot_clock::now() - time_started).count();
+ LOG(INFO) << "OnBootstrap done, duration=" << time_elapsed;
return 0;
}
-Result<void> remountApexFile(const std::string& path) {
- if (auto ret = deactivatePackage(path); !ret.ok()) {
+Result<void> RemountApexFile(const std::string& path) {
+ if (auto ret = DeactivatePackage(path); !ret.ok()) {
return ret;
}
- return activatePackage(path);
+ return ActivatePackage(path);
}
-void initializeVold(CheckpointInterface* checkpoint_service) {
+void InitializeVold(CheckpointInterface* checkpoint_service) {
if (checkpoint_service != nullptr) {
gVoldService = checkpoint_service;
Result<bool> supports_fs_checkpoints =
@@ -1853,21 +2419,300 @@ void initializeVold(CheckpointInterface* checkpoint_service) {
}
}
-void initialize(CheckpointInterface* checkpoint_service) {
- initializeVold(checkpoint_service);
- Result<void> status = collectPreinstalledData(kApexPackageBuiltinDirs);
+void Initialize(CheckpointInterface* checkpoint_service) {
+ InitializeVold(checkpoint_service);
+ ApexFileRepository& instance = ApexFileRepository::GetInstance();
+ Result<void> status = instance.AddPreInstalledApex(kApexPackageBuiltinDirs);
if (!status.ok()) {
- LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
+ LOG(ERROR) << "Failed to collect pre-installed APEX files : "
+ << status.error();
return;
}
+ gMountedApexes.PopulateFromMounts(gConfig->active_apex_data_dir,
+ gConfig->decompression_dir,
+ gConfig->apex_hash_tree_dir);
+}
- gMountedApexes.PopulateFromMounts();
+// Note: Pre-installed apex are initialized in Initialize(CheckpointInterface*)
+// TODO(b/172911822): Consolidate this with Initialize() when
+// ApexFileRepository can act as cache and re-scanning is not expensive
+void InitializeDataApex() {
+ ApexFileRepository& instance = ApexFileRepository::GetInstance();
+ Result<void> status = instance.AddDataApex(kActiveApexPackagesDataDir);
+ if (!status.ok()) {
+ LOG(ERROR) << "Failed to collect data APEX files : " << status.error();
+ return;
+ }
}
-void onStart() {
+/**
+ * For every package X, there can be at most two APEX, pre-installed vs
+ * installed on data. We usually select only one of these APEX for each package
+ * based on the following conditions:
+ * - Package X must be pre-installed on one of the built-in directories.
+ * - If there are multiple APEX, we select the one with highest version.
+ * - If there are multiple with same version, we give priority to APEX on
+ * /data partition.
+ *
+ * Typically, only one APEX is activated for each package, but APEX that provide
+ * shared libs are exceptions. We have to activate both APEX for them.
+ *
+ * @param all_apex all the APEX grouped by their package name
+ * @return list of ApexFile that needs to be activated
+ */
+std::vector<ApexFileRef> SelectApexForActivation(
+ const std::unordered_map<std::string, std::vector<ApexFileRef>>& all_apex,
+ const ApexFileRepository& instance) {
+ LOG(INFO) << "Selecting APEX for activation";
+ std::vector<ApexFileRef> activation_list;
+ // For every package X, select which APEX to activate
+ for (auto& apex_it : all_apex) {
+ const std::string& package_name = apex_it.first;
+ const std::vector<ApexFileRef>& apex_files = apex_it.second;
+
+ if (apex_files.size() > 2 || apex_files.size() == 0) {
+ LOG(FATAL) << "Unexpectedly found more than two versions or none for "
+ "APEX package "
+ << package_name;
+ continue;
+ }
+
+ // The package must have a pre-installed version before we consider it for
+ // activation
+ if (!instance.HasPreInstalledVersion(package_name)) {
+ LOG(INFO) << "Package " << package_name << " is not pre-installed";
+ continue;
+ }
+
+ if (apex_files.size() == 1) {
+ LOG(DEBUG) << "Selecting the only APEX: " << package_name << " "
+ << apex_files[0].get().GetPath();
+ activation_list.emplace_back(apex_files[0]);
+ continue;
+ }
+
+ // TODO(b/179497746): Now that we are dealing with list of reference, this
+ // selection process can be simplified by sorting the vector.
+
+ // Given an APEX A and the version of the other APEX B, should we activate
+ // it?
+ auto select_apex = [&instance, &activation_list](
+ const ApexFileRef& a_ref,
+ const int version_b) mutable {
+ const ApexFile& a = a_ref.get();
+ // APEX that provides shared library always gets activated
+ const bool provides_shared_apex_libs =
+ a.GetManifest().providesharedapexlibs();
+ // If A has higher version than B, then it should be activated
+ const bool higher_version = a.GetManifest().version() > version_b;
+ // If A has same version as B, then data version should get activated
+ const bool same_version_priority_to_data =
+ a.GetManifest().version() == version_b &&
+ !instance.IsPreInstalledApex(a);
+ if (provides_shared_apex_libs || higher_version ||
+ same_version_priority_to_data) {
+ LOG(DEBUG) << "Selecting between two APEX: " << a.GetManifest().name()
+ << " " << a.GetPath();
+ activation_list.emplace_back(a_ref);
+ }
+ };
+ const int version_0 = apex_files[0].get().GetManifest().version();
+ const int version_1 = apex_files[1].get().GetManifest().version();
+ select_apex(apex_files[0].get(), version_1);
+ select_apex(apex_files[1].get(), version_0);
+ }
+ return activation_list;
+}
+
+namespace {
+
+Result<ApexFile> OpenAndValidateDecompressedApex(const ApexFile& capex,
+ const std::string& apex_path) {
+ auto apex = ApexFile::Open(apex_path);
+ if (!apex.ok()) {
+ return Error() << "Failed to open decompressed APEX: " << apex.error();
+ }
+ auto result = ValidateDecompressedApex(capex, *apex);
+ if (!result.ok()) {
+ return result.error();
+ }
+ return std::move(*apex);
+}
+
+// Process a single compressed APEX. Returns the decompressed APEX if
+// successful.
+Result<ApexFile> ProcessCompressedApex(const ApexFile& capex,
+ bool is_ota_chroot) {
+ LOG(INFO) << "Processing compressed APEX " << capex.GetPath();
+ const auto decompressed_apex_path =
+ StringPrintf("%s/%s%s", gConfig->decompression_dir,
+ GetPackageId(capex.GetManifest()).c_str(),
+ kDecompressedApexPackageSuffix);
+ // Check if decompressed APEX already exist
+ auto decompressed_path_exists = PathExists(decompressed_apex_path);
+ if (decompressed_path_exists.ok() && *decompressed_path_exists) {
+ // Check if existing decompressed APEX is valid
+ auto result =
+ OpenAndValidateDecompressedApex(capex, decompressed_apex_path);
+ if (result.ok()) {
+ LOG(INFO) << "Skipping decompression for " << capex.GetPath();
+ return result;
+ }
+ // Do not delete existing decompressed APEX when is_ota_chroot is true
+ if (!is_ota_chroot) {
+ // Existing decompressed APEX is not valid. We will have to redecompress
+ LOG(WARNING) << "Existing decompressed APEX is invalid: "
+ << result.error();
+ RemoveFileIfExists(decompressed_apex_path);
+ }
+ }
+
+ // We can also reuse existing OTA APEX, depending on situation
+ auto ota_apex_path = StringPrintf("%s/%s%s", gConfig->decompression_dir,
+ GetPackageId(capex.GetManifest()).c_str(),
+ kOtaApexPackageSuffix);
+ auto ota_path_exists = PathExists(ota_apex_path);
+ if (ota_path_exists.ok() && *ota_path_exists) {
+ if (is_ota_chroot) {
+ // During ota_chroot, we try to reuse ota APEX as is
+ auto result = OpenAndValidateDecompressedApex(capex, ota_apex_path);
+ if (result.ok()) {
+ LOG(INFO) << "Skipping decompression for " << ota_apex_path;
+ return result;
+ }
+ // Existing ota_apex is not valid. We will have to decompress
+ LOG(WARNING) << "Existing decompressed OTA APEX is invalid: "
+ << result.error();
+ RemoveFileIfExists(ota_apex_path);
+ } else {
+ // During boot, we can avoid decompression by renaming OTA apex
+ // to expected decompressed_apex path
+
+ // Check if ota_apex APEX is valid
+ auto result = OpenAndValidateDecompressedApex(capex, ota_apex_path);
+ if (result.ok()) {
+ // ota_apex matches with capex. Slot has been switched.
+
+ // Rename ota_apex to expected decompressed_apex path
+ if (rename(ota_apex_path.c_str(), decompressed_apex_path.c_str()) ==
+ 0) {
+ // Check if renamed decompressed APEX is valid
+ result =
+ OpenAndValidateDecompressedApex(capex, decompressed_apex_path);
+ if (result.ok()) {
+ LOG(INFO) << "Renamed " << ota_apex_path << " to "
+ << decompressed_apex_path;
+ return result;
+ }
+ // Renamed ota_apex is not valid. We will have to decompress
+ LOG(WARNING) << "Renamed decompressed APEX from " << ota_apex_path
+ << " to " << decompressed_apex_path
+ << " is invalid: " << result.error();
+ RemoveFileIfExists(decompressed_apex_path);
+ } else {
+ PLOG(ERROR) << "Failed to rename file " << ota_apex_path;
+ }
+ }
+ }
+ }
+
+ // There was no way to avoid decompression
+
+ // Clean up reserved space before decompressing capex
+ if (auto ret = DeleteDirContent(gConfig->ota_reserved_dir); !ret.ok()) {
+ LOG(ERROR) << "Failed to clean up reserved space: " << ret.error();
+ }
+
+ auto decompression_dest =
+ is_ota_chroot ? ota_apex_path : decompressed_apex_path;
+ auto scope_guard = android::base::make_scope_guard(
+ [&]() { RemoveFileIfExists(decompression_dest); });
+
+ auto decompression_result = capex.Decompress(decompression_dest);
+ if (!decompression_result.ok()) {
+ return Error() << "Failed to decompress : " << capex.GetPath().c_str()
+ << " " << decompression_result.error();
+ }
+
+ // Fix label of decompressed file
+ auto restore = RestoreconPath(decompression_dest);
+ if (!restore.ok()) {
+ return restore.error();
+ }
+
+ // Validate the newly decompressed APEX
+ auto return_apex = OpenAndValidateDecompressedApex(capex, decompression_dest);
+ if (!return_apex.ok()) {
+ return Error() << "Failed to decompress CAPEX: " << return_apex.error();
+ }
+
+ /// Release compressed blocks in case decompression_dest is on f2fs-compressed
+ // filesystem.
+ ReleaseF2fsCompressedBlocks(decompression_dest);
+
+ scope_guard.Disable();
+ return return_apex;
+}
+} // namespace
+
+/**
+ * For each compressed APEX, decompress it to kApexDecompressedDir
+ * and return the decompressed APEX.
+ *
+ * Returns list of decompressed APEX.
+ */
+std::vector<ApexFile> ProcessCompressedApex(
+ const std::vector<ApexFileRef>& compressed_apex, bool is_ota_chroot) {
+ LOG(INFO) << "Processing compressed APEX";
+
+ std::vector<ApexFile> decompressed_apex_list;
+ for (const ApexFile& capex : compressed_apex) {
+ if (!capex.IsCompressed()) {
+ continue;
+ }
+
+ auto decompressed_apex = ProcessCompressedApex(capex, is_ota_chroot);
+ if (decompressed_apex.ok()) {
+ decompressed_apex_list.emplace_back(std::move(*decompressed_apex));
+ continue;
+ }
+ LOG(ERROR) << "Failed to process compressed APEX: "
+ << decompressed_apex.error();
+ }
+ return std::move(decompressed_apex_list);
+}
+
+Result<void> ValidateDecompressedApex(const ApexFile& capex,
+ const ApexFile& apex) {
+ // Decompressed APEX must have same public key as CAPEX
+ if (capex.GetBundledPublicKey() != apex.GetBundledPublicKey()) {
+ return Error()
+ << "Public key of compressed APEX is different than original "
+ << "APEX for " << apex.GetPath();
+ }
+ // Decompressed APEX must have same version as CAPEX
+ if (capex.GetManifest().version() != apex.GetManifest().version()) {
+ return Error()
+ << "Compressed APEX has different version than decompressed APEX "
+ << apex.GetPath();
+ }
+ // Decompressed APEX must have same root digest as what is stored in CAPEX
+ auto apex_verity = apex.VerifyApexVerity(apex.GetBundledPublicKey());
+ if (!apex_verity.ok() ||
+ capex.GetManifest().capexmetadata().originalapexdigest() !=
+ apex_verity->root_digest) {
+ return Error() << "Root digest of " << apex.GetPath()
+ << " does not match with"
+ << " expected root digest in " << capex.GetPath();
+ }
+ return {};
+}
+
+void OnStart() {
LOG(INFO) << "Marking APEXd as starting";
- if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusStarting)) {
- PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to "
+ auto time_started = boot_clock::now();
+ if (!SetProperty(gConfig->apex_status_sysprop, kApexStatusStarting)) {
+ PLOG(ERROR) << "Failed to set " << gConfig->apex_status_sysprop << " to "
<< kApexStatusStarting;
}
@@ -1883,98 +2728,134 @@ void onStart() {
LOG(INFO) << "Exceeded number of session retries ("
<< kNumRetriesWhenCheckpointingEnabled
<< "). Starting a revert";
- revertActiveSessions("");
+ RevertActiveSessions("", "");
}
}
- // Activate APEXes from /data/apex. If one in the directory is newer than the
- // system one, the new one will eclipse the old one.
- scanStagedSessionsDirAndStage();
- auto status = resumeRevertIfNeeded();
+ // Create directories for APEX shared libraries.
+ auto sharedlibs_apex_dir = CreateSharedLibsApexDir();
+ if (!sharedlibs_apex_dir.ok()) {
+ LOG(ERROR) << sharedlibs_apex_dir.error();
+ }
+
+ // If there is any new apex to be installed on /data/app-staging, hardlink
+ // them to /data/apex/active first.
+ ScanStagedSessionsDirAndStage();
+ if (auto status = ApexFileRepository::GetInstance().AddDataApex(
+ gConfig->active_apex_data_dir);
+ !status.ok()) {
+ LOG(ERROR) << "Failed to collect data APEX files : " << status.error();
+ }
+
+ auto status = ResumeRevertIfNeeded();
if (!status.ok()) {
LOG(ERROR) << "Failed to resume revert : " << status.error();
}
- std::vector<ApexFile> data_apex;
- if (auto scan = ScanApexFiles(kActiveApexPackagesDataDir); !scan.ok()) {
- LOG(ERROR) << "Failed to scan packages from " << kActiveApexPackagesDataDir
- << " : " << scan.error();
- if (auto revert = revertActiveSessionsAndReboot(""); !revert.ok()) {
- LOG(ERROR) << "Failed to revert : " << revert.error();
+ // Group every ApexFile on device by name
+ const auto& instance = ApexFileRepository::GetInstance();
+ const auto& all_apex = instance.AllApexFilesByName();
+ // There can be multiple APEX packages with package name X. Determine which
+ // one to activate.
+ auto activation_list = SelectApexForActivation(all_apex, instance);
+
+ // Process compressed APEX, if any
+ std::vector<ApexFileRef> compressed_apex;
+ for (auto it = activation_list.begin(); it != activation_list.end();) {
+ if (it->get().IsCompressed()) {
+ compressed_apex.emplace_back(*it);
+ it = activation_list.erase(it);
+ } else {
+ it++;
}
- } else {
- auto filter_fn = [](const ApexFile& apex) {
- if (!ShouldActivateApexOnData(apex)) {
- LOG(WARNING) << "Skipping " << apex.GetPath();
- return false;
- }
- return true;
- };
- std::copy_if(std::make_move_iterator(scan->begin()),
- std::make_move_iterator(scan->end()),
- std::back_inserter(data_apex), filter_fn);
}
-
- if (auto ret = ActivateApexPackages(data_apex); !ret.ok()) {
- LOG(ERROR) << "Failed to activate packages from "
- << kActiveApexPackagesDataDir << " : " << status.error();
- Result<void> revert_status = revertActiveSessionsAndReboot("");
- if (!revert_status.ok()) {
- // TODO: should we kill apexd in this case?
- LOG(ERROR) << "Failed to revert : " << revert_status.error()
- << kActiveApexPackagesDataDir << " : " << ret.error();
+ std::vector<ApexFile> decompressed_apex;
+ if (!compressed_apex.empty()) {
+ decompressed_apex =
+ ProcessCompressedApex(compressed_apex, /* is_ota_chroot= */ false);
+ for (const ApexFile& apex_file : decompressed_apex) {
+ activation_list.emplace_back(std::cref(apex_file));
}
}
- // Now also scan and activate APEXes from pre-installed directories.
- for (const auto& dir : kApexPackageBuiltinDirs) {
- auto scan_status = ScanApexFiles(dir.c_str());
- if (!scan_status.ok()) {
- LOG(ERROR) << "Failed to scan APEX packages from " << dir << " : "
- << scan_status.error();
- if (auto revert = revertActiveSessionsAndReboot(""); !revert.ok()) {
- LOG(ERROR) << "Failed to revert : " << revert.error();
- }
+ int data_apex_cnt = std::count_if(
+ activation_list.begin(), activation_list.end(), [](const auto& a) {
+ return !ApexFileRepository::GetInstance().IsPreInstalledApex(a.get());
+ });
+ if (data_apex_cnt > 0) {
+ Result<void> pre_allocate = loop::PreAllocateLoopDevices(data_apex_cnt);
+ if (!pre_allocate.ok()) {
+ LOG(ERROR) << "Failed to pre-allocate loop devices : "
+ << pre_allocate.error();
+ }
+ }
+
+ // TODO(b/179248390): activate parallelly if possible
+ auto activate_status =
+ ActivateApexPackages(activation_list, /* is_ota_chroot= */ false);
+ if (!activate_status.ok()) {
+ std::string error_message =
+ StringPrintf("Failed to activate packages: %s",
+ activate_status.error().message().c_str());
+ LOG(ERROR) << error_message;
+ Result<void> revert_status =
+ RevertActiveSessionsAndReboot("", error_message);
+ if (!revert_status.ok()) {
+ LOG(ERROR) << "Failed to revert : " << revert_status.error();
}
- if (auto activate = ActivateApexPackages(*scan_status); !activate.ok()) {
- // This should never happen. Like **really** never.
- // TODO: should we kill apexd in this case?
- LOG(ERROR) << "Failed to activate packages from " << dir << " : "
- << activate.error();
+ auto retry_status = ActivateMissingApexes(activation_list,
+ /* is_ota_chroot= */ false);
+ if (!retry_status.ok()) {
+ LOG(ERROR) << retry_status.error();
}
}
// Now that APEXes are mounted, snapshot or restore DE_sys data.
- snapshotOrRestoreDeSysData();
+ SnapshotOrRestoreDeSysData();
+
+ auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+ boot_clock::now() - time_started).count();
+ LOG(INFO) << "OnStart done, duration=" << time_elapsed;
}
-void onAllPackagesActivated() {
+void OnAllPackagesActivated(bool is_bootstrap) {
+ auto result = EmitApexInfoList(is_bootstrap);
+ if (!result.ok()) {
+ LOG(ERROR) << "cannot emit apex info list: " << result.error();
+ }
+
+ // Because apexd in bootstrap mode runs in blocking mode
+ // we don't have to set as activated.
+ if (is_bootstrap) {
+ return;
+ }
+
// Set a system property to let other components know that APEXs are
// activated, but are not yet ready to be used. init is expected to wait
// for this status before performing configuration based on activated
// apexes. Other components that need to use APEXs should wait for the
// ready state instead.
LOG(INFO) << "Marking APEXd as activated";
- if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusActivated)) {
- PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to "
+ if (!SetProperty(gConfig->apex_status_sysprop, kApexStatusActivated)) {
+ PLOG(ERROR) << "Failed to set " << gConfig->apex_status_sysprop << " to "
<< kApexStatusActivated;
}
}
-void onAllPackagesReady() {
+void OnAllPackagesReady() {
// Set a system property to let other components know that APEXs are
// correctly mounted and ready to be used. Before using any file from APEXs,
// they can query this system property to ensure that they are okay to
// access. Or they may have a on-property trigger to delay a task until
// APEXs become ready.
LOG(INFO) << "Marking APEXd as ready";
- if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusReady)) {
- PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to "
+ if (!SetProperty(gConfig->apex_status_sysprop, kApexStatusReady)) {
+ PLOG(ERROR) << "Failed to set " << gConfig->apex_status_sysprop << " to "
<< kApexStatusReady;
}
}
-Result<std::vector<ApexFile>> submitStagedSession(
+Result<std::vector<ApexFile>> SubmitStagedSession(
const int session_id, const std::vector<int>& child_session_ids,
const bool has_rollback_enabled, const bool is_rollback,
const int rollback_id) {
@@ -1999,7 +2880,7 @@ Result<std::vector<ApexFile>> submitStagedSession(
std::vector<ApexFile> ret;
for (int id_to_scan : ids_to_scan) {
- auto verified = verifySessionDir(id_to_scan);
+ auto verified = VerifySessionDir(id_to_scan);
if (!verified.ok()) {
return verified.error();
}
@@ -2033,10 +2914,15 @@ Result<std::vector<ApexFile>> submitStagedSession(
return commit_status.error();
}
+ for (const auto& apex : ret) {
+ // Release compressed blocks in case /data is f2fs-compressed filesystem.
+ ReleaseF2fsCompressedBlocks(apex.GetPath());
+ }
+
return ret;
}
-Result<void> markStagedSessionReady(const int session_id) {
+Result<void> MarkStagedSessionReady(const int session_id) {
auto session = ApexSession::GetSession(session_id);
if (!session.ok()) {
return session.error();
@@ -2055,7 +2941,7 @@ Result<void> markStagedSessionReady(const int session_id) {
<< ". Cannot mark it as ready.";
}
-Result<void> markStagedSessionSuccessful(const int session_id) {
+Result<void> MarkStagedSessionSuccessful(const int session_id) {
auto session = ApexSession::GetSession(session_id);
if (!session.ok()) {
return session.error();
@@ -2071,7 +2957,7 @@ Result<void> markStagedSessionSuccessful(const int session_id) {
<< " as successful : " << cleanup_status.error();
}
if (session->IsRollback() && !gInFsCheckpointMode) {
- deleteDePreRestoreSnapshots(*session);
+ DeleteDePreRestoreSnapshots(*session);
}
return session->UpdateStateAndCommit(SessionState::SUCCESS);
} else {
@@ -2079,86 +2965,63 @@ Result<void> markStagedSessionSuccessful(const int session_id) {
}
}
-namespace {
-
-// Find dangling mounts and unmount them.
-// If one is on /data/apex/active, remove it.
-void UnmountDanglingMounts() {
- std::multimap<std::string, MountedApexData> danglings;
- gMountedApexes.ForallMountedApexes([&](const std::string& package,
- const MountedApexData& data,
- bool latest) {
- if (!latest) {
- danglings.insert({package, data});
- }
- });
-
- for (const auto& [package, data] : danglings) {
- const std::string& path = data.full_path;
- LOG(VERBOSE) << "Unmounting " << data.mount_point;
- gMountedApexes.RemoveMountedApex(package, path);
- if (auto st = Unmount(data); !st.ok()) {
- LOG(ERROR) << st.error();
- }
- if (StartsWith(path, kActiveApexPackagesDataDir)) {
- LOG(VERBOSE) << "Deleting old APEX " << path;
- if (unlink(path.c_str()) != 0) {
- PLOG(ERROR) << "Failed to delete " << path;
- }
- }
+// Removes APEXes on /data that have not been activated
+void RemoveInactiveDataApex() {
+ std::vector<std::string> all_apex_files;
+ Result<std::vector<std::string>> active_apex =
+ FindFilesBySuffix(gConfig->active_apex_data_dir, {kApexPackageSuffix});
+ if (!active_apex.ok()) {
+ LOG(ERROR) << "Failed to scan " << gConfig->active_apex_data_dir << " : "
+ << active_apex.error();
+ } else {
+ all_apex_files.insert(all_apex_files.end(),
+ std::make_move_iterator(active_apex->begin()),
+ std::make_move_iterator(active_apex->end()));
+ }
+ Result<std::vector<std::string>> decompressed_apex = FindFilesBySuffix(
+ gConfig->decompression_dir, {kDecompressedApexPackageSuffix});
+ if (!decompressed_apex.ok()) {
+ LOG(ERROR) << "Failed to scan " << gConfig->decompression_dir << " : "
+ << decompressed_apex.error();
+ } else {
+ all_apex_files.insert(all_apex_files.end(),
+ std::make_move_iterator(decompressed_apex->begin()),
+ std::make_move_iterator(decompressed_apex->end()));
}
- RemoveObsoleteHashTrees();
-}
-
-// Removes APEXes on /data that don't have corresponding pre-installed version
-// or that are corrupt
-void RemoveOrphanedApexes() {
- auto data_apexes = FindApexFilesByName(kActiveApexPackagesDataDir);
- if (!data_apexes.ok()) {
- LOG(ERROR) << "Failed to scan " << kActiveApexPackagesDataDir << " : "
- << data_apexes.error();
- return;
- }
- for (const auto& path : *data_apexes) {
- auto apex = ApexFile::Open(path);
- if (!apex.ok()) {
- LOG(DEBUG) << "Failed to open APEX " << path << " : " << apex.error();
- // before removing, double-check if the path is active or not
- // just in case ApexFile::Open() fails with valid APEX
- if (!apexd_private::IsMounted(path)) {
- LOG(DEBUG) << "Removing corrupt APEX " << path;
- if (unlink(path.c_str()) != 0) {
- PLOG(ERROR) << "Failed to unlink " << path;
- }
- }
- continue;
- }
- if (!ShouldActivateApexOnData(*apex)) {
- LOG(DEBUG) << "Removing orphaned APEX " << path;
+ for (const auto& path : all_apex_files) {
+ if (!apexd_private::IsMounted(path)) {
+ LOG(INFO) << "Removing inactive data APEX " << path;
if (unlink(path.c_str()) != 0) {
- PLOG(ERROR) << "Failed to unlink " << path;
+ PLOG(ERROR) << "Failed to unlink inactive data APEX " << path;
}
}
}
}
-} // namespace
-
-void bootCompletedCleanup() {
- UnmountDanglingMounts();
- RemoveOrphanedApexes();
+void BootCompletedCleanup() {
+ RemoveInactiveDataApex();
+ ApexSession::DeleteFinalizedSessions();
}
-int unmountAll() {
- gMountedApexes.PopulateFromMounts();
+int UnmountAll() {
+ gMountedApexes.PopulateFromMounts(gConfig->active_apex_data_dir,
+ gConfig->decompression_dir,
+ gConfig->apex_hash_tree_dir);
int ret = 0;
gMountedApexes.ForallMountedApexes([&](const std::string& /*package*/,
const MountedApexData& data,
bool latest) {
LOG(INFO) << "Unmounting " << data.full_path << " mounted on "
<< data.mount_point;
- if (latest) {
+ auto apex = ApexFile::Open(data.full_path);
+ if (!apex.ok()) {
+ LOG(ERROR) << "Failed to open " << data.full_path << " : "
+ << apex.error();
+ ret = 1;
+ return;
+ }
+ if (latest && !apex->GetManifest().providesharedapexlibs()) {
auto pos = data.mount_point.find('@');
CHECK(pos != std::string::npos);
std::string bind_mount = data.mount_point.substr(0, pos);
@@ -2167,7 +3030,7 @@ int unmountAll() {
ret = 1;
}
}
- if (auto status = Unmount(data); !status.ok()) {
+ if (auto status = Unmount(data, /* deferred= */ false); !status.ok()) {
LOG(ERROR) << "Failed to unmount " << data.mount_point << " : "
<< status.error();
ret = 1;
@@ -2176,12 +3039,7 @@ int unmountAll() {
return ret;
}
-bool isBooting() {
- auto status = GetProperty(kApexStatusSysprop, "");
- return status != kApexStatusReady && status != kApexStatusActivated;
-}
-
-Result<void> remountPackages() {
+Result<void> RemountPackages() {
std::vector<std::string> apexes;
gMountedApexes.ForallMountedApexes([&apexes](const std::string& /*package*/,
const MountedApexData& data,
@@ -2195,7 +3053,7 @@ Result<void> remountPackages() {
for (const std::string& apex : apexes) {
// Since this is only used during development workflow, we are trying to
// remount as many apexes as possible instead of failing fast.
- if (auto ret = remountApexFile(apex); !ret) {
+ if (auto ret = RemountApexFile(apex); !ret.ok()) {
LOG(WARNING) << "Failed to remount " << apex << " : " << ret.error();
failed.emplace_back(apex);
}
@@ -2212,5 +3070,596 @@ Result<void> remountPackages() {
return {};
}
+// Given a single new APEX incoming via OTA, should we allocate space for it?
+Result<bool> ShouldAllocateSpaceForDecompression(
+ const std::string& new_apex_name, const int64_t new_apex_version,
+ const ApexFileRepository& instance) {
+ // An apex at most will have two versions on device: pre-installed and data.
+
+ // Check if there is a pre-installed version for the new apex.
+ if (!instance.HasPreInstalledVersion(new_apex_name)) {
+ // We are introducing a new APEX that doesn't exist at all
+ return true;
+ }
+
+ // Check if there is a data apex
+ if (!instance.HasDataVersion(new_apex_name)) {
+ // Data apex doesn't exist. Compare against pre-installed APEX
+ auto pre_installed_apex = instance.GetPreInstalledApex(new_apex_name);
+ if (!pre_installed_apex.get().IsCompressed()) {
+ // Compressing an existing uncompressed system APEX.
+ return true;
+ }
+ // Since there is no data apex, it means device is using the compressed
+ // pre-installed version. If new apex has higher version, we are upgrading
+ // the pre-install version and if new apex has lower version, we are
+ // downgrading it. So the current decompressed apex should be replaced
+ // with the new decompressed apex to reflect that.
+ const int64_t pre_installed_version =
+ instance.GetPreInstalledApex(new_apex_name)
+ .get()
+ .GetManifest()
+ .version();
+ return new_apex_version != pre_installed_version;
+ }
+
+ // From here on, data apex exists. So we should compare directly against data
+ // apex.
+ auto data_apex = instance.GetDataApex(new_apex_name);
+ // Compare the data apex version with new apex
+ const int64_t data_version = data_apex.get().GetManifest().version();
+ // We only decompress the new_apex if it has higher version than data apex.
+ return new_apex_version > data_version;
+}
+
+void CollectApexInfoList(std::ostream& os,
+ const std::vector<ApexFile>& active_apexs,
+ const std::vector<ApexFile>& inactive_apexs) {
+ std::vector<com::android::apex::ApexInfo> apex_infos;
+
+ auto convert_to_autogen = [&apex_infos](const ApexFile& apex,
+ bool is_active) {
+ auto& instance = ApexFileRepository::GetInstance();
+
+ auto preinstalled_path =
+ instance.GetPreinstalledPath(apex.GetManifest().name());
+ std::optional<std::string> preinstalled_module_path;
+ if (preinstalled_path.ok()) {
+ preinstalled_module_path = *preinstalled_path;
+ }
+
+ std::optional<int64_t> mtime;
+ struct stat stat_buf;
+ if (stat(apex.GetPath().c_str(), &stat_buf) == 0) {
+ mtime.emplace(stat_buf.st_mtime);
+ } else {
+ PLOG(WARNING) << "Failed to stat " << apex.GetPath();
+ }
+ com::android::apex::ApexInfo apex_info(
+ apex.GetManifest().name(), apex.GetPath(), preinstalled_module_path,
+ apex.GetManifest().version(), apex.GetManifest().versionname(),
+ instance.IsPreInstalledApex(apex), is_active, mtime);
+ apex_infos.emplace_back(apex_info);
+ };
+ for (const auto& apex : active_apexs) {
+ convert_to_autogen(apex, /* is_active= */ true);
+ }
+ for (const auto& apex : inactive_apexs) {
+ convert_to_autogen(apex, /* is_active= */ false);
+ }
+ com::android::apex::ApexInfoList apex_info_list(apex_infos);
+ com::android::apex::write(os, apex_info_list);
+}
+
+// Reserve |size| bytes in |dest_dir| by creating a zero-filled file.
+// Also, we always clean up ota_apex that has been processed as
+// part of pre-reboot decompression whenever we reserve space.
+Result<void> ReserveSpaceForCompressedApex(int64_t size,
+ const std::string& dest_dir) {
+ if (size < 0) {
+ return Error() << "Cannot reserve negative byte of space";
+ }
+
+ // Since we are reserving space, then we must be preparing for a new OTA.
+ // Clean up any processed ota_apex from previous OTA.
+ auto ota_apex_files =
+ FindFilesBySuffix(gConfig->decompression_dir, {kOtaApexPackageSuffix});
+ if (!ota_apex_files.ok()) {
+ return Error() << "Failed to clean up ota_apex: " << ota_apex_files.error();
+ }
+ for (const std::string& ota_apex : *ota_apex_files) {
+ RemoveFileIfExists(ota_apex);
+ }
+
+ auto file_path = StringPrintf("%s/full.tmp", dest_dir.c_str());
+ if (size == 0) {
+ LOG(INFO) << "Cleaning up reserved space for compressed APEX";
+ // Ota is being cancelled. Clean up reserved space
+ RemoveFileIfExists(file_path);
+ return {};
+ }
+
+ LOG(INFO) << "Reserving " << size << " bytes for compressed APEX";
+ unique_fd dest_fd(
+ open(file_path.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT, 0644));
+ if (dest_fd.get() == -1) {
+ return ErrnoError() << "Failed to open file for reservation "
+ << file_path.c_str();
+ }
+
+ // Resize to required size
+ std::error_code ec;
+ std::filesystem::resize_file(file_path, size, ec);
+ if (ec) {
+ RemoveFileIfExists(file_path);
+ return ErrnoError() << "Failed to resize file " << file_path.c_str()
+ << " : " << ec.message();
+ }
+
+ return {};
+}
+
+int OnOtaChrootBootstrap() {
+ auto& instance = ApexFileRepository::GetInstance();
+ if (auto status = instance.AddPreInstalledApex(gConfig->apex_built_in_dirs);
+ !status.ok()) {
+ LOG(ERROR) << "Failed to scan pre-installed apexes from "
+ << Join(gConfig->apex_built_in_dirs, ',');
+ return 1;
+ }
+ if (auto status = instance.AddDataApex(gConfig->active_apex_data_dir);
+ !status.ok()) {
+ LOG(ERROR) << "Failed to scan upgraded apexes from "
+ << gConfig->active_apex_data_dir;
+ // Failing to scan upgraded apexes is not fatal, since we can still try to
+ // run otapreopt using only pre-installed apexes. Worst case, apps will be
+ // re-optimized on next boot.
+ }
+
+ // Create directories for APEX shared libraries.
+ if (auto status = CreateSharedLibsApexDir(); !status.ok()) {
+ LOG(ERROR) << "Failed to create /apex/sharedlibs : " << status.ok();
+ return 1;
+ }
+
+ auto activation_list =
+ SelectApexForActivation(instance.AllApexFilesByName(), instance);
+
+ // TODO(b/179497746): This is the third time we are duplicating this code
+ // block. This will be easier to dedup once we start opening ApexFiles via
+ // ApexFileRepository. That way, ProcessCompressedApex can return list of
+ // ApexFileRef, instead of ApexFile.
+
+ // Process compressed APEX, if any
+ std::vector<ApexFileRef> compressed_apex;
+ for (auto it = activation_list.begin(); it != activation_list.end();) {
+ if (it->get().IsCompressed()) {
+ compressed_apex.emplace_back(*it);
+ it = activation_list.erase(it);
+ } else {
+ it++;
+ }
+ }
+ std::vector<ApexFile> decompressed_apex;
+ if (!compressed_apex.empty()) {
+ decompressed_apex =
+ ProcessCompressedApex(compressed_apex, /* is_ota_chroot= */ true);
+
+ for (const ApexFile& apex_file : decompressed_apex) {
+ activation_list.emplace_back(std::cref(apex_file));
+ }
+ }
+
+ auto activate_status = ActivateApexPackages(activation_list,
+ /* is_ota_chroot= */ true);
+ if (!activate_status.ok()) {
+ LOG(ERROR) << "Failed to activate apex packages : "
+ << activate_status.error();
+ auto retry_status = ActivateMissingApexes(activation_list,
+ /* is_ota_chroot= */ true);
+ if (!retry_status.ok()) {
+ LOG(ERROR) << retry_status.error();
+ }
+ }
+
+ // There are a bunch of places that are producing apex-info.xml file.
+ // We should consolidate the logic in one function and make all other places
+ // use it.
+ auto active_apexes = GetActivePackages();
+ std::vector<ApexFile> inactive_apexes = GetFactoryPackages();
+ auto new_end = std::remove_if(
+ inactive_apexes.begin(), inactive_apexes.end(),
+ [&active_apexes](const ApexFile& apex) {
+ return std::any_of(active_apexes.begin(), active_apexes.end(),
+ [&apex](const ApexFile& active_apex) {
+ return apex.GetPath() == active_apex.GetPath();
+ });
+ });
+ inactive_apexes.erase(new_end, inactive_apexes.end());
+ std::stringstream xml;
+ CollectApexInfoList(xml, active_apexes, inactive_apexes);
+ std::string file_name = StringPrintf("%s/%s", kApexRoot, kApexInfoList);
+ unique_fd fd(TEMP_FAILURE_RETRY(
+ open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)));
+ if (fd.get() == -1) {
+ PLOG(ERROR) << "Can't open " << file_name;
+ return 1;
+ }
+
+ if (!android::base::WriteStringToFd(xml.str(), fd)) {
+ PLOG(ERROR) << "Can't write to " << file_name;
+ return 1;
+ }
+
+ fd.reset();
+
+ if (auto status = RestoreconPath(file_name); !status.ok()) {
+ LOG(ERROR) << "Failed to restorecon " << file_name << " : "
+ << status.error();
+ return 1;
+ }
+
+ return 0;
+}
+
+int OnOtaChrootBootstrapFlattenedApex() {
+ LOG(INFO) << "OnOtaChrootBootstrapFlattenedApex";
+
+ std::vector<com::android::apex::ApexInfo> apex_infos;
+
+ for (const std::string& dir : gConfig->apex_built_in_dirs) {
+ LOG(INFO) << "Scanning " << dir;
+ auto dir_content = ReadDir(dir, [](const auto& entry) {
+ std::error_code ec;
+ return entry.is_directory(ec);
+ });
+
+ if (!dir_content.ok()) {
+ LOG(ERROR) << "Failed to scan " << dir << " : " << dir_content.error();
+ continue;
+ }
+
+ // Sort to make sure that /apex/apex-info-list.xml generation doesn't depend
+ // on the unstable directory scan.
+ std::vector<std::string> entries = std::move(*dir_content);
+ std::sort(entries.begin(), entries.end());
+
+ for (const std::string& apex_dir : entries) {
+ std::string manifest_file = apex_dir + "/" + kManifestFilenamePb;
+ if (access(manifest_file.c_str(), F_OK) != 0) {
+ PLOG(ERROR) << "Failed to access " << manifest_file;
+ continue;
+ }
+
+ auto manifest = ReadManifest(manifest_file);
+ if (!manifest.ok()) {
+ LOG(ERROR) << "Failed to read apex manifest from " << manifest_file
+ << " : " << manifest.error();
+ continue;
+ }
+
+ std::string mount_point = std::string(kApexRoot) + "/" + manifest->name();
+ if (mkdir(mount_point.c_str(), 0755) != 0) {
+ PLOG(ERROR) << "Failed to mkdir " << mount_point;
+ continue;
+ }
+
+ LOG(INFO) << "Bind mounting " << apex_dir << " onto " << mount_point;
+ if (mount(apex_dir.c_str(), mount_point.c_str(), nullptr, MS_BIND,
+ nullptr) != 0) {
+ PLOG(ERROR) << "Failed to bind mount " << apex_dir << " to "
+ << mount_point;
+ continue;
+ }
+
+ apex_infos.emplace_back(manifest->name(), /* modulePath= */ apex_dir,
+ /* preinstalledModulePath= */ apex_dir,
+ /* versionCode= */ manifest->version(),
+ /* versionName= */ manifest->versionname(),
+ /* isFactory= */ true, /* isActive= */ true,
+ /* lastUpdateMillis= */ 0);
+ }
+ }
+
+ std::string file_name = StringPrintf("%s/%s", kApexRoot, kApexInfoList);
+ unique_fd fd(TEMP_FAILURE_RETRY(
+ open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)));
+ if (fd.get() == -1) {
+ PLOG(ERROR) << "Can't open " << file_name;
+ return 1;
+ }
+
+ std::ostringstream xml;
+ com::android::apex::ApexInfoList apex_info_list(apex_infos);
+ com::android::apex::write(xml, apex_info_list);
+ if (!android::base::WriteStringToFd(xml.str(), fd)) {
+ PLOG(ERROR) << "Can't write to " << file_name;
+ return 1;
+ }
+ fd.reset();
+
+ if (auto status = RestoreconPath(file_name); !status.ok()) {
+ LOG(ERROR) << "Failed to restorecon " << file_name << " : "
+ << status.error();
+ return 1;
+ }
+
+ return 0;
+}
+
+android::apex::MountedApexDatabase& GetApexDatabaseForTesting() {
+ return gMountedApexes;
+}
+
+// A version of apex verification that happens during non-staged APEX
+// installation.
+Result<void> VerifyPackageNonStagedInstall(const ApexFile& apex_file) {
+ const auto& verify_package_boot_status = VerifyPackageBoot(apex_file);
+ if (!verify_package_boot_status.ok()) {
+ return verify_package_boot_status;
+ }
+
+ auto check_fn = [&apex_file](const std::string& mount_point) -> Result<void> {
+ auto dirs = GetSubdirs(mount_point);
+ if (!dirs.ok()) {
+ return dirs.error();
+ }
+ if (std::find(dirs->begin(), dirs->end(), mount_point + "/app") !=
+ dirs->end()) {
+ return Error() << apex_file.GetPath() << " contains app inside";
+ }
+ if (std::find(dirs->begin(), dirs->end(), mount_point + "/priv-app") !=
+ dirs->end()) {
+ return Error() << apex_file.GetPath() << " contains priv-app inside";
+ }
+ return Result<void>{};
+ };
+ return RunVerifyFnInsideTempMount(apex_file, check_fn, true);
+}
+
+Result<void> CheckSupportsNonStagedInstall(const ApexFile& cur_apex,
+ const ApexFile& new_apex) {
+ const auto& cur_manifest = cur_apex.GetManifest();
+ const auto& new_manifest = new_apex.GetManifest();
+
+ if (!new_manifest.supportsrebootlessupdate()) {
+ return Error() << new_apex.GetPath()
+ << " does not support non-staged update";
+ }
+
+ // Check if update will impact linkerconfig.
+
+ // Updates to shared libs APEXes must be done via staged install flow.
+ if (new_manifest.providesharedapexlibs()) {
+ return Error() << new_apex.GetPath() << " is a shared libs APEX";
+ }
+
+ // This APEX provides native libs to other parts of the platform. It can only
+ // be updated via staged install flow.
+ if (new_manifest.providenativelibs_size() > 0) {
+ return Error() << new_apex.GetPath() << " provides native libs";
+ }
+
+ // This APEX requires libs provided by dynamic common library APEX, hence it
+ // can only be installed using staged install flow.
+ if (new_manifest.requiresharedapexlibs_size() > 0) {
+ return Error() << new_apex.GetPath() << " requires shared apex libs";
+ }
+
+ // We don't allow non-staged updates of APEXES that have java libs inside.
+ if (new_manifest.jnilibs_size() > 0) {
+ return Error() << new_apex.GetPath() << " requires JNI libs";
+ }
+
+ // For requireNativeLibs bit, we only allow updates that don't change list of
+ // required libs.
+
+ std::vector<std::string> cur_required_libs(
+ cur_manifest.requirenativelibs().begin(),
+ cur_manifest.requirenativelibs().end());
+ sort(cur_required_libs.begin(), cur_required_libs.end());
+
+ std::vector<std::string> new_required_libs(
+ new_manifest.requirenativelibs().begin(),
+ new_manifest.requirenativelibs().end());
+ sort(new_required_libs.begin(), new_required_libs.end());
+
+ if (cur_required_libs != new_required_libs) {
+ return Error() << "Set of native libs required by " << new_apex.GetPath()
+ << " differs from the one required by the currently active "
+ << cur_apex.GetPath();
+ }
+
+ auto expected_public_key =
+ ApexFileRepository::GetInstance().GetPublicKey(new_manifest.name());
+ if (!expected_public_key.ok()) {
+ return expected_public_key.error();
+ }
+ auto verity_data = new_apex.VerifyApexVerity(*expected_public_key);
+ if (!verity_data.ok()) {
+ return verity_data.error();
+ }
+ // Supporting non-staged install of APEXes without a hashtree is additional
+ // hassle, it's easier not to support it.
+ if (verity_data->desc->tree_size == 0) {
+ return Error() << new_apex.GetPath()
+ << " does not have an embedded hash tree";
+ }
+ return {};
+}
+
+Result<size_t> ComputePackageIdMinor(const ApexFile& apex) {
+ static constexpr size_t kMaxVerityDevicesPerApexName = 3u;
+ DeviceMapper& dm = DeviceMapper::Instance();
+ std::vector<DeviceMapper::DmBlockDevice> dm_devices;
+ if (!dm.GetAvailableDevices(&dm_devices)) {
+ return Error() << "Failed to list dm devices";
+ }
+ size_t devices = 0;
+ size_t next_minor = 1;
+ for (const auto& dm_device : dm_devices) {
+ std::string_view dm_name(dm_device.name());
+ // Format is <module_name>@<version_code>[_<minor>]
+ if (!ConsumePrefix(&dm_name, apex.GetManifest().name())) {
+ continue;
+ }
+ devices++;
+ auto pos = dm_name.find_last_of('_');
+ if (pos == std::string_view::npos) {
+ continue;
+ }
+ size_t minor;
+ if (!ParseUint(std::string(dm_name.substr(pos + 1)), &minor)) {
+ return Error() << "Unexpected dm device name " << dm_device.name();
+ }
+ if (next_minor < minor + 1) {
+ next_minor = minor + 1;
+ }
+ }
+ if (devices > kMaxVerityDevicesPerApexName) {
+ return Error() << "There are too many (" << devices
+ << ") dm block devices associated with package "
+ << apex.GetManifest().name();
+ }
+ while (true) {
+ std::string target_file =
+ StringPrintf("%s/%s_%zu.apex", gConfig->active_apex_data_dir,
+ GetPackageId(apex.GetManifest()).c_str(), next_minor);
+ if (access(target_file.c_str(), F_OK) == 0) {
+ next_minor++;
+ } else {
+ break;
+ }
+ }
+
+ return next_minor;
+}
+
+Result<void> UpdateApexInfoList() {
+ std::vector<ApexFile> active(GetActivePackages());
+ std::vector<ApexFile> inactive = CalculateInactivePackages(active);
+
+ std::stringstream xml;
+ CollectApexInfoList(xml, active, inactive);
+
+ std::string name = StringPrintf("%s/.default-%s", kApexRoot, kApexInfoList);
+ unique_fd fd(TEMP_FAILURE_RETRY(
+ open(name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)));
+ if (fd.get() == -1) {
+ return ErrnoError() << "Can't open " << name;
+ }
+ if (!WriteStringToFd(xml.str(), fd)) {
+ return ErrnoError() << "Failed to write to " << name;
+ }
+
+ return {};
+}
+
+Result<ApexFile> InstallPackage(const std::string& package_path) {
+ LOG(INFO) << "Installing " << package_path;
+ auto temp_apex = ApexFile::Open(package_path);
+ if (!temp_apex.ok()) {
+ return temp_apex.error();
+ }
+
+ const std::string& module_name = temp_apex->GetManifest().name();
+ // Don't allow non-staged update if there are no active versions of this APEX.
+ auto cur_mounted_data = gMountedApexes.GetLatestMountedApex(module_name);
+ if (!cur_mounted_data.has_value()) {
+ return Error() << "No active version found for package " << module_name;
+ }
+
+ auto cur_apex = ApexFile::Open(cur_mounted_data->full_path);
+ if (!cur_apex.ok()) {
+ return cur_apex.error();
+ }
+
+ // Do a quick check if this APEX can be installed without a reboot.
+ // Note that passing this check doesn't guarantee that APEX will be
+ // successfully installed.
+ if (auto r = CheckSupportsNonStagedInstall(*cur_apex, *temp_apex); !r.ok()) {
+ return r.error();
+ }
+
+ // 1. Verify that APEX is correct. This is a heavy check that involves
+ // mounting an APEX on a temporary mount point and reading the entire
+ // dm-verity block device.
+ if (auto verify = VerifyPackageNonStagedInstall(*temp_apex); !verify.ok()) {
+ return verify.error();
+ }
+
+ // 2. Compute params for mounting new apex.
+ auto new_id_minor = ComputePackageIdMinor(*temp_apex);
+ if (!new_id_minor.ok()) {
+ return new_id_minor.error();
+ }
+
+ std::string new_id = GetPackageId(temp_apex->GetManifest()) + "_" +
+ std::to_string(*new_id_minor);
+
+ // 2. Unmount currently active APEX.
+ if (auto res = UnmountPackage(*cur_apex, /* allow_latest= */ true,
+ /* deferred= */ true);
+ !res.ok()) {
+ return res.error();
+ }
+
+ // 3. Hard link to final destination.
+ std::string target_file =
+ StringPrintf("%s/%s.apex", gConfig->active_apex_data_dir, new_id.c_str());
+
+ auto guard = android::base::make_scope_guard([&]() {
+ if (unlink(target_file.c_str()) != 0 && errno != ENOENT) {
+ PLOG(ERROR) << "Failed to unlink " << target_file;
+ }
+ // We can't really rely on the fact that dm-verity device backing up
+ // previously active APEX is still around. We need to create a new one.
+ std::string old_new_id = GetPackageId(temp_apex->GetManifest()) + "_" +
+ std::to_string(*new_id_minor + 1);
+ if (auto res = ActivatePackageImpl(*cur_apex, old_new_id); !res.ok()) {
+ // At this point not much we can do... :(
+ LOG(ERROR) << res.error();
+ }
+ });
+
+ // At this point it should be safe to hard link |temp_apex| to
+ // |params->target_file|. In case reboot happens during one of the stages
+ // below, then on next boot apexd will pick up the new verified APEX.
+ if (link(package_path.c_str(), target_file.c_str()) != 0) {
+ return ErrnoError() << "Failed to link " << package_path << " to "
+ << target_file;
+ }
+
+ auto new_apex = ApexFile::Open(target_file);
+ if (!new_apex.ok()) {
+ return new_apex.error();
+ }
+
+ // 4. And activate new one.
+ if (auto res = ActivatePackageImpl(*new_apex, new_id); !res.ok()) {
+ return res.error();
+ }
+
+ // Accept the install.
+ guard.Disable();
+
+ // 4. Now we can unlink old APEX if it's not pre-installed.
+ if (!ApexFileRepository::GetInstance().IsPreInstalledApex(*cur_apex)) {
+ if (unlink(cur_mounted_data->full_path.c_str()) != 0) {
+ PLOG(ERROR) << "Failed to unlink " << cur_mounted_data->full_path;
+ }
+ }
+
+ if (auto res = UpdateApexInfoList(); !res.ok()) {
+ LOG(ERROR) << res.error();
+ }
+
+ // Release compressed blocks in case target_file is on f2fs-compressed
+ // filesystem.
+ ReleaseF2fsCompressedBlocks(target_file);
+
+ return new_apex;
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd.h b/apexd/apexd.h
index 36f72beb..2465bde9 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_APEXD_APEXD_H_
#define ANDROID_APEXD_APEXD_H_
+#include <ostream>
#include <string>
#include <vector>
@@ -24,101 +25,177 @@
#include <android-base/result.h>
#include "apex_constants.h"
+#include "apex_database.h"
#include "apex_file.h"
+#include "apex_file_repository.h"
+#include "apexd_session.h"
namespace android {
namespace apex {
+// A structure containing all the values that might need to be injected for
+// testing (e.g. apexd status property, etc.)
+//
+// Ideally we want to introduce Apexd class and use dependency injection for
+// such values, but that will require a sizeable refactoring. For the time being
+// this config should do the trick.
+struct ApexdConfig {
+ const char* apex_status_sysprop;
+ std::vector<std::string> apex_built_in_dirs;
+ const char* active_apex_data_dir;
+ const char* decompression_dir;
+ const char* ota_reserved_dir;
+ const char* apex_hash_tree_dir;
+ const char* staged_session_dir;
+};
+
+static const ApexdConfig kDefaultConfig = {
+ kApexStatusSysprop, kApexPackageBuiltinDirs, kActiveApexPackagesDataDir,
+ kApexDecompressedDir, kOtaReservedDir, kApexHashTreeDir,
+ kStagedSessionsDir,
+};
+
class CheckpointInterface;
-android::base::Result<void> resumeRevertIfNeeded();
+void SetConfig(const ApexdConfig& config);
+
+// Exposed only for testing.
+android::base::Result<void> Unmount(
+ const MountedApexDatabase::MountedApexData& data, bool deferred);
-// Keep it for now to make otapreopt_chroot keep happy.
-// TODO(b/137086602): remove this function.
-android::base::Result<void> scanPackagesDirAndActivate(
- const char* apex_package_dir);
-void scanStagedSessionsDirAndStage();
-android::base::Result<void> preinstallPackages(
+android::base::Result<void> ResumeRevertIfNeeded();
+
+android::base::Result<void> PreinstallPackages(
const std::vector<std::string>& paths) WARN_UNUSED;
-android::base::Result<void> postinstallPackages(
+android::base::Result<void> PostinstallPackages(
const std::vector<std::string>& paths) WARN_UNUSED;
-android::base::Result<void> stagePackages(
+android::base::Result<void> StagePackages(
const std::vector<std::string>& tmpPaths) WARN_UNUSED;
-android::base::Result<void> unstagePackages(
+android::base::Result<void> UnstagePackages(
const std::vector<std::string>& paths) WARN_UNUSED;
-android::base::Result<std::vector<ApexFile>> submitStagedSession(
+android::base::Result<std::vector<ApexFile>> SubmitStagedSession(
const int session_id, const std::vector<int>& child_session_ids,
const bool has_rollback_enabled, const bool is_rollback,
const int rollback_id) WARN_UNUSED;
-android::base::Result<void> markStagedSessionReady(const int session_id)
+android::base::Result<void> MarkStagedSessionReady(const int session_id)
WARN_UNUSED;
-android::base::Result<void> markStagedSessionSuccessful(const int session_id)
+android::base::Result<void> MarkStagedSessionSuccessful(const int session_id)
WARN_UNUSED;
-android::base::Result<void> revertActiveSessions(
- const std::string& crashing_native_process);
-android::base::Result<void> revertActiveSessionsAndReboot(
- const std::string& crashing_native_process);
-
-android::base::Result<void> activatePackage(const std::string& full_path)
+// Only only of the parameters should be passed during revert
+android::base::Result<void> RevertActiveSessions(
+ const std::string& crashing_native_process,
+ const std::string& error_message);
+// Only only of the parameters should be passed during revert
+android::base::Result<void> RevertActiveSessionsAndReboot(
+ const std::string& crashing_native_process,
+ const std::string& error_message);
+
+android::base::Result<void> ActivatePackage(const std::string& full_path)
WARN_UNUSED;
-android::base::Result<void> deactivatePackage(const std::string& full_path)
+android::base::Result<void> DeactivatePackage(const std::string& full_path)
WARN_UNUSED;
-std::vector<ApexFile> getActivePackages();
-android::base::Result<ApexFile> getActivePackage(
+std::vector<ApexFile> GetActivePackages();
+android::base::Result<ApexFile> GetActivePackage(
const std::string& package_name);
-std::vector<ApexFile> getFactoryPackages();
+std::vector<ApexFile> GetFactoryPackages();
-android::base::Result<void> abortStagedSession(const int session_id);
-android::base::Result<void> abortActiveSession();
+android::base::Result<void> AbortStagedSession(const int session_id);
-android::base::Result<ino_t> snapshotCeData(const int user_id,
- const int rollback_id,
- const std::string& apex_name);
-android::base::Result<void> restoreCeData(const int user_id,
+android::base::Result<void> SnapshotCeData(const int user_id,
+ const int rollback_id,
+ const std::string& apex_name);
+android::base::Result<void> RestoreCeData(const int user_id,
const int rollback_id,
const std::string& apex_name);
-android::base::Result<void> destroyDeSnapshots(const int rollback_id);
-android::base::Result<void> destroyCeSnapshotsNotSpecified(
+
+android::base::Result<void> DestroyDeSnapshots(const int rollback_id);
+android::base::Result<void> DestroyCeSnapshots(const int user_id,
+ const int rollback_id);
+android::base::Result<void> DestroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids);
-int onBootstrap();
-// Small helper function to tell if device is currently booting.
-bool isBooting();
+int OnBootstrap();
// Sets the values of gVoldService and gInFsCheckpointMode.
-void initializeVold(CheckpointInterface* checkpoint_service);
+void InitializeVold(CheckpointInterface* checkpoint_service);
// Initializes in-memory state (e.g. pre-installed data, activated apexes).
// Must be called first before calling any other boot sequence related function.
-void initialize(CheckpointInterface* checkpoint_service);
+void Initialize(CheckpointInterface* checkpoint_service);
+// Initializes data apex as in-memory state. Should be called only if we are
+// not booting, since initialization timing is different when booting
+void InitializeDataApex();
// Migrates sessions from /data/apex/session to /metadata/session.i
// Must only be called during boot (i.e apexd.status is not "ready" or
// "activated").
-android::base::Result<void> migrateSessionsDirIfNeeded();
+android::base::Result<void> MigrateSessionsDirIfNeeded();
// Apex activation logic. Scans staged apex sessions and activates apexes.
// Must only be called during boot (i.e apexd.status is not "ready" or
// "activated").
-void onStart();
+void OnStart();
+// For every package X, there can be at most two APEX, pre-installed vs
+// installed on data. We decide which ones should be activated and return them
+// as a list
+std::vector<ApexFileRef> SelectApexForActivation(
+ const std::unordered_map<std::string, std::vector<ApexFileRef>>& all_apex,
+ const ApexFileRepository& instance);
+std::vector<ApexFile> ProcessCompressedApex(
+ const std::vector<ApexFileRef>& compressed_apex, bool is_ota_chroot);
+// Validate |apex| is same as |capex|
+android::base::Result<void> ValidateDecompressedApex(const ApexFile& capex,
+ const ApexFile& apex);
// Notifies system that apexes are activated by setting apexd.status property to
// "activated".
// Must only be called during boot (i.e. apexd.status is not "ready" or
// "activated").
-void onAllPackagesActivated();
+void OnAllPackagesActivated(bool is_bootstrap);
// Notifies system that apexes are ready by setting apexd.status property to
// "ready".
// Must only be called during boot (i.e. apexd.status is not "ready" or
// "activated").
-void onAllPackagesReady();
-void bootCompletedCleanup();
-int snapshotOrRestoreDeUserData();
+void OnAllPackagesReady();
+void OnBootCompleted();
+// Exposed for testing
+void RemoveInactiveDataApex();
+void BootCompletedCleanup();
+int SnapshotOrRestoreDeUserData();
+
+int UnmountAll();
-int unmountAll();
+android::base::Result<MountedApexDatabase::MountedApexData>
+GetTempMountedApexData(const std::string& package);
// Optimistically tries to remount as many APEX packages as possible.
// For more documentation see corresponding binder call in IApexService.aidl.
-android::base::Result<void> remountPackages();
+android::base::Result<void> RemountPackages();
+
+// Exposed for unit tests
+android::base::Result<bool> ShouldAllocateSpaceForDecompression(
+ const std::string& new_apex_name, int64_t new_apex_version,
+ const ApexFileRepository& instance);
+
+void CollectApexInfoList(std::ostream& os,
+ const std::vector<ApexFile>& active_apexs,
+ const std::vector<ApexFile>& inactive_apexs);
+
+// Reserve |size| bytes in |dest_dir| by creating a zero-filled file
+android::base::Result<void> ReserveSpaceForCompressedApex(
+ int64_t size, const std::string& dest_dir);
+
+// Activates apexes in otapreot_chroot environment.
+// TODO(b/172911822): support compressed apexes.
+int OnOtaChrootBootstrap();
+
+// Activates flattened apexes in otapreopt_chroot environment.
+int OnOtaChrootBootstrapFlattenedApex();
+
+android::apex::MountedApexDatabase& GetApexDatabaseForTesting();
+
+// Performs a non-staged install of an APEX specified by |package_path|.
+// TODO(ioffe): add more documentation.
+android::base::Result<ApexFile> InstallPackage(const std::string& package_path);
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_checkpoint.h b/apexd/apexd_checkpoint.h
index 7177c047..2b4c3a12 100644
--- a/apexd/apexd_checkpoint.h
+++ b/apexd/apexd_checkpoint.h
@@ -32,7 +32,7 @@ class CheckpointInterface {
virtual android::base::Result<bool> NeedsCheckpoint() = 0;
virtual android::base::Result<bool> NeedsRollback() = 0;
- virtual android::base::Result<void> StartCheckpoint(int32_t numRetries) = 0;
+ virtual android::base::Result<void> StartCheckpoint(int32_t num_retries) = 0;
virtual android::base::Result<void> AbortChanges(const std::string& msg,
bool retry) = 0;
diff --git a/apexd/apexd_checkpoint_vold.cpp b/apexd/apexd_checkpoint_vold.cpp
index ca29209d..8a8d0ff1 100644
--- a/apexd/apexd_checkpoint_vold.cpp
+++ b/apexd/apexd_checkpoint_vold.cpp
@@ -31,11 +31,11 @@ namespace android {
namespace apex {
Result<VoldCheckpointInterface> VoldCheckpointInterface::Create() {
- auto voldService =
+ auto vold_service =
defaultServiceManager()->getService(android::String16("vold"));
- if (voldService != nullptr) {
+ if (vold_service != nullptr) {
return VoldCheckpointInterface(
- android::interface_cast<android::os::IVold>(voldService));
+ android::interface_cast<android::os::IVold>(vold_service));
}
return Errorf("Failed to retrieve vold service.");
}
@@ -91,9 +91,10 @@ Result<bool> VoldCheckpointInterface::NeedsRollback() {
return false;
}
-Result<void> VoldCheckpointInterface::StartCheckpoint(int32_t numRetries) {
+Result<void> VoldCheckpointInterface::StartCheckpoint(int32_t num_retries) {
if (supports_fs_checkpoints_) {
- android::binder::Status status = vold_service_->startCheckpoint(numRetries);
+ android::binder::Status status =
+ vold_service_->startCheckpoint(num_retries);
if (!status.isOk()) {
return Error() << status.toString8().c_str();
}
diff --git a/apexd/apexd_checkpoint_vold.h b/apexd/apexd_checkpoint_vold.h
index dbd190da..f547532e 100644
--- a/apexd/apexd_checkpoint_vold.h
+++ b/apexd/apexd_checkpoint_vold.h
@@ -44,7 +44,7 @@ class VoldCheckpointInterface : public CheckpointInterface {
android::base::Result<void> StartCheckpoint(int32_t retry) override;
android::base::Result<void> AbortChanges(const std::string& msg,
- bool numRetries) override;
+ bool num_retries) override;
static android::base::Result<VoldCheckpointInterface> Create();
diff --git a/apexd/apexd_prop.cpp b/apexd/apexd_lifecycle.cpp
index 31030e10..41c1abef 100644
--- a/apexd/apexd_prop.cpp
+++ b/apexd/apexd_lifecycle.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -16,23 +16,28 @@
#define LOG_TAG "apexd"
-#include "apexd_prop.h"
+#include "apexd_lifecycle.h"
#include <android-base/logging.h>
#include <android-base/properties.h>
#include "apexd_utils.h"
-using android::base::GetBoolProperty;
using android::base::GetProperty;
using android::base::Result;
using android::base::WaitForProperty;
namespace android {
namespace apex {
-void waitForBootStatus(Result<void> (&revert_fn)(const std::string&),
- void (&complete_fn)()) {
- while (!GetBoolProperty("sys.boot_completed", false)) {
+
+bool ApexdLifecycle::IsBooting() {
+ auto status = GetProperty(kApexStatusSysprop, "");
+ return status != kApexStatusReady && status != kApexStatusActivated;
+}
+
+void ApexdLifecycle::WaitForBootStatus(
+ Result<void> (&revert_fn)(const std::string&, const std::string&)) {
+ while (!boot_completed_) {
// Check for change in either crashing property or sys.boot_completed
// Wait for updatable_crashing property change for most of the time
// (arbitrary 30s), briefly check if boot has completed successfully,
@@ -44,7 +49,7 @@ void waitForBootStatus(Result<void> (&revert_fn)(const std::string&),
auto name = GetProperty("sys.init.updatable_crashing_process_name", "");
LOG(ERROR) << "Native process '" << (name.empty() ? "[unknown]" : name)
<< "' is crashing. Attempting a revert";
- auto result = revert_fn(name);
+ auto result = revert_fn(name, "");
if (!result.ok()) {
LOG(ERROR) << "Revert failed : " << result.error();
break;
@@ -59,15 +64,9 @@ void waitForBootStatus(Result<void> (&revert_fn)(const std::string&),
}
}
}
- // Wait for boot to complete, and then run complete_fn.
- // TODO(ioffe): this is a hack, instead we should have a binder call from
- // system_server into apexd when boot completes.
- if (WaitForProperty("sys.boot_completed", "1", std::chrono::minutes(5))) {
- complete_fn();
- return;
- } else {
- LOG(ERROR) << "Boot never completed";
- }
}
+
+void ApexdLifecycle::MarkBootCompleted() { boot_completed_ = true; }
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_lifecycle.h b/apexd/apexd_lifecycle.h
new file mode 100644
index 00000000..ecdef96e
--- /dev/null
+++ b/apexd/apexd_lifecycle.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef ANDROID_APEXD_APEXD_LIFECYCLE_H_
+#define ANDROID_APEXD_APEXD_LIFECYCLE_H_
+
+#include <android-base/result.h>
+
+namespace android {
+namespace apex {
+
+class ApexdLifecycle {
+ private:
+ ApexdLifecycle(){};
+ std::atomic<bool> boot_completed_;
+
+ // Non-copyable && non-moveable.
+ ApexdLifecycle(const ApexdLifecycle&) = delete;
+ ApexdLifecycle& operator=(const ApexdLifecycle&) = delete;
+ ApexdLifecycle& operator=(ApexdLifecycle&&) = delete;
+
+ public:
+ static ApexdLifecycle& GetInstance() {
+ static ApexdLifecycle instance;
+ return instance;
+ }
+ bool IsBooting();
+ void MarkBootCompleted();
+ void WaitForBootStatus(android::base::Result<void> (&rollback_fn)(
+ const std::string&, const std::string&));
+};
+} // namespace apex
+} // namespace android
+
+#endif // ANDROID_APEXD_APEXD_LIFECYCLE_H
diff --git a/apexd/apexd_loop.cpp b/apexd/apexd_loop.cpp
index 568eb055..08805eaf 100644
--- a/apexd/apexd_loop.cpp
+++ b/apexd/apexd_loop.cpp
@@ -18,29 +18,49 @@
#include "apexd_loop.h"
+#include <mutex>
+
#include <dirent.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "apexd_utils.h"
#include "string_log.h"
+using android::base::Basename;
+using android::base::ErrnoError;
using android::base::Error;
+using android::base::GetBoolProperty;
+using android::base::ParseUint;
using android::base::Result;
using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
+#ifndef LOOP_CONFIGURE
+// These can be removed whenever we pull in the Linux v5.8 UAPI headers
+struct loop_config {
+ __u32 fd;
+ __u32 block_size;
+ struct loop_info64 info;
+ __u64 __reserved[8];
+};
+#define LOOP_CONFIGURE 0x4C0A
+#endif
+
namespace android {
namespace apex {
namespace loop {
@@ -66,13 +86,9 @@ void LoopbackDeviceUniqueFd::MaybeCloseBad() {
}
}
-Result<void> configureReadAhead(const std::string& device_path) {
- auto pos = device_path.find("/dev/block/");
- if (pos != 0) {
- return Error() << "Device path does not start with /dev/block.";
- }
- pos = device_path.find_last_of('/');
- std::string device_name = device_path.substr(pos + 1, std::string::npos);
+Result<void> ConfigureReadAhead(const std::string& device_path) {
+ CHECK(StartsWith(device_path, "/dev/"));
+ std::string device_name = Basename(device_path);
std::string sysfs_device =
StringPrintf("/sys/block/%s/queue/read_ahead_kb", device_name.c_str());
@@ -90,10 +106,10 @@ Result<void> configureReadAhead(const std::string& device_path) {
return {};
}
-Result<void> preAllocateLoopDevices(size_t num) {
- Result<void> loopReady = WaitForFile("/dev/loop-control", 20s);
- if (!loopReady.ok()) {
- return loopReady;
+Result<void> PreAllocateLoopDevices(size_t num) {
+ Result<void> loop_ready = WaitForFile("/dev/loop-control", 20s);
+ if (!loop_ready.ok()) {
+ return loop_ready;
}
unique_fd ctl_fd(
TEMP_FAILURE_RETRY(open("/dev/loop-control", O_RDWR | O_CLOEXEC)));
@@ -101,13 +117,30 @@ Result<void> preAllocateLoopDevices(size_t num) {
return ErrnoError() << "Failed to open loop-control";
}
+ bool found = false;
+ size_t start_id = 0;
+ constexpr const char* kLoopPrefix = "loop";
+ WalkDir("/dev/block", [&](const std::filesystem::directory_entry& entry) {
+ std::string devname = entry.path().filename().string();
+ if (StartsWith(devname, kLoopPrefix)) {
+ size_t id;
+ auto parse_ok = ParseUint(
+ devname.substr(std::char_traits<char>::length(kLoopPrefix)), &id);
+ if (parse_ok && id > start_id) {
+ start_id = id;
+ found = true;
+ }
+ }
+ });
+ if (found) ++start_id;
+
// Assumption: loop device ID [0..num) is valid.
// This is because pre-allocation happens during bootstrap.
// Anyway Kernel pre-allocated loop devices
// as many as CONFIG_BLK_DEV_LOOP_MIN_COUNT,
// Within the amount of kernel-pre-allocation,
// LOOP_CTL_ADD will fail with EEXIST
- for (size_t id = 0ul; id < num; ++id) {
+ for (size_t id = start_id; id < num + start_id; ++id) {
int ret = ioctl(ctl_fd.get(), LOOP_CTL_ADD, id);
if (ret < 0 && errno != EEXIST) {
return ErrnoError() << "Failed LOOP_CTL_ADD";
@@ -120,25 +153,30 @@ Result<void> preAllocateLoopDevices(size_t num) {
// just optimistally hope that they are all created when we actually
// access them for activating APEXes. If the dev nodes are not ready
// even then, we wait 50ms and warning message will be printed (see below
- // createLoopDevice()).
+ // CreateLoopDevice()).
LOG(INFO) << "Pre-allocated " << num << " loopback devices";
return {};
}
-Result<LoopbackDeviceUniqueFd> createLoopDevice(const std::string& target,
- const int32_t imageOffset,
- const size_t imageSize) {
- unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
- if (ctl_fd.get() == -1) {
- return ErrnoError() << "Failed to open loop-control";
- }
-
- int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
- if (num == -1) {
- return ErrnoError() << "Failed LOOP_CTL_GET_FREE";
- }
-
- std::string device = StringPrintf("/dev/block/loop%d", num);
+Result<void> ConfigureLoopDevice(const int device_fd, const std::string& target,
+ const int32_t image_offset,
+ const size_t image_size) {
+ static bool use_loop_configure;
+ static std::once_flag once_flag;
+ std::call_once(once_flag, [&]() {
+ // LOOP_CONFIGURE is a new ioctl in Linux 5.8 (and backported in Android
+ // common) that allows atomically configuring a loop device. It is a lot
+ // faster than the traditional LOOP_SET_FD/LOOP_SET_STATUS64 combo, but
+ // it may not be available on updating devices, so try once before
+ // deciding.
+ struct loop_config config;
+ memset(&config, 0, sizeof(config));
+ config.fd = -1;
+ if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1 && errno == EBADF) {
+ // If the IOCTL exists, it will fail with EBADF for the -1 fd
+ use_loop_configure = true;
+ }
+ });
/*
* Using O_DIRECT will tell the kernel that we want to use Direct I/O
@@ -151,74 +189,151 @@ Result<LoopbackDeviceUniqueFd> createLoopDevice(const std::string& target,
*/
unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
if (target_fd.get() == -1) {
- return ErrnoError() << "Failed to open " << target;
+ struct statfs stbuf;
+ int saved_errno = errno;
+ // let's give another try with buffered I/O for EROFS and squashfs
+ if (statfs(target.c_str(), &stbuf) != 0 ||
+ (stbuf.f_type != EROFS_SUPER_MAGIC_V1 &&
+ stbuf.f_type != SQUASHFS_MAGIC &&
+ stbuf.f_type != OVERLAYFS_SUPER_MAGIC)) {
+ return Error(saved_errno) << "Failed to open " << target;
+ }
+ LOG(WARNING) << "Fallback to buffered I/O for " << target;
+ target_fd.reset(open(target.c_str(), O_RDONLY | O_CLOEXEC));
+ if (target_fd.get() == -1) {
+ return ErrnoError() << "Failed to open " << target;
+ }
+ }
+
+ struct loop_info64 li;
+ memset(&li, 0, sizeof(li));
+ strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
+ li.lo_offset = image_offset;
+ li.lo_sizelimit = image_size;
+ // Automatically free loop device on last close.
+ li.lo_flags |= LO_FLAGS_AUTOCLEAR;
+
+ if (use_loop_configure) {
+ struct loop_config config;
+ memset(&config, 0, sizeof(config));
+ li.lo_flags |= LO_FLAGS_DIRECT_IO;
+ config.fd = target_fd.get();
+ config.info = li;
+ config.block_size = 4096;
+
+ if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1) {
+ return ErrnoError() << "Failed to LOOP_CONFIGURE";
+ }
+
+ return {};
+ } else {
+ if (ioctl(device_fd, LOOP_SET_FD, target_fd.get()) == -1) {
+ return ErrnoError() << "Failed to LOOP_SET_FD";
+ }
+
+ if (ioctl(device_fd, LOOP_SET_STATUS64, &li) == -1) {
+ return ErrnoError() << "Failed to LOOP_SET_STATUS64";
+ }
+
+ if (ioctl(device_fd, BLKFLSBUF, 0) == -1) {
+ // This works around a kernel bug where the following happens.
+ // 1) The device runs with a value of loop.max_part > 0
+ // 2) As part of LOOP_SET_FD above, we do a partition scan, which loads
+ // the first 2 pages of the underlying file into the buffer cache
+ // 3) When we then change the offset with LOOP_SET_STATUS64, those pages
+ // are not invalidated from the cache.
+ // 4) When we try to mount an ext4 filesystem on the loop device, the ext4
+ // code will try to find a superblock by reading 4k at offset 0; but,
+ // because we still have the old pages at offset 0 lying in the cache,
+ // those pages will be returned directly. However, those pages contain
+ // the data at offset 0 in the underlying file, not at the offset that
+ // we configured
+ // 5) the ext4 driver fails to find a superblock in the (wrong) data, and
+ // fails to mount the filesystem.
+ //
+ // To work around this, explicitly flush the block device, which will
+ // flush the buffer cache and make sure we actually read the data at the
+ // correct offset.
+ return ErrnoError() << "Failed to flush buffers on the loop device";
+ }
+
+ // Direct-IO requires the loop device to have the same block size as the
+ // underlying filesystem.
+ if (ioctl(device_fd, LOOP_SET_BLOCK_SIZE, 4096) == -1) {
+ PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
+ }
}
- LoopbackDeviceUniqueFd device_fd;
- {
- // See comment on kLoopDeviceRetryAttempts.
- unique_fd sysfs_fd;
- for (size_t i = 0; i != kLoopDeviceRetryAttempts; ++i) {
+ return {};
+}
+
+Result<LoopbackDeviceUniqueFd> WaitForDevice(int num) {
+ std::string opened_device;
+ const std::vector<std::string> candidate_devices = {
+ StringPrintf("/dev/block/loop%d", num),
+ StringPrintf("/dev/loop%d", num),
+ };
+
+ // apexd-bootstrap runs in parallel with ueventd to optimize boot time. In
+ // rare cases apexd would try attempt to mount an apex before ueventd created
+ // a loop device for it. To work around this we keep polling for loop device
+ // to be created until ueventd's cold boot sequence is done.
+ // See comment on kLoopDeviceRetryAttempts.
+ unique_fd sysfs_fd;
+ bool cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
+ for (size_t i = 0; i != kLoopDeviceRetryAttempts; ++i) {
+ if (!cold_boot_done) {
+ cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
+ }
+ for (const auto& device : candidate_devices) {
sysfs_fd.reset(open(device.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() != -1) {
- break;
+ return LoopbackDeviceUniqueFd(std::move(sysfs_fd), device);
}
- PLOG(WARNING) << "Loopback device " << device
- << " not ready. Waiting 50ms...";
- usleep(50000);
}
- if (sysfs_fd.get() == -1) {
- return ErrnoError() << "Failed to open " << device;
+ PLOG(WARNING) << "Loopback device " << num << " not ready. Waiting 50ms...";
+ usleep(50000);
+ if (!cold_boot_done) {
+ // ueventd hasn't finished cold boot yet, keep trying.
+ i = 0;
}
- device_fd = LoopbackDeviceUniqueFd(std::move(sysfs_fd), device);
- CHECK_NE(device_fd.get(), -1);
}
- if (ioctl(device_fd.get(), LOOP_SET_FD, target_fd.get()) == -1) {
- return ErrnoError() << "Failed to LOOP_SET_FD";
+ return Error() << "Faled to open loopback device " << num;
+}
+
+Result<LoopbackDeviceUniqueFd> CreateLoopDevice(const std::string& target,
+ const int32_t image_offset,
+ const size_t image_size) {
+ unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
+ if (ctl_fd.get() == -1) {
+ return ErrnoError() << "Failed to open loop-control";
+ }
+
+ static std::mutex mlock;
+ std::lock_guard lock(mlock);
+ int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
+ if (num == -1) {
+ return ErrnoError() << "Failed LOOP_CTL_GET_FREE";
}
- struct loop_info64 li;
- memset(&li, 0, sizeof(li));
- strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
- li.lo_offset = imageOffset;
- li.lo_sizelimit = imageSize;
- if (ioctl(device_fd.get(), LOOP_SET_STATUS64, &li) == -1) {
- return ErrnoError() << "Failed to LOOP_SET_STATUS64";
- }
-
- if (ioctl(device_fd.get(), BLKFLSBUF, 0) == -1) {
- // This works around a kernel bug where the following happens.
- // 1) The device runs with a value of loop.max_part > 0
- // 2) As part of LOOP_SET_FD above, we do a partition scan, which loads
- // the first 2 pages of the underlying file into the buffer cache
- // 3) When we then change the offset with LOOP_SET_STATUS64, those pages
- // are not invalidated from the cache.
- // 4) When we try to mount an ext4 filesystem on the loop device, the ext4
- // code will try to find a superblock by reading 4k at offset 0; but,
- // because we still have the old pages at offset 0 lying in the cache,
- // those pages will be returned directly. However, those pages contain
- // the data at offset 0 in the underlying file, not at the offset that
- // we configured
- // 5) the ext4 driver fails to find a superblock in the (wrong) data, and
- // fails to mount the filesystem.
- //
- // To work around this, explicitly flush the block device, which will flush
- // the buffer cache and make sure we actually read the data at the correct
- // offset.
- return ErrnoError() << "Failed to flush buffers on the loop device";
- }
-
- // Direct-IO requires the loop device to have the same block size as the
- // underlying filesystem.
- if (ioctl(device_fd.get(), LOOP_SET_BLOCK_SIZE, 4096) == -1) {
- PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
- }
-
- Result<void> readAheadStatus = configureReadAhead(device);
- if (!readAheadStatus.ok()) {
- return readAheadStatus.error();
- }
- return device_fd;
+ Result<LoopbackDeviceUniqueFd> loop_device = WaitForDevice(num);
+ if (!loop_device.ok()) {
+ return loop_device.error();
+ }
+ CHECK_NE(loop_device->device_fd.get(), -1);
+
+ Result<void> configureStatus = ConfigureLoopDevice(
+ loop_device->device_fd.get(), target, image_offset, image_size);
+ if (!configureStatus.ok()) {
+ return configureStatus.error();
+ }
+
+ Result<void> read_ahead_status = ConfigureReadAhead(loop_device->name);
+ if (!read_ahead_status.ok()) {
+ return read_ahead_status.error();
+ }
+
+ return loop_device;
}
void DestroyLoopDevice(const std::string& path, const DestroyLoopFn& extra) {
diff --git a/apexd/apexd_loop.h b/apexd/apexd_loop.h
index 8341675d..c727944a 100644
--- a/apexd/apexd_loop.h
+++ b/apexd/apexd_loop.h
@@ -38,7 +38,7 @@ struct LoopbackDeviceUniqueFd {
: device_fd(std::move(fd)), name(name) {}
LoopbackDeviceUniqueFd(LoopbackDeviceUniqueFd&& fd) noexcept
- : device_fd(std::move(fd.device_fd)), name(fd.name) {}
+ : device_fd(std::move(fd.device_fd)), name(std::move(fd.name)) {}
LoopbackDeviceUniqueFd& operator=(LoopbackDeviceUniqueFd&& other) noexcept {
MaybeCloseBad();
device_fd = std::move(other.device_fd);
@@ -52,16 +52,16 @@ struct LoopbackDeviceUniqueFd {
void CloseGood() { device_fd.reset(-1); }
- int get() { return device_fd.get(); }
+ int Get() { return device_fd.get(); }
};
-android::base::Result<void> configureReadAhead(const std::string& device_path);
+android::base::Result<void> ConfigureReadAhead(const std::string& device_path);
-android::base::Result<void> preAllocateLoopDevices(size_t num);
+android::base::Result<void> PreAllocateLoopDevices(size_t num);
-android::base::Result<LoopbackDeviceUniqueFd> createLoopDevice(
- const std::string& target, const int32_t imageOffset,
- const size_t imageSize);
+android::base::Result<LoopbackDeviceUniqueFd> CreateLoopDevice(
+ const std::string& target, const int32_t image_offset,
+ const size_t image_size);
using DestroyLoopFn =
std::function<void(const std::string&, const std::string&)>;
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index c60f4faf..26d5732e 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -17,14 +17,15 @@
#define LOG_TAG "apexd"
#include <strings.h>
+#include <sys/stat.h>
#include <ApexProperties.sysprop.h>
#include <android-base/logging.h>
#include "apexd.h"
#include "apexd_checkpoint_vold.h"
+#include "apexd_lifecycle.h"
#include "apexd_prepostinstall.h"
-#include "apexd_prop.h"
#include "apexservice.h"
#include <android-base/properties.h>
@@ -44,12 +45,17 @@ int HandleSubcommand(char** argv) {
if (strcmp("--bootstrap", argv[1]) == 0) {
LOG(INFO) << "Bootstrap subcommand detected";
- return android::apex::onBootstrap();
+ return android::apex::OnBootstrap();
}
if (strcmp("--unmount-all", argv[1]) == 0) {
LOG(INFO) << "Unmount all subcommand detected";
- return android::apex::unmountAll();
+ return android::apex::UnmountAll();
+ }
+
+ if (strcmp("--otachroot-bootstrap", argv[1]) == 0) {
+ LOG(INFO) << "OTA chroot bootstrap subcommand detected";
+ return android::apex::OnOtaChrootBootstrap();
}
if (strcmp("--snapshotde", argv[1]) == 0) {
@@ -62,16 +68,16 @@ int HandleSubcommand(char** argv) {
LOG(ERROR) << "Could not retrieve vold service: "
<< vold_service_st.error();
} else {
- android::apex::initializeVold(&*vold_service_st);
+ android::apex::InitializeVold(&*vold_service_st);
}
- int result = android::apex::snapshotOrRestoreDeUserData();
+ int result = android::apex::SnapshotOrRestoreDeUserData();
if (result == 0) {
// Notify other components (e.g. init) that all APEXs are ready to be used
// Note that it's important that the binder service is registered at this
// point, since other system services might depend on it.
- android::apex::onAllPackagesReady();
+ android::apex::OnAllPackagesReady();
}
return result;
}
@@ -98,20 +104,40 @@ void InstallSigtermSignalHandler() {
int main(int /*argc*/, char** argv) {
android::base::InitLogging(argv, &android::base::KernelLogger);
- // TODO: add a -v flag or an external setting to change LogSeverity.
- android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+ // TODO(b/158468454): add a -v flag or an external setting to change severity.
+ android::base::SetMinimumLogSeverity(android::base::INFO);
+
+ // set umask to 022 so that files/dirs created are accessible to other
+ // processes e.g.) apex-info-file.xml is supposed to be read by other
+ // processes
+ umask(022);
InstallSigtermSignalHandler();
+ android::apex::SetConfig(android::apex::kDefaultConfig);
+
+ android::apex::ApexdLifecycle& lifecycle =
+ android::apex::ApexdLifecycle::GetInstance();
+ bool booting = lifecycle.IsBooting();
+
const bool has_subcommand = argv[1] != nullptr;
if (!android::sysprop::ApexProperties::updatable().value_or(false)) {
- LOG(INFO) << "This device does not support updatable APEX. Exiting";
if (!has_subcommand) {
- // mark apexd as activated so that init can proceed
- android::apex::onAllPackagesActivated();
+ if (!booting) {
+ // We've finished booting, but for some reason somebody tried to start
+ // apexd. Simply exit.
+ return 0;
+ }
+
+ LOG(INFO) << "This device does not support updatable APEX. Exiting";
+ // Mark apexd as activated so that init can proceed.
+ android::apex::OnAllPackagesActivated(/*is_bootstrap=*/false);
} else if (strcmp("--snapshotde", argv[1]) == 0) {
+ LOG(INFO) << "This device does not support updatable APEX. Exiting";
// mark apexd as ready
- android::apex::onAllPackagesReady();
+ android::apex::OnAllPackagesReady();
+ } else if (strcmp("--otachroot-bootstrap", argv[1]) == 0) {
+ return android::apex::OnOtaChrootBootstrapFlattenedApex();
}
return 0;
}
@@ -129,12 +155,21 @@ int main(int /*argc*/, char** argv) {
} else {
vold_service = &*vold_service_st;
}
- android::apex::initialize(vold_service);
+ android::apex::Initialize(vold_service);
- bool booting = android::apex::isBooting();
if (booting) {
- android::apex::migrateSessionsDirIfNeeded();
- android::apex::onStart();
+ if (auto res = android::apex::MigrateSessionsDirIfNeeded(); !res.ok()) {
+ LOG(ERROR) << "Failed to migrate sessions to /metadata partition : "
+ << res.error();
+ }
+ android::apex::OnStart();
+ } else {
+ // TODO(b/172911822): Trying to use data apex related ApexFileRepository
+ // apis without initializing it should throw error. Also, unit tests should
+ // not pass without initialization.
+ // TODO(b/172911822): Consolidate this with Initialize() when
+ // ApexFileRepository can act as cache and re-scanning is not expensive
+ android::apex::InitializeDataApex();
}
android::apex::binder::CreateAndRegisterService();
android::apex::binder::StartThreadPool();
@@ -146,10 +181,8 @@ int main(int /*argc*/, char** argv) {
// themselves should wait for the ready status instead, which is set when
// the "--snapshotde" subcommand is received and snapshot/restore is
// complete.
- android::apex::onAllPackagesActivated();
- android::apex::waitForBootStatus(
- android::apex::revertActiveSessionsAndReboot,
- android::apex::bootCompletedCleanup);
+ android::apex::OnAllPackagesActivated(/*is_bootstrap=*/false);
+ lifecycle.WaitForBootStatus(android::apex::RevertActiveSessionsAndReboot);
}
android::apex::binder::AllowServiceShutdown();
diff --git a/apexd/apexd_prepostinstall.cpp b/apexd/apexd_prepostinstall.cpp
index 21b8650a..51b60b4b 100644
--- a/apexd/apexd_prepostinstall.cpp
+++ b/apexd/apexd_prepostinstall.cpp
@@ -35,6 +35,7 @@
#include "apex_database.h"
#include "apex_file.h"
+#include "apex_manifest.h"
#include "apexd.h"
#include "apexd_private.h"
#include "apexd_utils.h"
@@ -42,6 +43,7 @@
using android::base::Error;
using android::base::Result;
+using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
@@ -58,14 +60,12 @@ void CloseSTDDescriptors() {
close(STDERR_FILENO);
}
-// Instead of temp mounting inside this fuction, we can make a caller do it.
-// This will align with the plan of extending temp mounting to provide a
-// way to run additional pre-reboot verification of an APEX.
-// TODO(ioffe): pass mount points instead of apex files.
template <typename Fn>
-Result<void> StageFnInstall(const std::vector<ApexFile>& apexes, Fn fn,
+Result<void> StageFnInstall(const std::vector<ApexFile>& apexes,
+ const std::vector<std::string>& mount_points, Fn fn,
const char* arg, const char* name) {
- // TODO: Support a session with more than one pre-install hook.
+ // TODO(b/158470023): consider supporting a session with more than one
+ // pre-install hook.
int hook_idx = -1;
for (size_t i = 0; i < apexes.size(); i++) {
if (!(apexes[i].GetManifest().*fn)().empty()) {
@@ -78,73 +78,32 @@ Result<void> StageFnInstall(const std::vector<ApexFile>& apexes, Fn fn,
CHECK(hook_idx != -1);
LOG(VERBOSE) << name << " for " << apexes[hook_idx].GetPath();
- std::vector<MountedApexData> mounted_apexes;
- std::vector<std::string> activation_dirs;
- auto preinstall_guard = android::base::make_scope_guard([&]() {
- for (const auto& mount : mounted_apexes) {
- Result<void> st = apexd_private::Unmount(mount);
- if (!st.ok()) {
- LOG(ERROR) << "Failed to unmount " << mount.full_path << " from "
- << mount.mount_point << " after " << name << ": "
- << st.error();
- }
- }
- for (const std::string& active_point : activation_dirs) {
- if (0 != rmdir(active_point.c_str())) {
- PLOG(ERROR) << "Could not delete temporary active point "
- << active_point;
- }
- }
- });
-
- for (const ApexFile& apex : apexes) {
- // 1) Mount the package.
- std::string mount_point =
- apexd_private::GetPackageTempMountPoint(apex.GetManifest());
-
- auto mount_data = apexd_private::TempMountPackage(apex, mount_point);
- if (!mount_data.ok()) {
- return mount_data.error();
- }
- mounted_apexes.push_back(std::move(*mount_data));
-
- // Given the fact, that we only allow updates of existing APEXes, all the
- // activation points will always be already created. Only scenario, when it
- // won't be the case might be apexservice_test. But even then, it might be
- // safer to move active_point creation logic to run after unshare.
- // TODO(ioffe): move creation of activation points inside RunFnInstall?
- // 2) Ensure there is an activation point, and we will clean it up.
- std::string active_point =
- apexd_private::GetActiveMountPoint(apex.GetManifest());
- if (0 == mkdir(active_point.c_str(), kMkdirMode)) {
- activation_dirs.emplace_back(std::move(active_point));
- } else {
- int saved_errno = errno;
- if (saved_errno != EEXIST) {
- return Error() << "Unable to create mount point" << active_point << ": "
- << strerror(saved_errno);
- }
- }
- }
-
- // 3) Create invocation args.
+ // Create invocation args.
std::vector<std::string> args{
"/system/bin/apexd", arg,
- mounted_apexes[hook_idx].mount_point, // Make the APEX with hook first.
+ mount_points[hook_idx] // Make the APEX with hook first.
};
- for (size_t i = 0; i < mounted_apexes.size(); i++) {
+ for (size_t i = 0; i < mount_points.size(); i++) {
if ((int)i != hook_idx) {
- args.push_back(mounted_apexes[i].mount_point);
+ args.push_back(mount_points[i]);
}
}
- std::string error_msg;
- int res = ForkAndRun(args, &error_msg);
- return res == 0 ? Result<void>{} : Error() << error_msg;
+ return ForkAndRun(args);
}
template <typename Fn>
int RunFnInstall(char** in_argv, Fn fn, const char* name) {
+ std::vector<std::string> activation_dirs;
+ auto preinstall_guard = android::base::make_scope_guard([&]() {
+ for (const std::string& active_point : activation_dirs) {
+ if (0 != rmdir(active_point.c_str())) {
+ PLOG(ERROR) << "Could not delete temporary active point "
+ << active_point;
+ }
+ }
+ });
+
// 1) Unshare.
if (unshare(CLONE_NEWNS) != 0) {
PLOG(ERROR) << "Failed to unshare() for apex " << name;
@@ -160,7 +119,8 @@ int RunFnInstall(char** in_argv, Fn fn, const char* name) {
std::string hook_path;
{
- auto bind_fn = [&fn, name](const std::string& mount_point) {
+ auto bind_fn = [&fn, name,
+ activation_dirs](const std::string& mount_point) mutable {
std::string hook;
std::string active_point;
{
@@ -183,6 +143,14 @@ int RunFnInstall(char** in_argv, Fn fn, const char* name) {
const auto& manifest = *manifest_or;
hook = (manifest.*fn)();
active_point = apexd_private::GetActiveMountPoint(manifest);
+ // Ensure there is an activation point. If not, create one and delete
+ // later.
+ if (0 == mkdir(active_point.c_str(), kMkdirMode)) {
+ activation_dirs.push_back(active_point);
+ } else if (errno != EEXIST) {
+ PLOG(ERROR) << "Unable to create mount point " << active_point;
+ _exit(205);
+ }
}
// 3) Activate the new apex.
@@ -233,17 +201,19 @@ int RunFnInstall(char** in_argv, Fn fn, const char* name) {
} // namespace
-Result<void> StagePreInstall(const std::vector<ApexFile>& apexes) {
- return StageFnInstall(apexes, &ApexManifest::preinstallhook, "--pre-install",
- "pre-install");
+Result<void> StagePreInstall(const std::vector<ApexFile>& apexes,
+ const std::vector<std::string>& mount_points) {
+ return StageFnInstall(apexes, mount_points, &ApexManifest::preinstallhook,
+ "--pre-install", "pre-install");
}
int RunPreInstall(char** in_argv) {
return RunFnInstall(in_argv, &ApexManifest::preinstallhook, "pre-install");
}
-Result<void> StagePostInstall(const std::vector<ApexFile>& apexes) {
- return StageFnInstall(apexes, &ApexManifest::postinstallhook,
+Result<void> StagePostInstall(const std::vector<ApexFile>& apexes,
+ const std::vector<std::string>& mount_points) {
+ return StageFnInstall(apexes, mount_points, &ApexManifest::postinstallhook,
"--post-install", "post-install");
}
diff --git a/apexd/apexd_prepostinstall.h b/apexd/apexd_prepostinstall.h
index 66cd2f57..65125bae 100644
--- a/apexd/apexd_prepostinstall.h
+++ b/apexd/apexd_prepostinstall.h
@@ -27,16 +27,20 @@ namespace apex {
class ApexFile;
-// Temp mounts given apexes and then forks into:
-// apexd --pre-install <mount-point-of-apex-with-hook> [<other-mount-points>]
+// Forks into: apexd --pre-install <mount-point-of-apex-with-hook>
+// [<other-mount-points>] The caller must pass the temp mount point for each
+// apex file.
android::base::Result<void> StagePreInstall(
- const std::vector<ApexFile>& apexes);
+ const std::vector<ApexFile>& apexes,
+ const std::vector<std::string>& mount_points);
int RunPreInstall(char** argv);
-// Temp mounts given apexes and then forks into:
-// apexd --post-install <mount-point-of-apex-with-hook> [<other-mount-points>]
+// Forks into: apexd --post-install <mount-point-of-apex-with-hook>
+// [<other-mount-points>] The caller must pass the temp mount point for each
+// apex file.
android::base::Result<void> StagePostInstall(
- const std::vector<ApexFile>& apexes);
+ const std::vector<ApexFile>& apexes,
+ const std::vector<std::string>& mount_points);
int RunPostInstall(char** argv);
} // namespace apex
diff --git a/apexd/apexd_private.h b/apexd/apexd_private.h
index 1706e08b..b3108cff 100644
--- a/apexd/apexd_private.h
+++ b/apexd/apexd_private.h
@@ -21,6 +21,7 @@
#include <android-base/result.h>
#include "apex_database.h"
+#include "apex_file.h"
#include "apex_manifest.h"
namespace android {
@@ -32,16 +33,16 @@ static constexpr int kMkdirMode = 0755;
namespace apexd_private {
-std::string GetPackageMountPoint(const ApexManifest& manifest);
-std::string GetPackageTempMountPoint(const ApexManifest& manifest);
-std::string GetActiveMountPoint(const ApexManifest& manifest);
+std::string GetPackageMountPoint(const ::apex::proto::ApexManifest& manifest);
+std::string GetPackageTempMountPoint(
+ const ::apex::proto::ApexManifest& manifest);
+std::string GetActiveMountPoint(const ::apex::proto::ApexManifest& manifest);
android::base::Result<void> BindMount(const std::string& target,
const std::string& source);
-android::base::Result<MountedApexDatabase::MountedApexData> TempMountPackage(
- const ApexFile& apex, const std::string& mount_point);
-android::base::Result<void> Unmount(
- const MountedApexDatabase::MountedApexData& data);
+android::base::Result<MountedApexDatabase::MountedApexData>
+GetTempMountedApexData(const std::string& package);
+android::base::Result<void> UnmountTempMount(const ApexFile& apex);
} // namespace apexd_private
} // namespace apex
diff --git a/apexd/apexd_prop.h b/apexd/apexd_prop.h
deleted file mode 100644
index 4b8a7661..00000000
--- a/apexd/apexd_prop.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#ifndef ANDROID_APEXD_APEXD_PROP_H_
-#define ANDROID_APEXD_APEXD_PROP_H_
-
-#include <android-base/result.h>
-
-namespace android {
-namespace apex {
-
-void waitForBootStatus(
- android::base::Result<void> (&rollback_fn)(const std::string&),
- void (&complete_fn)());
-
-} // namespace apex
-} // namespace android
-
-#endif // ANDROID_APEXD_APEXD_PROP_H
diff --git a/apexd/apexd_rollback_utils.h b/apexd/apexd_rollback_utils.h
index 9990be26..c7fa05ec 100644
--- a/apexd/apexd_rollback_utils.h
+++ b/apexd/apexd_rollback_utils.h
@@ -25,10 +25,6 @@
#include <android-base/result.h>
#include <android-base/scopeguard.h>
#include <logwrap/logwrap.h>
-#include <selinux/android.h>
-
-using android::base::Error;
-using android::base::Result;
namespace android {
namespace apex {
@@ -40,7 +36,7 @@ static constexpr const char* kCpPath = "/system/bin/cp";
* path. Note that this will fail if run before APEXes are mounted, due to a
* dependency on runtime.
*/
-int32_t copy_directory_recursive(const char* from, const char* to) {
+inline int32_t CopyDirectoryRecursive(const char* from, const char* to) {
const char* const argv[] = {
kCpPath,
"-F", /* delete any existing destination file first
@@ -62,15 +58,15 @@ int32_t copy_directory_recursive(const char* from, const char* to) {
* from from_path into to_path. Note that this must be run after APEXes are
* mounted.
*/
-inline Result<void> ReplaceFiles(const std::string& from_path,
- const std::string& to_path) {
+inline android::base::Result<void> ReplaceFiles(const std::string& from_path,
+ const std::string& to_path) {
namespace fs = std::filesystem;
std::error_code error_code;
fs::remove_all(to_path, error_code);
if (error_code) {
- return Error() << "Failed to delete existing files at " << to_path << " : "
- << error_code.message();
+ return android::base::Error() << "Failed to delete existing files at "
+ << to_path << " : " << error_code.message();
}
auto deleter = [&] {
@@ -83,23 +79,15 @@ inline Result<void> ReplaceFiles(const std::string& from_path,
};
auto scope_guard = android::base::make_scope_guard(deleter);
- int rc = copy_directory_recursive(from_path.c_str(), to_path.c_str());
+ int rc = CopyDirectoryRecursive(from_path.c_str(), to_path.c_str());
if (rc != 0) {
- return Error() << "Failed to copy from [" << from_path << "] to ["
- << to_path << "]";
+ return android::base::Error() << "Failed to copy from [" << from_path
+ << "] to [" << to_path << "]";
}
scope_guard.Disable();
return {};
}
-inline Result<void> RestoreconPath(const std::string& path) {
- unsigned int seflags = SELINUX_ANDROID_RESTORECON_RECURSE;
- if (selinux_android_restorecon(path.c_str(), seflags) < 0) {
- return Error() << "Failed to restorecon " << path;
- }
- return {};
-}
-
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_session.cpp b/apexd/apexd_session.cpp
index 3d916c48..defef6fa 100644
--- a/apexd/apexd_session.cpp
+++ b/apexd/apexd_session.cpp
@@ -22,6 +22,7 @@
#include "session_state.pb.h"
#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
#include <dirent.h>
#include <sys/stat.h>
@@ -32,6 +33,7 @@
using android::base::Error;
using android::base::Result;
+using android::base::StringPrintf;
using apex::proto::SessionState;
namespace android {
@@ -39,68 +41,57 @@ namespace apex {
namespace {
-static constexpr const char* kStateFileName = "state";
+// Starting from R, apexd prefers /metadata partition (kNewApexSessionsDir) as
+// location for sessions-related information. For devices that don't have
+// /metadata partition, apexd will fallback to the /data one
+// (kOldApexSessionsDir).
+static constexpr const char* kOldApexSessionsDir = "/data/apex/sessions";
+static constexpr const char* kNewApexSessionsDir = "/metadata/apex/sessions";
-std::string getSessionDir(int session_id) {
- return kApexSessionsDir + "/" + std::to_string(session_id);
-}
+static constexpr const char* kStateFileName = "state";
-std::string getSessionStateFilePath(int session_id) {
- return getSessionDir(session_id) + "/" + kStateFileName;
-}
+} // namespace
-Result<std::string> createSessionDirIfNeeded(int session_id) {
- // create /data/sessions
- auto res = createDirIfNeeded(kApexSessionsDir, 0700);
- if (!res.ok()) {
- return res.error();
- }
- // create /data/sessions/session_id
- std::string sessionDir = getSessionDir(session_id);
- res = createDirIfNeeded(sessionDir, 0700);
- if (!res.ok()) {
- return res.error();
- }
+ApexSession::ApexSession(SessionState state) : state_(std::move(state)) {}
- return sessionDir;
+std::string ApexSession::GetSessionsDir() {
+ static std::string result;
+ static std::once_flag once_flag;
+ std::call_once(once_flag, [&]() {
+ auto status =
+ FindFirstExistingDirectory(kNewApexSessionsDir, kOldApexSessionsDir);
+ if (!status.ok()) {
+ LOG(FATAL) << status.error();
+ }
+ result = std::move(*status);
+ });
+ return result;
}
-Result<void> deleteSessionDir(int session_id) {
- std::string session_dir = getSessionDir(session_id);
- LOG(DEBUG) << "Deleting " << session_dir;
- auto path = std::filesystem::path(session_dir);
- std::error_code error_code;
- std::filesystem::remove_all(path, error_code);
- if (error_code) {
- return Error() << "Failed to delete " << session_dir << " : "
- << error_code.message();
- }
- return {};
+Result<void> ApexSession::MigrateToMetadataSessionsDir() {
+ return MoveDir(kOldApexSessionsDir, kNewApexSessionsDir);
}
-} // namespace
-
-ApexSession::ApexSession(SessionState state) : state_(std::move(state)) {}
-
Result<ApexSession> ApexSession::CreateSession(int session_id) {
SessionState state;
// Create session directory
- auto sessionPath = createSessionDirIfNeeded(session_id);
- if (!sessionPath.ok()) {
- return sessionPath.error();
+ std::string session_dir = GetSessionsDir() + "/" + std::to_string(session_id);
+ if (auto status = CreateDirIfNeeded(session_dir, 0700); !status.ok()) {
+ return status.error();
}
state.set_id(session_id);
return ApexSession(state);
}
+
Result<ApexSession> ApexSession::GetSessionFromFile(const std::string& path) {
SessionState state;
- std::fstream stateFile(path, std::ios::in | std::ios::binary);
- if (!stateFile) {
+ std::fstream state_file(path, std::ios::in | std::ios::binary);
+ if (!state_file) {
return Error() << "Failed to open " << path;
}
- if (!state.ParseFromIstream(&stateFile)) {
+ if (!state.ParseFromIstream(&state_file)) {
return Error() << "Failed to parse " << path;
}
@@ -108,7 +99,8 @@ Result<ApexSession> ApexSession::GetSessionFromFile(const std::string& path) {
}
Result<ApexSession> ApexSession::GetSession(int session_id) {
- auto path = getSessionStateFilePath(session_id);
+ auto path = StringPrintf("%s/%d/%s", GetSessionsDir().c_str(), session_id,
+ kStateFileName);
return GetSessionFromFile(path);
}
@@ -116,19 +108,19 @@ Result<ApexSession> ApexSession::GetSession(int session_id) {
std::vector<ApexSession> ApexSession::GetSessions() {
std::vector<ApexSession> sessions;
- Result<std::vector<std::string>> sessionPaths = ReadDir(
- kApexSessionsDir, [](const std::filesystem::directory_entry& entry) {
+ Result<std::vector<std::string>> session_paths = ReadDir(
+ GetSessionsDir(), [](const std::filesystem::directory_entry& entry) {
std::error_code ec;
return entry.is_directory(ec);
});
- if (!sessionPaths.ok()) {
+ if (!session_paths.ok()) {
return sessions;
}
- for (const std::string& sessionDirPath : *sessionPaths) {
+ for (const std::string& session_dir_path : *session_paths) {
// Try to read session state
- auto session = GetSessionFromFile(sessionDirPath + "/" + kStateFileName);
+ auto session = GetSessionFromFile(session_dir_path + "/" + kStateFileName);
if (!session.ok()) {
LOG(WARNING) << session.error();
continue;
@@ -152,20 +144,20 @@ std::vector<ApexSession> ApexSession::GetSessionsInState(
std::vector<ApexSession> ApexSession::GetActiveSessions() {
auto sessions = GetSessions();
- std::vector<ApexSession> activeSessions;
+ std::vector<ApexSession> active_sessions;
for (const ApexSession& session : sessions) {
if (!session.IsFinalized() && session.GetState() != SessionState::UNKNOWN) {
- activeSessions.push_back(session);
+ active_sessions.push_back(session);
}
}
- return activeSessions;
+ return active_sessions;
}
SessionState::State ApexSession::GetState() const { return state_.state(); }
int ApexSession::GetId() const { return state_.id(); }
-std::string ApexSession::GetBuildFingerprint() const {
+const std::string& ApexSession::GetBuildFingerprint() const {
return state_.expected_build_fingerprint();
}
@@ -189,10 +181,14 @@ bool ApexSession::IsRollback() const { return state_.is_rollback(); }
int ApexSession::GetRollbackId() const { return state_.rollback_id(); }
-std::string ApexSession::GetCrashingNativeProcess() const {
+const std::string& ApexSession::GetCrashingNativeProcess() const {
return state_.crashing_native_process();
}
+const std::string& ApexSession::GetErrorMessage() const {
+ return state_.error_message();
+}
+
const google::protobuf::RepeatedField<int> ApexSession::GetChildSessionIds()
const {
return state_.child_session_ids();
@@ -230,6 +226,10 @@ void ApexSession::SetCrashingNativeProcess(
state_.set_crashing_native_process(crashing_process);
}
+void ApexSession::SetErrorMessage(const std::string& error_message) {
+ state_.set_error_message(error_message);
+}
+
void ApexSession::AddApexName(const std::string& apex_name) {
state_.add_apex_names(apex_name);
}
@@ -238,19 +238,29 @@ Result<void> ApexSession::UpdateStateAndCommit(
const SessionState::State& session_state) {
state_.set_state(session_state);
- auto stateFilePath = getSessionStateFilePath(state_.id());
+ auto state_file_path = StringPrintf("%s/%d/%s", GetSessionsDir().c_str(),
+ state_.id(), kStateFileName);
- std::fstream stateFile(stateFilePath,
- std::ios::out | std::ios::trunc | std::ios::binary);
- if (!state_.SerializeToOstream(&stateFile)) {
- return Error() << "Failed to write state file " << stateFilePath;
+ std::fstream state_file(state_file_path,
+ std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!state_.SerializeToOstream(&state_file)) {
+ return Error() << "Failed to write state file " << state_file_path;
}
return {};
}
Result<void> ApexSession::DeleteSession() const {
- return deleteSessionDir(GetId());
+ std::string session_dir = GetSessionsDir() + "/" + std::to_string(GetId());
+ LOG(INFO) << "Deleting " << session_dir;
+ auto path = std::filesystem::path(session_dir);
+ std::error_code error_code;
+ std::filesystem::remove_all(path, error_code);
+ if (error_code) {
+ return Error() << "Failed to delete " << session_dir << " : "
+ << error_code.message();
+ }
+ return {};
}
std::ostream& operator<<(std::ostream& out, const ApexSession& session) {
@@ -259,5 +269,18 @@ std::ostream& operator<<(std::ostream& out, const ApexSession& session) {
<< "]";
}
+void ApexSession::DeleteFinalizedSessions() {
+ auto sessions = GetSessions();
+ for (const ApexSession& session : sessions) {
+ if (!session.IsFinalized()) {
+ continue;
+ }
+ auto result = session.DeleteSession();
+ if (!result.ok()) {
+ LOG(WARNING) << "Failed to delete finalized session: " << session.GetId();
+ }
+ }
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_session.h b/apexd/apexd_session.h
index e0cea91c..5c1f1e3e 100644
--- a/apexd/apexd_session.h
+++ b/apexd/apexd_session.h
@@ -28,10 +28,18 @@
namespace android {
namespace apex {
-static const std::string kApexSessionsDir = "/metadata/apex/sessions";
-
class ApexSession {
public:
+ // Returns top-level directory to store sessions metadata in.
+ // If device has /metadata partition, this will return
+ // /metadata/apex/sessions, on all other devices it will return
+ // /data/apex/sessions.
+ static std::string GetSessionsDir();
+ // Migrates content of /data/apex/sessions to /metadata/apex/sessions.
+ // If device doesn't have /metadata partition this call will be a no-op.
+ // If /data/apex/sessions this call will also be a no-op.
+ static android::base::Result<void> MigrateToMetadataSessionsDir();
+
static android::base::Result<ApexSession> CreateSession(int session_id);
static android::base::Result<ApexSession> GetSession(int session_id);
static std::vector<ApexSession> GetSessions();
@@ -44,8 +52,9 @@ class ApexSession {
const google::protobuf::RepeatedField<int> GetChildSessionIds() const;
::apex::proto::SessionState::State GetState() const;
int GetId() const;
- std::string GetBuildFingerprint() const;
- std::string GetCrashingNativeProcess() const;
+ const std::string& GetBuildFingerprint() const;
+ const std::string& GetCrashingNativeProcess() const;
+ const std::string& GetErrorMessage() const;
bool IsFinalized() const;
bool HasRollbackEnabled() const;
bool IsRollback() const;
@@ -58,15 +67,17 @@ class ApexSession {
void SetIsRollback(const bool is_rollback);
void SetRollbackId(const int rollback_id);
void SetCrashingNativeProcess(const std::string& crashing_process);
+ void SetErrorMessage(const std::string& error_message);
void AddApexName(const std::string& apex_name);
android::base::Result<void> UpdateStateAndCommit(
const ::apex::proto::SessionState::State& state);
android::base::Result<void> DeleteSession() const;
+ static void DeleteFinalizedSessions();
private:
- ApexSession(::apex::proto::SessionState state);
+ explicit ApexSession(::apex::proto::SessionState state);
::apex::proto::SessionState state_;
static android::base::Result<ApexSession> GetSessionFromFile(
diff --git a/apexd/apexd_session_test.cpp b/apexd/apexd_session_test.cpp
new file mode 100644
index 00000000..50d0f858
--- /dev/null
+++ b/apexd/apexd_session_test.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 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 <filesystem>
+#include <fstream>
+#include <string>
+
+#include <errno.h>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+
+#include "apexd_session.h"
+#include "apexd_test_utils.h"
+#include "apexd_utils.h"
+#include "session_state.pb.h"
+
+namespace android {
+namespace apex {
+namespace {
+
+using android::apex::testing::IsOk;
+using android::base::Join;
+using android::base::make_scope_guard;
+
+// TODO(b/170329726): add unit tests for apexd_sessions.h
+
+TEST(ApexdSessionTest, GetSessionsDirSessionsStoredInMetadata) {
+ if (access("/metadata", F_OK) != 0) {
+ GTEST_SKIP() << "Device doesn't have /metadata partition";
+ }
+
+ std::string result = ApexSession::GetSessionsDir();
+ ASSERT_EQ(result, "/metadata/apex/sessions");
+}
+
+TEST(ApexdSessionTest, GetSessionsDirNoMetadataPartitionFallbackToData) {
+ if (access("/metadata", F_OK) == 0) {
+ GTEST_SKIP() << "Device has /metadata partition";
+ }
+
+ std::string result = ApexSession::GetSessionsDir();
+ ASSERT_EQ(result, "/data/apex/sessions");
+}
+
+TEST(ApexdSessionTest, MigrateToMetadataSessionsDir) {
+ namespace fs = std::filesystem;
+
+ if (access("/metadata", F_OK) != 0) {
+ GTEST_SKIP() << "Device doesn't have /metadata partition";
+ }
+
+ // This is ugly, but does the job. To have a truly hermetic unit tests we
+ // need to refactor ApexSession class.
+ for (const auto& entry : fs::directory_iterator("/metadata/apex/sessions")) {
+ fs::remove_all(entry.path());
+ }
+
+ // This is a very ugly test set up, but to have something better we need to
+ // refactor ApexSession class.
+ class TestApexSession {
+ public:
+ TestApexSession(int id, const SessionState::State& state) {
+ path_ = "/data/apex/sessions/" + std::to_string(id);
+ if (auto status = CreateDirIfNeeded(path_, 0700); !status.ok()) {
+ ADD_FAILURE() << "Failed to create " << path_ << " : "
+ << status.error();
+ }
+ SessionState session;
+ session.set_id(id);
+ session.set_state(state);
+ std::fstream state_file(
+ path_ + "/state", std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!session.SerializeToOstream(&state_file)) {
+ ADD_FAILURE() << "Failed to write to " << path_;
+ }
+ }
+
+ ~TestApexSession() { fs::remove_all(path_); }
+
+ private:
+ std::string path_;
+ };
+
+ auto deleter = make_scope_guard([&]() {
+ fs::remove_all("/metadata/apex/sessions/239");
+ fs::remove_all("/metadata/apex/sessions/1543");
+ });
+
+ TestApexSession session1(239, SessionState::SUCCESS);
+ TestApexSession session2(1543, SessionState::ACTIVATION_FAILED);
+
+ ASSERT_TRUE(IsOk(ApexSession::MigrateToMetadataSessionsDir()));
+
+ auto sessions = ApexSession::GetSessions();
+ ASSERT_EQ(2u, sessions.size()) << Join(sessions, ',');
+
+ auto migrated_session_1 = ApexSession::GetSession(239);
+ ASSERT_TRUE(IsOk(migrated_session_1));
+ ASSERT_EQ(SessionState::SUCCESS, migrated_session_1->GetState());
+
+ auto migrated_session_2 = ApexSession::GetSession(1543);
+ ASSERT_TRUE(IsOk(migrated_session_2));
+ ASSERT_EQ(SessionState::ACTIVATION_FAILED, migrated_session_2->GetState());
+}
+
+} // namespace
+} // namespace apex
+} // namespace android
diff --git a/apexd/apexd_test.cpp b/apexd/apexd_test.cpp
new file mode 100644
index 00000000..6e62a7f3
--- /dev/null
+++ b/apexd/apexd_test.cpp
@@ -0,0 +1,3524 @@
+/*
+ * Copyright (C) 2021 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 <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "apex_database.h"
+#include "apex_file_repository.h"
+#include "apexd.h"
+#include "apexd_checkpoint.h"
+#include "apexd_session.h"
+#include "apexd_test_utils.h"
+#include "apexd_utils.h"
+
+#include "apex_manifest.pb.h"
+#include "com_android_apex.h"
+#include "gmock/gmock-matchers.h"
+
+namespace android {
+namespace apex {
+
+namespace fs = std::filesystem;
+
+using MountedApexData = MountedApexDatabase::MountedApexData;
+using android::apex::testing::ApexFileEq;
+using android::apex::testing::IsOk;
+using android::base::GetExecutableDirectory;
+using android::base::GetProperty;
+using android::base::make_scope_guard;
+using android::base::RemoveFileIfExists;
+using android::base::Result;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::base::WriteStringToFile;
+using com::android::apex::testing::ApexInfoXmlEq;
+using ::testing::ByRef;
+using ::testing::HasSubstr;
+using ::testing::IsEmpty;
+using ::testing::StartsWith;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+
+static std::string GetTestDataDir() { return GetExecutableDirectory(); }
+static std::string GetTestFile(const std::string& name) {
+ return GetTestDataDir() + "/" + name;
+}
+
+static int64_t GetMTime(const std::string& path) {
+ struct stat st_buf;
+ if (stat(path.c_str(), &st_buf) != 0) {
+ PLOG(ERROR) << "Failed to stat " << path;
+ return 0;
+ }
+ return st_buf.st_mtime;
+}
+
+// A very basic mock of CheckpointInterface.
+class MockCheckpointInterface : public CheckpointInterface {
+ public:
+ Result<bool> SupportsFsCheckpoints() override { return {}; }
+
+ Result<bool> NeedsCheckpoint() override { return false; }
+
+ Result<bool> NeedsRollback() override { return false; }
+
+ Result<void> StartCheckpoint(int32_t num_retries) override { return {}; }
+
+ Result<void> AbortChanges(const std::string& msg, bool retry) override {
+ return {};
+ }
+};
+
+static constexpr const char* kTestApexdStatusSysprop = "apexd.status.test";
+
+// A test fixture that provides frequently required temp directories for tests
+class ApexdUnitTest : public ::testing::Test {
+ public:
+ ApexdUnitTest() {
+ built_in_dir_ = StringPrintf("%s/pre-installed-apex", td_.path);
+ data_dir_ = StringPrintf("%s/data-apex", td_.path);
+ decompression_dir_ = StringPrintf("%s/decompressed-apex", td_.path);
+ ota_reserved_dir_ = StringPrintf("%s/ota-reserved", td_.path);
+ hash_tree_dir_ = StringPrintf("%s/apex-hash-tree", td_.path);
+ staged_session_dir_ = StringPrintf("%s/staged-session-dir", td_.path);
+ config_ = {kTestApexdStatusSysprop, {built_in_dir_},
+ data_dir_.c_str(), decompression_dir_.c_str(),
+ ota_reserved_dir_.c_str(), hash_tree_dir_.c_str(),
+ staged_session_dir_.c_str()};
+ }
+
+ const std::string& GetBuiltInDir() { return built_in_dir_; }
+ const std::string& GetDataDir() { return data_dir_; }
+ const std::string& GetDecompressionDir() { return decompression_dir_; }
+ const std::string& GetOtaReservedDir() { return ota_reserved_dir_; }
+ const std::string& GetHashTreeDir() { return hash_tree_dir_; }
+ const std::string GetStagedDir(int session_id) {
+ return StringPrintf("%s/session_%d", staged_session_dir_.c_str(),
+ session_id);
+ }
+
+ std::string GetRootDigest(const ApexFile& apex) {
+ if (apex.IsCompressed()) {
+ return "";
+ }
+ auto digest = apex.VerifyApexVerity(apex.GetBundledPublicKey());
+ if (!digest.ok()) {
+ return "";
+ }
+ return digest->root_digest;
+ }
+
+ std::string AddPreInstalledApex(const std::string& apex_name) {
+ fs::copy(GetTestFile(apex_name), built_in_dir_);
+ return StringPrintf("%s/%s", built_in_dir_.c_str(), apex_name.c_str());
+ }
+
+ std::string AddDataApex(const std::string& apex_name) {
+ fs::copy(GetTestFile(apex_name), data_dir_);
+ return StringPrintf("%s/%s", data_dir_.c_str(), apex_name.c_str());
+ }
+
+ std::string AddDataApex(const std::string& apex_name,
+ const std::string& target_name) {
+ fs::copy(GetTestFile(apex_name), data_dir_ + "/" + target_name);
+ return StringPrintf("%s/%s", data_dir_.c_str(), target_name.c_str());
+ }
+
+ // Copies the compressed apex to |built_in_dir| and decompresses it to
+ // |decompressed_dir| and then hard links to |target_dir|
+ std::string PrepareCompressedApex(const std::string& name,
+ const std::string& built_in_dir) {
+ fs::copy(GetTestFile(name), built_in_dir);
+ auto compressed_apex = ApexFile::Open(
+ StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str()));
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex));
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /*is_ota_chroot*/ false);
+ return StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str());
+ }
+
+ std::string PrepareCompressedApex(const std::string& name) {
+ return PrepareCompressedApex(name, built_in_dir_);
+ }
+
+ Result<ApexSession> CreateStagedSession(const std::string& apex_name,
+ int session_id) {
+ CreateDirIfNeeded(GetStagedDir(session_id), 0755);
+ fs::copy(GetTestFile(apex_name), GetStagedDir(session_id));
+ auto result = ApexSession::CreateSession(session_id);
+ result->SetBuildFingerprint(GetProperty("ro.build.fingerprint", ""));
+ return result;
+ }
+
+ protected:
+ void SetUp() override {
+ SetConfig(config_);
+ ApexFileRepository::GetInstance().Reset(decompression_dir_);
+ ASSERT_EQ(mkdir(built_in_dir_.c_str(), 0755), 0);
+ ASSERT_EQ(mkdir(data_dir_.c_str(), 0755), 0);
+ ASSERT_EQ(mkdir(decompression_dir_.c_str(), 0755), 0);
+ ASSERT_EQ(mkdir(ota_reserved_dir_.c_str(), 0755), 0);
+ ASSERT_EQ(mkdir(hash_tree_dir_.c_str(), 0755), 0);
+ ASSERT_EQ(mkdir(staged_session_dir_.c_str(), 0755), 0);
+
+ DeleteDirContent(ApexSession::GetSessionsDir());
+ }
+
+ void TearDown() override { DeleteDirContent(ApexSession::GetSessionsDir()); }
+
+ private:
+ TemporaryDir td_;
+ std::string built_in_dir_;
+ std::string data_dir_;
+ std::string decompression_dir_;
+ std::string ota_reserved_dir_;
+ std::string hash_tree_dir_;
+ std::string staged_session_dir_;
+ ApexdConfig config_;
+};
+
+// Apex that does not have pre-installed version, does not get selected
+TEST_F(ApexdUnitTest, ApexMustHavePreInstalledVersionForSelection) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddPreInstalledApex("com.android.apex.cts.shim.apex");
+ auto shared_lib_1 = ApexFile::Open(AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
+ auto& instance = ApexFileRepository::GetInstance();
+ // Pre-installed data needs to be present so that we can add data apex
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+
+ auto apexd_test_file = ApexFile::Open(AddDataApex("apex.apexd_test.apex"));
+ auto shim_v1 = ApexFile::Open(AddDataApex("com.android.apex.cts.shim.apex"));
+ auto shared_lib_2 = ApexFile::Open(
+ AddDataApex("com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+
+ const auto all_apex = instance.AllApexFilesByName();
+ // Pass a blank instance so that the data apex files are not considered
+ // pre-installed
+ const ApexFileRepository instance_blank;
+ auto result = SelectApexForActivation(all_apex, instance_blank);
+ ASSERT_EQ(result.size(), 0u);
+ // When passed proper instance they should get selected
+ result = SelectApexForActivation(all_apex, instance);
+ ASSERT_EQ(result.size(), 4u);
+ ASSERT_THAT(result, UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file)),
+ ApexFileEq(ByRef(*shim_v1)),
+ ApexFileEq(ByRef(*shared_lib_1)),
+ ApexFileEq(ByRef(*shared_lib_2))));
+}
+
+// Higher version gets priority when selecting for activation
+TEST_F(ApexdUnitTest, HigherVersionOfApexIsSelected) {
+ auto apexd_test_file_v2 =
+ ApexFile::Open(AddPreInstalledApex("apex.apexd_test_v2.apex"));
+ AddPreInstalledApex("com.android.apex.cts.shim.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+
+ TemporaryDir data_dir;
+ AddDataApex("apex.apexd_test.apex");
+ auto shim_v2 =
+ ApexFile::Open(AddDataApex("com.android.apex.cts.shim.v2.apex"));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+
+ auto all_apex = instance.AllApexFilesByName();
+ auto result = SelectApexForActivation(all_apex, instance);
+ ASSERT_EQ(result.size(), 2u);
+
+ ASSERT_THAT(result,
+ UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file_v2)),
+ ApexFileEq(ByRef(*shim_v2))));
+}
+
+// When versions are equal, non-pre-installed version gets priority
+TEST_F(ApexdUnitTest, DataApexGetsPriorityForSameVersions) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddPreInstalledApex("com.android.apex.cts.shim.apex");
+ // Initialize pre-installed APEX information
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+
+ auto apexd_test_file = ApexFile::Open(AddDataApex("apex.apexd_test.apex"));
+ auto shim_v1 = ApexFile::Open(AddDataApex("com.android.apex.cts.shim.apex"));
+ // Initialize ApexFile repo
+ ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+
+ auto all_apex = instance.AllApexFilesByName();
+ auto result = SelectApexForActivation(all_apex, instance);
+ ASSERT_EQ(result.size(), 2u);
+
+ ASSERT_THAT(result, UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file)),
+ ApexFileEq(ByRef(*shim_v1))));
+}
+
+// Both versions of shared libs can be selected
+TEST_F(ApexdUnitTest, SharedLibsCanHaveBothVersionSelected) {
+ auto shared_lib_v1 = ApexFile::Open(AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex"));
+ // Initialize pre-installed APEX information
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+
+ auto shared_lib_v2 = ApexFile::Open(
+ AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex"));
+ // Initialize data APEX information
+ ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+
+ auto all_apex = instance.AllApexFilesByName();
+ auto result = SelectApexForActivation(all_apex, instance);
+ ASSERT_EQ(result.size(), 2u);
+
+ ASSERT_THAT(result, UnorderedElementsAre(ApexFileEq(ByRef(*shared_lib_v1)),
+ ApexFileEq(ByRef(*shared_lib_v2))));
+}
+
+TEST_F(ApexdUnitTest, ProcessCompressedApex) {
+ auto compressed_apex = ApexFile::Open(
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex"));
+
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex));
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+
+ std::string decompressed_file_path = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ // Assert output path is not empty
+ auto exists = PathExists(decompressed_file_path);
+ ASSERT_TRUE(IsOk(exists));
+ ASSERT_TRUE(*exists) << decompressed_file_path << " does not exist";
+
+ // Assert that decompressed apex is same as original apex
+ const std::string original_apex_file_path =
+ GetTestFile("com.android.apex.compressed.v1_original.apex");
+ auto comparison_result =
+ CompareFiles(original_apex_file_path, decompressed_file_path);
+ ASSERT_TRUE(IsOk(comparison_result));
+ ASSERT_TRUE(*comparison_result);
+
+ // Assert that return value contains decompressed APEX
+ auto decompressed_apex = ApexFile::Open(decompressed_file_path);
+ ASSERT_THAT(return_value,
+ UnorderedElementsAre(ApexFileEq(ByRef(*decompressed_apex))));
+}
+
+TEST_F(ApexdUnitTest, ProcessCompressedApexRunsVerification) {
+ auto compressed_apex_mismatch_key = ApexFile::Open(AddPreInstalledApex(
+ "com.android.apex.compressed_key_mismatch_with_original.capex"));
+ auto compressed_apex_version_mismatch = ApexFile::Open(
+ AddPreInstalledApex("com.android.apex.compressed.v1_with_v2_apex.capex"));
+
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex_mismatch_key));
+ compressed_apex_list.emplace_back(
+ std::cref(*compressed_apex_version_mismatch));
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(return_value.size(), 0u);
+}
+
+TEST_F(ApexdUnitTest, ValidateDecompressedApex) {
+ auto capex = ApexFile::Open(
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex"));
+ auto decompressed_v1 = ApexFile::Open(
+ AddDataApex("com.android.apex.compressed.v1_original.apex"));
+
+ auto result =
+ ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v1));
+ ASSERT_TRUE(IsOk(result));
+
+ // Validation checks version
+ auto decompressed_v2 = ApexFile::Open(
+ AddDataApex("com.android.apex.compressed.v2_original.apex"));
+ result =
+ ValidateDecompressedApex(std::cref(*capex), std::cref(*decompressed_v2));
+ ASSERT_FALSE(IsOk(result));
+ ASSERT_THAT(
+ result.error().message(),
+ HasSubstr(
+ "Compressed APEX has different version than decompressed APEX"));
+
+ // Validation check root digest
+ auto decompressed_v1_different_digest = ApexFile::Open(AddDataApex(
+ "com.android.apex.compressed.v1_different_digest_original.apex"));
+ result = ValidateDecompressedApex(
+ std::cref(*capex), std::cref(*decompressed_v1_different_digest));
+ ASSERT_FALSE(IsOk(result));
+ ASSERT_THAT(result.error().message(),
+ HasSubstr("does not match with expected root digest"));
+
+ // Validation checks key
+ auto capex_different_key = ApexFile::Open(
+ AddDataApex("com.android.apex.compressed_different_key.capex"));
+ result = ValidateDecompressedApex(std::cref(*capex_different_key),
+ std::cref(*decompressed_v1));
+ ASSERT_FALSE(IsOk(result));
+ ASSERT_THAT(
+ result.error().message(),
+ HasSubstr("Public key of compressed APEX is different than original"));
+}
+
+TEST_F(ApexdUnitTest, ProcessCompressedApexCanBeCalledMultipleTimes) {
+ auto compressed_apex = ApexFile::Open(
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex"));
+
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex));
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(return_value.size(), 1u);
+
+ // Capture the creation time of the decompressed APEX
+ std::error_code ec;
+ auto decompressed_apex_path = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ auto last_write_time_1 = fs::last_write_time(decompressed_apex_path, ec);
+ ASSERT_FALSE(ec) << "Failed to capture last write time of "
+ << decompressed_apex_path;
+
+ // Now try to decompress the same capex again. It should not fail.
+ return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(return_value.size(), 1u);
+
+ // Ensure the decompressed APEX file did not change
+ auto last_write_time_2 = fs::last_write_time(decompressed_apex_path, ec);
+ ASSERT_FALSE(ec) << "Failed to capture last write time of "
+ << decompressed_apex_path;
+ ASSERT_EQ(last_write_time_1, last_write_time_2);
+}
+
+// Test behavior of ProcessCompressedApex when is_ota_chroot is true
+TEST_F(ApexdUnitTest, ProcessCompressedApexOnOtaChroot) {
+ auto compressed_apex = ApexFile::Open(
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex"));
+
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex));
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ true);
+ ASSERT_EQ(return_value.size(), 1u);
+
+ // Decompressed APEX should be located in decompression_dir
+ std::string decompressed_file_path =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ // Assert output path is not empty
+ auto exists = PathExists(decompressed_file_path);
+ ASSERT_TRUE(IsOk(exists));
+ ASSERT_TRUE(*exists) << decompressed_file_path << " does not exist";
+
+ // Assert that decompressed apex is same as original apex
+ const std::string original_apex_file_path =
+ GetTestFile("com.android.apex.compressed.v1_original.apex");
+ auto comparison_result =
+ CompareFiles(original_apex_file_path, decompressed_file_path);
+ ASSERT_TRUE(IsOk(comparison_result));
+ ASSERT_TRUE(*comparison_result);
+
+ // Assert that return value contains the decompressed APEX
+ auto apex_file = ApexFile::Open(decompressed_file_path);
+ ASSERT_THAT(return_value,
+ UnorderedElementsAre(ApexFileEq(ByRef(*apex_file))));
+}
+
+// When decompressing APEX, reuse existing OTA APEX
+TEST_F(ApexdUnitTest, ProcessCompressedApexReuseOtaApex) {
+ // Push a compressed APEX that will fail to decompress
+ auto compressed_apex = ApexFile::Open(AddPreInstalledApex(
+ "com.android.apex.compressed.v1_not_decompressible.capex"));
+
+ std::vector<ApexFileRef> compressed_apex_list;
+ compressed_apex_list.emplace_back(std::cref(*compressed_apex));
+
+ // If we try to decompress capex directly, it should fail since the capex
+ // pushed is faulty and cannot be decompressed
+ auto return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(return_value.size(), 0u);
+
+ // But, if there is an ota_apex present for reuse, it should reuse that
+ // and avoid decompressing the faulty capex
+
+ // Push an OTA apex that should be reused to skip decompression
+ auto ota_apex_path =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
+ ota_apex_path);
+ return_value =
+ ProcessCompressedApex(compressed_apex_list, /* is_ota_chroot= */ false);
+ ASSERT_EQ(return_value.size(), 1u);
+
+ // Ota Apex should be cleaned up
+ ASSERT_FALSE(*PathExists(ota_apex_path));
+ ASSERT_EQ(return_value[0].GetPath(),
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix));
+}
+
+TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompressionNewApex) {
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+
+ // A brand new compressed APEX is being introduced: selected
+ auto result =
+ ShouldAllocateSpaceForDecompression("com.android.brand.new", 1, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(*result);
+}
+
+TEST_F(ApexdUnitTest,
+ ShouldAllocateSpaceForDecompressionWasNotCompressedBefore) {
+ // Prepare fake pre-installed apex
+ AddPreInstalledApex("apex.apexd_test.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+
+ // An existing pre-installed APEX is now compressed in the OTA: selected
+ {
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.test_package", 1, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(*result);
+ }
+
+ // Even if there is a data apex (lower version)
+ // Include data apex within calculation now
+ AddDataApex("apex.apexd_test_v2.apex");
+ ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+ {
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.test_package", 3, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(*result);
+ }
+
+ // But not if data apex has equal or higher version
+ {
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.test_package", 2, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_FALSE(*result);
+ }
+}
+
+TEST_F(ApexdUnitTest, ShouldAllocateSpaceForDecompressionVersionCompare) {
+ // Prepare fake pre-installed apex
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({GetBuiltInDir()})));
+ ASSERT_TRUE(IsOk(instance.AddDataApex(GetDataDir())));
+
+ {
+ // New Compressed apex has higher version than decompressed data apex:
+ // selected
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.compressed", 2, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(*result)
+ << "Higher version test with decompressed data returned false";
+ }
+
+ // Compare against decompressed data apex
+ {
+ // New Compressed apex has same version as decompressed data apex: not
+ // selected
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.compressed", 1, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_FALSE(*result)
+ << "Same version test with decompressed data returned true";
+ }
+
+ {
+ // New Compressed apex has lower version than decompressed data apex:
+ // selected
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.compressed", 0, instance);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(*result)
+ << "lower version test with decompressed data returned false";
+ }
+
+ // Replace decompressed data apex with a higher version
+ ApexFileRepository instance_new(GetDecompressionDir());
+ ASSERT_TRUE(IsOk(instance_new.AddPreInstalledApex({GetBuiltInDir()})));
+ TemporaryDir data_dir_new;
+ fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"),
+ data_dir_new.path);
+ ASSERT_TRUE(IsOk(instance_new.AddDataApex(data_dir_new.path)));
+
+ {
+ // New Compressed apex has higher version as data apex: selected
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.compressed", 3, instance_new);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(*result) << "Higher version test with new data returned false";
+ }
+
+ {
+ // New Compressed apex has same version as data apex: not selected
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.compressed", 2, instance_new);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_FALSE(*result) << "Same version test with new data returned true";
+ }
+
+ {
+ // New Compressed apex has lower version than data apex: not selected
+ auto result = ShouldAllocateSpaceForDecompression(
+ "com.android.apex.compressed", 1, instance_new);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_FALSE(*result) << "lower version test with new data returned true";
+ }
+}
+
+TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexCreatesSingleFile) {
+ TemporaryDir dest_dir;
+ // Reserving space should create a single file in dest_dir with exact size
+
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 1u);
+ EXPECT_EQ(fs::file_size((*files)[0]), 100u);
+}
+
+TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexSafeToCallMultipleTimes) {
+ TemporaryDir dest_dir;
+ // Calling ReserveSpaceForCompressedApex multiple times should still create
+ // a single file
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 1u);
+ EXPECT_EQ(fs::file_size((*files)[0]), 100u);
+}
+
+TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexShrinkAndGrow) {
+ TemporaryDir dest_dir;
+
+ // Create a 100 byte file
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+
+ // Should be able to shrink and grow the reserved space
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(1000, dest_dir.path)));
+ auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 1u);
+ EXPECT_EQ(fs::file_size((*files)[0]), 1000u);
+
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(10, dest_dir.path)));
+ files = ReadDir(dest_dir.path, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 1u);
+ EXPECT_EQ(fs::file_size((*files)[0]), 10u);
+}
+
+TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexDeallocateIfPassedZero) {
+ TemporaryDir dest_dir;
+
+ // Create a file first
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(100, dest_dir.path)));
+ auto files = ReadDir(dest_dir.path, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 1u);
+
+ // Should delete the reserved file if size passed is 0
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(0, dest_dir.path)));
+ files = ReadDir(dest_dir.path, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 0u);
+}
+
+TEST_F(ApexdUnitTest, ReserveSpaceForCapexCleansOtaApex) {
+ TemporaryDir dest_dir;
+
+ auto ota_apex_path = StringPrintf(
+ "%s/ota_apex%s", GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ auto create_ota_apex = [&]() {
+ // Create an ota_apex first
+ fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
+ ota_apex_path);
+ auto path_exists = PathExists(ota_apex_path);
+ ASSERT_TRUE(*path_exists);
+ };
+ create_ota_apex();
+
+ // Should not delete the reserved file if size passed is negative
+ ASSERT_FALSE(IsOk(ReserveSpaceForCompressedApex(-1, dest_dir.path)));
+ auto path_exists = PathExists(ota_apex_path);
+ ASSERT_TRUE(*path_exists);
+
+ // Should delete the reserved file if size passed is 0
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(0, dest_dir.path)));
+ path_exists = PathExists(ota_apex_path);
+ ASSERT_FALSE(*path_exists);
+
+ create_ota_apex();
+ // Should delete the reserved file if size passed is positive
+ ASSERT_TRUE(IsOk(ReserveSpaceForCompressedApex(10, dest_dir.path)));
+ path_exists = PathExists(ota_apex_path);
+ ASSERT_FALSE(*path_exists);
+}
+
+TEST_F(ApexdUnitTest, ReserveSpaceForCompressedApexErrorForNegativeValue) {
+ TemporaryDir dest_dir;
+ // Should return error if negative value is passed
+ ASSERT_FALSE(IsOk(ReserveSpaceForCompressedApex(-1, dest_dir.path)));
+}
+
+// A test fixture to use for tests that mount/unmount apexes.
+class ApexdMountTest : public ApexdUnitTest {
+ public:
+
+ void UnmountOnTearDown(const std::string& apex_file) {
+ to_unmount_.push_back(apex_file);
+ }
+
+ protected:
+ void SetUp() final {
+ ApexdUnitTest::SetUp();
+ GetApexDatabaseForTesting().Reset();
+ ASSERT_TRUE(IsOk(SetUpApexTestEnvironment()));
+ }
+
+ void TearDown() final {
+ ApexdUnitTest::TearDown();
+ for (const auto& apex : to_unmount_) {
+ if (auto status = DeactivatePackage(apex); !status.ok()) {
+ LOG(ERROR) << "Failed to unmount " << apex << " : " << status.error();
+ }
+ }
+ }
+
+ private:
+ MountNamespaceRestorer restorer_;
+ std::vector<std::string> to_unmount_;
+};
+
+// TODO(b/187864524): cover other negative scenarios.
+TEST_F(ApexdMountTest, InstallPackageRejectsApexWithoutRebootlessSupport) {
+ std::string file_path = AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(GetTestFile("apex.apexd_test.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(),
+ HasSubstr("does not support non-staged update"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsNoPreInstalledApex) {
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(
+ ret.error().message(),
+ HasSubstr("No active version found for package test.apex.rebootless"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsNoHashtree) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret =
+ InstallPackage(GetTestFile("test.rebootless_apex_v2_no_hashtree.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(),
+ HasSubstr(" does not have an embedded hash tree"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsNoActiveApex) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(
+ ret.error().message(),
+ HasSubstr("No active version found for package test.apex.rebootless"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsManifestMismatch) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(
+ GetTestFile("test.rebootless_apex_manifest_mismatch.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(
+ ret.error().message(),
+ HasSubstr(
+ "Manifest inside filesystem does not match manifest outside it"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsCorrupted) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_corrupted.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr("Can't verify /dev/block/dm-"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsProvidesSharedLibs) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(
+ GetTestFile("test.rebootless_apex_provides_sharedlibs.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr(" is a shared libs APEX"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsProvidesNativeLibs) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(
+ GetTestFile("test.rebootless_apex_provides_native_libs.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr(" provides native libs"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsRequiresSharedApexLibs) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(
+ GetTestFile("test.rebootless_apex_requires_shared_apex_libs.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr(" requires shared apex libs"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsJniLibs) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_jni_libs.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr(" requires JNI libs"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsAddRequiredNativeLib) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret =
+ InstallPackage(GetTestFile("test.rebootless_apex_add_native_lib.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(),
+ HasSubstr("Set of native libs required by"));
+ ASSERT_THAT(
+ ret.error().message(),
+ HasSubstr("differs from the one required by the currently active"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsRemovesRequiredNativeLib) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret = InstallPackage(
+ GetTestFile("test.rebootless_apex_remove_native_lib.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(),
+ HasSubstr("Set of native libs required by"));
+ ASSERT_THAT(
+ ret.error().message(),
+ HasSubstr("differs from the one required by the currently active"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsAppInApex) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret =
+ InstallPackage(GetTestFile("test.rebootless_apex_app_in_apex.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr("contains app inside"));
+}
+
+TEST_F(ApexdMountTest, InstallPackageRejectsPrivAppInApex) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto ret =
+ InstallPackage(GetTestFile("test.rebootless_apex_priv_app_in_apex.apex"));
+ ASSERT_FALSE(IsOk(ret));
+ ASSERT_THAT(ret.error().message(), HasSubstr("contains priv-app inside"));
+}
+
+TEST_F(ApexdMountTest, InstallPackagePreInstallVersionActive) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_TRUE(IsOk(ret));
+ UnmountOnTearDown(ret->GetPath());
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@2"));
+
+ // Check that /apex/test.apex.rebootless is a bind mount of
+ // /apex/test.apex.rebootless@2.
+ auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
+ ASSERT_TRUE(IsOk(manifest));
+ ASSERT_EQ(2u, manifest->version());
+
+ // Check that GetActivePackage correctly reports upgraded version.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
+
+ // Check that pre-installed APEX is still around
+ ASSERT_EQ(0, access(file_path.c_str(), F_OK))
+ << "Can't access " << file_path << " : " << strerror(errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that upgraded APEX is mounted on top of dm-verity device.
+ db.ForallMountedApexes(
+ "test.apex.rebootless", [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, ret->GetPath());
+ ASSERT_EQ(data.device_name, "test.apex.rebootless@2_1");
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackagePreInstallVersionActiveSamegrade) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"));
+ ASSERT_TRUE(IsOk(ret));
+ UnmountOnTearDown(ret->GetPath());
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@1"));
+
+ // Check that GetActivePackage correctly reports upgraded version.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
+
+ // Check that pre-installed APEX is still around
+ ASSERT_EQ(0, access(file_path.c_str(), F_OK))
+ << "Can't access " << file_path << " : " << strerror(errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that upgraded APEX is mounted on top of dm-verity device.
+ db.ForallMountedApexes(
+ "test.apex.rebootless", [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, ret->GetPath());
+ ASSERT_EQ(data.device_name, "test.apex.rebootless@1_1");
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackageDataVersionActive) {
+ AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("test.rebootless_apex_v1.apex");
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_TRUE(IsOk(ret));
+ UnmountOnTearDown(ret->GetPath());
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@2"));
+
+ // Check that /apex/test.apex.rebootless is a bind mount of
+ // /apex/test.apex.rebootless@2.
+ auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
+ ASSERT_TRUE(IsOk(manifest));
+ ASSERT_EQ(2u, manifest->version());
+
+ // Check that GetActivePackage correctly reports upgraded version.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
+
+ // Check that previously active APEX was deleted.
+ ASSERT_EQ(-1, access(file_path.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that upgraded APEX is mounted on top of dm-verity device.
+ db.ForallMountedApexes(
+ "test.apex.rebootless", [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, ret->GetPath());
+ ASSERT_EQ(data.device_name, "test.apex.rebootless@2_1");
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackageResolvesPathCollision) {
+ AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("test.rebootless_apex_v1.apex",
+ "test.apex.rebootless@1_1.apex");
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v1.apex"));
+ ASSERT_TRUE(IsOk(ret));
+ UnmountOnTearDown(ret->GetPath());
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@1"));
+
+ // Check that /apex/test.apex.rebootless is a bind mount of
+ // /apex/test.apex.rebootless@2.
+ auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
+ ASSERT_TRUE(IsOk(manifest));
+ ASSERT_EQ(1u, manifest->version());
+
+ // Check that GetActivePackage correctly reports upgraded version.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
+
+ // Check that we correctly resolved active apex path collision.
+ ASSERT_EQ(active_apex->GetPath(),
+ GetDataDir() + "/test.apex.rebootless@1_2.apex");
+
+ // Check that previously active APEX was deleted.
+ ASSERT_EQ(-1, access(file_path.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that upgraded APEX is mounted on top of dm-verity device.
+ db.ForallMountedApexes(
+ "test.apex.rebootless", [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, ret->GetPath());
+ ASSERT_EQ(data.device_name, "test.apex.rebootless@1_2");
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackageDataVersionActiveSamegrade) {
+ AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("test.rebootless_apex_v2.apex");
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_TRUE(IsOk(ret));
+ UnmountOnTearDown(ret->GetPath());
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@2"));
+
+ // Check that /apex/test.apex.rebootless is a bind mount of
+ // /apex/test.apex.rebootless@2.
+ auto manifest = ReadManifest("/apex/test.apex.rebootless/apex_manifest.pb");
+ ASSERT_TRUE(IsOk(manifest));
+ ASSERT_EQ(2u, manifest->version());
+
+ // Check that GetActivePackage correctly reports upgraded version.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), ret->GetPath());
+
+ // Check that previously active APEX was deleted.
+ ASSERT_EQ(-1, access(file_path.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that upgraded APEX is mounted on top of dm-verity device.
+ db.ForallMountedApexes(
+ "test.apex.rebootless", [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, ret->GetPath());
+ ASSERT_EQ(data.device_name, "test.apex.rebootless@2_1");
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackageUnmountFailsPreInstalledApexActive) {
+ std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ unique_fd fd(open("/apex/test.apex.rebootless/apex_manifest.pb",
+ O_RDONLY | O_CLOEXEC));
+ ASSERT_NE(-1, fd.get());
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_FALSE(IsOk(ret));
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@1"));
+
+ // Check that GetActivePackage correctly reports upgraded version.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+
+ // Check that old APEX is still around
+ ASSERT_EQ(0, access(file_path.c_str(), F_OK))
+ << "Can't access " << file_path << " : " << strerror(errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that upgraded APEX is mounted on top of dm-verity device.
+ db.ForallMountedApexes("test.apex.rebootless",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, file_path);
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackageUnmountFailedUpdatedApexActive) {
+ AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ std::string file_path = AddDataApex("test.rebootless_apex_v1.apex");
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ {
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+ }
+
+ unique_fd fd(open("/apex/test.apex.rebootless/apex_manifest.pb",
+ O_RDONLY | O_CLOEXEC));
+ ASSERT_NE(-1, fd.get());
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_FALSE(IsOk(ret));
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/test.apex.rebootless",
+ "/apex/test.apex.rebootless@1"));
+
+ // Check that GetActivePackage correctly reports old apex.
+ auto active_apex = GetActivePackage("test.apex.rebootless");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+
+ // Check that old APEX is still around
+ ASSERT_EQ(0, access(file_path.c_str(), F_OK))
+ << "Can't access " << file_path << " : " << strerror(errno);
+
+ auto& db = GetApexDatabaseForTesting();
+ db.ForallMountedApexes(
+ "test.apex.rebootless", [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, file_path);
+ ASSERT_EQ(data.device_name, "test.apex.rebootless@1");
+ });
+}
+
+TEST_F(ApexdMountTest, InstallPackageUpdatesApexInfoList) {
+ auto apex_1 = AddPreInstalledApex("test.rebootless_apex_v1.apex");
+ auto apex_2 = AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ UnmountOnTearDown(apex_1);
+ UnmountOnTearDown(apex_2);
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_1)));
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_2)));
+
+ // Call OnAllPackagesActivated to create /apex/apex-info-list.xml.
+ OnAllPackagesActivated(/* is_bootstrap= */ false);
+ // Check /apex/apex-info-list.xml was created.
+ ASSERT_EQ(0, access("/apex/apex-info-list.xml", F_OK));
+
+ auto ret = InstallPackage(GetTestFile("test.rebootless_apex_v2.apex"));
+ ASSERT_TRUE(IsOk(ret));
+ UnmountOnTearDown(ret->GetPath());
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "test.apex.rebootless",
+ /* modulePath= */ apex_1,
+ /* preinstalledModulePath= */ apex_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_2, /* preinstalledModulePath= */ apex_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_2));
+ auto apex_info_xml_3 = com::android::apex::ApexInfo(
+ /* moduleName= */ "test.apex.rebootless",
+ /* modulePath= */ ret->GetPath(),
+ /* preinstalledModulePath= */ apex_1,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(ret->GetPath()));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2),
+ ApexInfoXmlEq(apex_info_xml_3)));
+}
+
+TEST_F(ApexdMountTest, ActivatePackage) {
+ std::string file_path = AddPreInstalledApex("apex.apexd_test.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+ UnmountOnTearDown(file_path);
+
+ auto active_apex = GetActivePackage("com.android.apex.test_package");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1"));
+
+ ASSERT_TRUE(IsOk(DeactivatePackage(file_path)));
+ ASSERT_FALSE(IsOk(GetActivePackage("com.android.apex.test_package")));
+
+ auto new_apex_mounts = GetApexMounts();
+ ASSERT_EQ(new_apex_mounts.size(), 0u);
+}
+
+TEST_F(ApexdMountTest, ActivateDeactivateSharedLibsApex) {
+ ASSERT_EQ(mkdir("/apex/sharedlibs", 0755), 0);
+ ASSERT_EQ(mkdir("/apex/sharedlibs/lib", 0755), 0);
+ ASSERT_EQ(mkdir("/apex/sharedlibs/lib64", 0755), 0);
+ auto deleter = make_scope_guard([]() {
+ std::error_code ec;
+ fs::remove_all("/apex/sharedlibs", ec);
+ if (ec) {
+ LOG(ERROR) << "Failed to delete /apex/sharedlibs : " << ec;
+ }
+ });
+
+ std::string file_path = AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex");
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+
+ UnmountOnTearDown(file_path);
+ ASSERT_TRUE(IsOk(ActivatePackage(file_path)));
+
+ auto active_apex = GetActivePackage("com.android.apex.test.sharedlibs");
+ ASSERT_TRUE(IsOk(active_apex));
+ ASSERT_EQ(active_apex->GetPath(), file_path);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test.sharedlibs@1"));
+
+ ASSERT_TRUE(IsOk(DeactivatePackage(file_path)));
+ ASSERT_FALSE(IsOk(GetActivePackage("com.android.apex.test.sharedlibs")));
+
+ auto new_apex_mounts = GetApexMounts();
+ ASSERT_EQ(new_apex_mounts.size(), 0u);
+}
+
+TEST_F(ApexdMountTest, RemoveInactiveDataApex) {
+ AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+ // Add a decompressed apex that will not be mounted, so should be removed
+ auto decompressed_apex = StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
+ decompressed_apex);
+ // Add a decompressed apex that will be mounted, so should be not be removed
+ auto active_decompressed_apex = StringPrintf(
+ "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"),
+ active_decompressed_apex);
+ // Apex that do not have kDecompressedApexPackageSuffix, should not be removed
+ // from decompression_dir
+ auto decompressed_different_suffix =
+ StringPrintf("%s/com.android.apex.compressed@2%s",
+ GetDecompressionDir().c_str(), kApexPackageSuffix);
+ fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"),
+ decompressed_different_suffix);
+
+ AddPreInstalledApex("apex.apexd_test.apex");
+ auto data_apex = AddDataApex("apex.apexd_test.apex");
+ auto active_data_apex = AddDataApex("apex.apexd_test_v2.apex");
+
+ // Activate some of the apex
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
+ UnmountOnTearDown(active_decompressed_apex);
+ UnmountOnTearDown(active_data_apex);
+ ASSERT_TRUE(IsOk(ActivatePackage(active_decompressed_apex)));
+ ASSERT_TRUE(IsOk(ActivatePackage(active_data_apex)));
+ // Clean up inactive apex packages
+ RemoveInactiveDataApex();
+
+ // Verify inactive apex packages have been deleted
+ ASSERT_TRUE(*PathExists(active_decompressed_apex));
+ ASSERT_TRUE(*PathExists(active_data_apex));
+ ASSERT_TRUE(*PathExists(decompressed_different_suffix));
+ ASSERT_FALSE(*PathExists(decompressed_apex));
+ ASSERT_FALSE(*PathExists(data_apex));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapOnlyPreInstalledApexes) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapFailsToScanPreInstalledApexes) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ AddPreInstalledApex("apex.apexd_test_corrupt_superblock_apex.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 1);
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasHigherVersion) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+ auto apex_info_xml_3 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_3,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(apex_path_3));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2),
+ ApexInfoXmlEq(apex_info_xml_3)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasSameVersion) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+ auto apex_info_xml_3 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_3,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(apex_path_3));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2),
+ ApexInfoXmlEq(apex_info_xml_3)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapSystemHasHigherVersion) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test_v2.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ AddDataApex("apex.apexd_test.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasSameVersionButDifferentKey) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ AddDataApex("apex.apexd_test_different_key.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest,
+ OnOtaChrootBootstrapDataHasHigherVersionButDifferentKey) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 =
+ AddDataApex("apex.apexd_test_different_key_v2.apex");
+
+ {
+ auto apex = ApexFile::Open(apex_path_3);
+ ASSERT_TRUE(IsOk(apex));
+ ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL);
+ }
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataApexWithoutPreInstalledApex) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ AddDataApex("apex.apexd_test_different_app.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_1);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapPreInstalledSharedLibsApex) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 = AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.test.sharedlibs@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test.sharedlibs",
+ /* modulePath= */ apex_path_2,
+ /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_2));
+ auto apex_info_xml_3 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_3,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(apex_path_3));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2),
+ ApexInfoXmlEq(apex_info_xml_3)));
+
+ ASSERT_EQ(access("/apex/sharedlibs", F_OK), 0);
+
+ // Check /apex/sharedlibs is populated properly.
+ std::vector<std::string> sharedlibs;
+ for (const auto& p : fs::recursive_directory_iterator("/apex/sharedlibs")) {
+ if (fs::is_symlink(p)) {
+ auto src = fs::read_symlink(p.path());
+ ASSERT_EQ(p.path().filename(), src.filename());
+ sharedlibs.push_back(p.path().parent_path().string() + "->" +
+ src.parent_path().string());
+ }
+ }
+
+ std::vector<std::string> expected = {
+ "/apex/sharedlibs/lib/libsharedlibtest.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib/libsharedlibtest.so",
+ "/apex/sharedlibs/lib/libc++.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib/libc++.so",
+ };
+
+ // On 64bit devices we also have lib64.
+ if (!GetProperty("ro.product.cpu.abilist64", "").empty()) {
+ expected.push_back(
+ "/apex/sharedlibs/lib64/libsharedlibtest.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib64/libsharedlibtest.so");
+ expected.push_back(
+ "/apex/sharedlibs/lib64/libc++.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib64/libc++.so");
+ }
+ ASSERT_THAT(sharedlibs, UnorderedElementsAreArray(expected));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapSharedLibsApexBothVersions) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 = AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+ std::string apex_path_4 =
+ AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+ UnmountOnTearDown(apex_path_4);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.test.sharedlibs@1",
+ "/apex/com.android.apex.test.sharedlibs@2"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test.sharedlibs",
+ /* modulePath= */ apex_path_2,
+ /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_path_2));
+ auto apex_info_xml_3 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_3,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(apex_path_3));
+ auto apex_info_xml_4 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test.sharedlibs",
+ /* modulePath= */ apex_path_4,
+ /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(apex_path_4));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2),
+ ApexInfoXmlEq(apex_info_xml_3),
+ ApexInfoXmlEq(apex_info_xml_4)));
+
+ ASSERT_EQ(access("/apex/sharedlibs", F_OK), 0);
+
+ // Check /apex/sharedlibs is populated properly.
+ // Because we don't want to hardcode full paths (they are pretty long and have
+ // a hash in them which might change if new prebuilts are dropped in), the
+ // assertion logic is a little bit clunky.
+ std::vector<std::string> sharedlibs;
+ for (const auto& p : fs::recursive_directory_iterator("/apex/sharedlibs")) {
+ if (fs::is_symlink(p)) {
+ auto src = fs::read_symlink(p.path());
+ ASSERT_EQ(p.path().filename(), src.filename());
+ sharedlibs.push_back(p.path().parent_path().string() + "->" +
+ src.parent_path().string());
+ }
+ }
+
+ std::vector<std::string> expected = {
+ "/apex/sharedlibs/lib/libsharedlibtest.so->"
+ "/apex/com.android.apex.test.sharedlibs@2/lib/libsharedlibtest.so",
+ "/apex/sharedlibs/lib/libsharedlibtest.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib/libsharedlibtest.so",
+ "/apex/sharedlibs/lib/libc++.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib/libc++.so",
+ };
+ // On 64bit devices we also have lib64.
+ if (!GetProperty("ro.product.cpu.abilist64", "").empty()) {
+ expected.push_back(
+ "/apex/sharedlibs/lib64/libsharedlibtest.so->"
+ "/apex/com.android.apex.test.sharedlibs@2/lib64/libsharedlibtest.so");
+ expected.push_back(
+ "/apex/sharedlibs/lib64/libsharedlibtest.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib64/libsharedlibtest.so");
+ expected.push_back(
+ "/apex/sharedlibs/lib64/libc++.so->"
+ "/apex/com.android.apex.test.sharedlibs@1/lib64/libc++.so");
+ }
+
+ ASSERT_THAT(sharedlibs, UnorderedElementsAreArray(expected));
+}
+
+// Test when we move from uncompressed APEX to CAPEX via ota
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapOnlyCompressedApexes) {
+ std::string apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Decompressed APEX should be mounted from decompression_dir
+ std::string decompressed_apex =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(decompressed_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1.chroot");
+ });
+}
+
+// Test we decompress only once even if OnOtaChrootBootstrap is called multiple
+// times
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDecompressOnlyOnceMultipleCalls) {
+ std::string apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Decompressed OTA APEX should be mounted
+ std::string decompressed_ota_apex =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_ota_apex);
+
+ // Capture the creation time of the OTA APEX
+ std::error_code ec;
+ auto last_write_time_1 = fs::last_write_time(decompressed_ota_apex, ec);
+ ASSERT_FALSE(ec) << "Failed to capture last write time of "
+ << decompressed_ota_apex;
+
+ // Call OnOtaChrootBootstrap again. Since we do not hardlink decompressed APEX
+ // to /data/apex/active directory when in chroot, when selecting apex for
+ // activation, we will end up selecting compressed APEX again.
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Compare write time to ensure we did not decompress again
+ auto last_write_time_2 = fs::last_write_time(decompressed_ota_apex, ec);
+ ASSERT_FALSE(ec) << "Failed to capture last write time of "
+ << decompressed_ota_apex << ec.message();
+ ASSERT_EQ(last_write_time_1, last_write_time_2);
+}
+
+// Test when we upgrade existing CAPEX to higher version via OTA
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapUpgradeCapex) {
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex",
+ previous_built_in_dir.path);
+ // Place a higher version capex in current built_in_dir
+ std::string apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Upgraded decompressed APEX should be mounted from decompression dir
+ std::string decompressed_active_apex =
+ StringPrintf("%s/com.android.apex.compressed@2%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@2"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_active_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_active_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@2.chroot");
+ });
+}
+
+// Test when we update existing CAPEX to same version via OTA
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapSamegradeCapex) {
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex",
+ previous_built_in_dir.path);
+ // Place a same version capex in current built_in_dir, under a different name
+ auto apex_path =
+ StringPrintf("%s/different-name.capex", GetBuiltInDir().c_str());
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), apex_path);
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Previously decompressed APEX should be mounted from decompression_dir
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_active_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_active_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1.chroot");
+ });
+}
+
+// Test when we update existing CAPEX to same version, but different digest
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapSamegradeCapexDifferentDigest) {
+ TemporaryDir previous_built_in_dir;
+ auto different_digest_apex_path = PrepareCompressedApex(
+ "com.android.apex.compressed.v1_different_digest.capex",
+ previous_built_in_dir.path);
+ // Place a same version capex in current built_in_dir, which has different
+ // digest
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // New decompressed ota APEX should be mounted with kOtaApexPackageSuffix
+ std::string decompressed_ota_apex =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_ota_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_ota_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_ota_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_ota_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1.chroot");
+ });
+
+ // Ensure decompressed apex has same digest as pre-installed
+ auto pre_installed_apex = ApexFile::Open(apex_path);
+ auto decompressed_apex = ApexFile::Open(decompressed_ota_apex);
+ auto different_digest_apex = ApexFile::Open(different_digest_apex_path);
+ ASSERT_EQ(
+ pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
+ GetRootDigest(*decompressed_apex));
+ ASSERT_NE(
+ pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
+ GetRootDigest(*different_digest_apex));
+
+ // Ensure we didn't remove previous decompressed APEX
+ std::string previous_decompressed_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ auto path_exists = PathExists(previous_decompressed_apex);
+ ASSERT_TRUE(*path_exists);
+}
+
+// Test when we update existing CAPEX to same version, but different key via OTA
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapSamegradeCapexDifferentKey) {
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed_different_key.capex",
+ previous_built_in_dir.path);
+ // Place a same version capex in current built_in_dir, which has different key
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // New decompressed APEX should be mounted from ota_reserved directory
+ std::string decompressed_active_apex =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_active_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_active_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1.chroot");
+ });
+}
+
+// Test when we remove CAPEX via OTA
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapCapexToApex) {
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex",
+ previous_built_in_dir.path);
+ // Place a uncompressed version apex in current built_in_dir
+ std::string apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // New uncompressed APEX should be mounted
+ UnmountOnTearDown(apex_path);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_uncompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ apex_path,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_uncompressed)));
+}
+
+TEST_F(ApexdMountTest,
+ OnOtaChrootBootstrapDecompressedApexVersionDifferentThanCapex) {
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v2.capex",
+ previous_built_in_dir.path);
+ // Place a lower version capex in current built_in_dir, so that previously
+ // decompressed APEX has higher version but still doesn't get picked during
+ // selection.
+ std::string apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Pre-installed CAPEX should be decompressed again and mounted from
+ // decompression_dir
+ std::string decompressed_active_apex =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_active_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_active_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+}
+
+// Test when we update CAPEX and there is a higher version present in data
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHigherThanCapex) {
+ auto system_apex_path =
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex");
+ auto data_apex_path =
+ AddDataApex("com.android.apex.compressed.v2_original.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Data APEX should be mounted
+ UnmountOnTearDown(data_apex_path);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@2"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_data = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ data_apex_path,
+ /* preinstalledModulePath= */ system_apex_path,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(data_apex_path));
+ auto apex_info_xml_system = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ system_apex_path,
+ /* preinstalledModulePath= */ system_apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(system_apex_path));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_data),
+ ApexInfoXmlEq(apex_info_xml_system)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, data_apex_path);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@2.chroot");
+ });
+}
+
+// Test when we update CAPEX and there is a lower version present in data
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataLowerThanCapex) {
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+ AddDataApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Decompressed APEX should be mounted from reserved dir
+ std::string decompressed_active_apex =
+ StringPrintf("%s/com.android.apex.compressed@2%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@2"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_active_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_active_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@2.chroot");
+ });
+}
+
+// Test when we update CAPEX and there is a same version present in data
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataSameAsCapex) {
+ auto system_apex_path =
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex");
+ auto data_apex_path =
+ AddDataApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // Data APEX should be mounted
+ UnmountOnTearDown(data_apex_path);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_data = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ data_apex_path,
+ /* preinstalledModulePath= */ system_apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ false, /* isActive= */ true, GetMTime(data_apex_path));
+ auto apex_info_xml_system = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ system_apex_path,
+ /* preinstalledModulePath= */ system_apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(system_apex_path));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_data),
+ ApexInfoXmlEq(apex_info_xml_system)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, data_apex_path);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1.chroot");
+ });
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDataHasDifferentKeyThanCapex) {
+ AddDataApex("com.android.apex.compressed_different_key.capex");
+ // Place a same version capex in current built_in_dir, which has different key
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ // New decompressed APEX should be mounted from ota_reserved directory
+ std::string decompressed_active_apex =
+ StringPrintf("%s/com.android.apex.compressed@1%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_decompressed = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.compressed",
+ /* modulePath= */ decompressed_active_apex,
+ /* preinstalledModulePath= */ apex_path,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true,
+ GetMTime(decompressed_active_apex));
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_decompressed)));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1.chroot");
+ });
+}
+
+static std::string GetSelinuxContext(const std::string& file) {
+ char* ctx;
+ if (getfilecon(file.c_str(), &ctx) < 0) {
+ PLOG(ERROR) << "Failed to getfilecon " << file;
+ return "";
+ }
+ std::string result(ctx);
+ freecon(ctx);
+ return result;
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapSelinuxLabelsAreCorrect) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 = AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+
+ EXPECT_EQ(GetSelinuxContext("/apex/apex-info-list.xml"),
+ "u:object_r:apex_info_file:s0");
+
+ EXPECT_EQ(GetSelinuxContext("/apex/sharedlibs"),
+ "u:object_r:apex_mnt_dir:s0");
+
+ EXPECT_EQ(GetSelinuxContext("/apex/com.android.apex.test_package"),
+ "u:object_r:system_file:s0");
+ EXPECT_EQ(GetSelinuxContext("/apex/com.android.apex.test_package@2"),
+ "u:object_r:system_file:s0");
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapDmDevicesHaveCorrectName) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+
+ MountedApexDatabase& db = GetApexDatabaseForTesting();
+ // com.android.apex.test_package_2 should be mounted directly on top of loop
+ // device.
+ db.ForallMountedApexes("com.android.apex.test_package_2",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_THAT(data.device_name, IsEmpty());
+ ASSERT_THAT(data.loop_name, StartsWith("/dev"));
+ });
+ // com.android.apex.test_package should be mounted on top of dm-verity device.
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.test_package@2.chroot");
+ ASSERT_THAT(data.loop_name, StartsWith("/dev"));
+ });
+}
+
+TEST_F(ApexdMountTest,
+ OnOtaChrootBootstrapFailsToActivatePreInstalledApexKeepsGoing) {
+ std::string apex_path_1 =
+ AddPreInstalledApex("apex.apexd_test_manifest_mismatch.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 137, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ false, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest,
+ OnOtaChrootBootstrapFailsToActivateDataApexFallsBackToPreInstalled) {
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 =
+ AddDataApex("apex.apexd_test_manifest_mismatch.apex");
+
+ ASSERT_EQ(OnOtaChrootBootstrap(), 0);
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_path_1,
+ /* preinstalledModulePath= */ apex_path_1,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true, GetMTime(apex_path_1));
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_path_2, /* preinstalledModulePath= */ apex_path_2,
+ /* versionCode= */ 1, /* versionName= */ "1", /* isFactory= */ true,
+ /* isActive= */ true, GetMTime(apex_path_2));
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest, OnOtaChrootBootstrapFlattenedApex) {
+ std::string apex_dir_1 = GetBuiltInDir() + "/com.android.apex.test_package";
+ std::string apex_dir_2 = GetBuiltInDir() + "/com.android.apex.test_package_2";
+
+ ASSERT_EQ(mkdir(apex_dir_1.c_str(), 0755), 0);
+ ASSERT_EQ(mkdir(apex_dir_2.c_str(), 0755), 0);
+
+ auto write_manifest_fn = [&](const std::string& apex_dir,
+ const std::string& module_name, int version) {
+ using ::apex::proto::ApexManifest;
+
+ ApexManifest manifest;
+ manifest.set_name(module_name);
+ manifest.set_version(version);
+ manifest.set_versionname(std::to_string(version));
+
+ std::string out;
+ manifest.SerializeToString(&out);
+ ASSERT_TRUE(WriteStringToFile(out, apex_dir + "/apex_manifest.pb"));
+ };
+
+ write_manifest_fn(apex_dir_1, "com.android.apex.test_package", 2);
+ write_manifest_fn(apex_dir_2, "com.android.apex.test_package_2", 1);
+
+ ASSERT_EQ(OnOtaChrootBootstrapFlattenedApex(), 0);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package_2"));
+
+ ASSERT_EQ(access("/apex/apex-info-list.xml", F_OK), 0);
+ ASSERT_EQ(GetSelinuxContext("/apex/apex-info-list.xml"),
+ "u:object_r:apex_info_file:s0");
+
+ auto info_list =
+ com::android::apex::readApexInfoList("/apex/apex-info-list.xml");
+ ASSERT_TRUE(info_list.has_value());
+ auto apex_info_xml_1 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package",
+ /* modulePath= */ apex_dir_1,
+ /* preinstalledModulePath= */ apex_dir_1,
+ /* versionCode= */ 2, /* versionName= */ "2",
+ /* isFactory= */ true, /* isActive= */ true,
+ /* lastUpdateMillis= */ 0);
+ auto apex_info_xml_2 = com::android::apex::ApexInfo(
+ /* moduleName= */ "com.android.apex.test_package_2",
+ /* modulePath= */ apex_dir_2,
+ /* preinstalledModulePath= */ apex_dir_2,
+ /* versionCode= */ 1, /* versionName= */ "1",
+ /* isFactory= */ true, /* isActive= */ true,
+ /* lastUpdateMillis= */ 0);
+
+ ASSERT_THAT(info_list->getApexInfo(),
+ UnorderedElementsAre(ApexInfoXmlEq(apex_info_xml_1),
+ ApexInfoXmlEq(apex_info_xml_2)));
+}
+
+TEST_F(ApexdMountTest, OnStartOnlyPreInstalledApexes) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+}
+
+TEST_F(ApexdMountTest, OnStartDataHasHigherVersion) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+}
+
+TEST_F(ApexdMountTest, OnStartDataHasWrongSHA) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path = AddPreInstalledApex("com.android.apex.cts.shim.apex");
+ AddDataApex("com.android.apex.cts.shim.v2_wrong_sha.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ UnmountOnTearDown(apex_path);
+ OnStart();
+
+ // Check system shim apex is activated instead of the data one.
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.cts.shim",
+ "/apex/com.android.apex.cts.shim@1"));
+}
+
+TEST_F(ApexdMountTest, OnStartDataHasSameVersion) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from data apex, not pre-installed one.
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path_3);
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartSystemHasHigherVersion) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test_v2.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ AddDataApex("apex.apexd_test.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from pre-installed one.
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path_1);
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartFailsToActivateApexOnDataFallsBackToBuiltIn) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ AddDataApex("apex.apexd_test_manifest_mismatch.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from pre-installed apex.
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path_1);
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartApexOnDataHasWrongKeyFallsBackToBuiltIn) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path_1 = AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 =
+ AddDataApex("apex.apexd_test_different_key_v2.apex");
+
+ {
+ auto apex = ApexFile::Open(apex_path_3);
+ ASSERT_TRUE(IsOk(apex));
+ ASSERT_EQ(static_cast<uint64_t>(apex->GetManifest().version()), 2ULL);
+ }
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from pre-installed apex.
+ db.ForallMountedApexes("com.android.apex.test_package",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path_1);
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartOnlyPreInstalledCapexes) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path_1 =
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Decompressed APEX should be mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1");
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartDataHasHigherVersionThanCapex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ std::string apex_path_2 =
+ AddDataApex("com.android.apex.compressed.v2_original.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ UnmountOnTearDown(apex_path_2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@2"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from data apex.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path_2);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@2");
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartDataHasSameVersionAsCapex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ std::string apex_path_2 =
+ AddDataApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Data APEX should be mounted
+ UnmountOnTearDown(apex_path_2);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from data apex, not pre-installed one.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path_2);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1");
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartSystemHasHigherVersionCapexThanData) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ std::string apex_path_1 =
+ AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+ AddDataApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Decompressed APEX should be mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@2"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from compressed apex
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@2");
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartFailsToActivateApexOnDataFallsBackToCapex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Decompressed APEX should be mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex. It should also be mounted
+ // on dm-verity device.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1");
+ });
+}
+
+// Test scenario when we fallback to capex but it already has a decompressed
+// version on data
+TEST_F(ApexdMountTest, OnStartFallbackToAlreadyDecompressedCapex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex");
+ AddDataApex("com.android.apex.compressed.v2_manifest_mismatch.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Decompressed APEX should be mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1");
+ });
+}
+
+// Test scenario when we fallback to capex but it has same version as corrupt
+// data apex
+TEST_F(ApexdMountTest, OnStartFallbackToCapexSameVersion) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+ // Add data apex using the common naming convention for /data/apex/active
+ // directory
+ fs::copy(GetTestFile("com.android.apex.compressed.v2_manifest_mismatch.apex"),
+ GetDataDir() + "/com.android.apex.compressed@2.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Decompressed APEX should be mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@2"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@2");
+ });
+}
+
+TEST_F(ApexdMountTest, OnStartCapexToApex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex",
+ previous_built_in_dir.path);
+ auto apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Uncompressed APEX should be mounted
+ UnmountOnTearDown(apex_path);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from decompressed apex.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path);
+ ASSERT_THAT(data.device_name, IsEmpty());
+ });
+}
+
+// Test to ensure we do not mount decompressed APEX from /data/apex/active
+TEST_F(ApexdMountTest, OnStartOrphanedDecompressedApexInActiveDirectory) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Place a decompressed APEX in /data/apex/active. This apex should not
+ // be mounted since it's not in correct location. Instead, the
+ // pre-installed APEX should be mounted.
+ auto decompressed_apex_in_active_dir =
+ StringPrintf("%s/com.android.apex.compressed@1%s", GetDataDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
+ decompressed_apex_in_active_dir);
+ auto apex_path =
+ AddPreInstalledApex("com.android.apex.compressed.v1_original.apex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Pre-installed APEX should be mounted
+ UnmountOnTearDown(apex_path);
+ auto& db = GetApexDatabaseForTesting();
+ // Check that pre-installed APEX has been activated
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, apex_path);
+ ASSERT_THAT(data.device_name, IsEmpty());
+ });
+}
+
+// Test scenario when decompressed version has different version than
+// pre-installed CAPEX
+TEST_F(ApexdMountTest, OnStartDecompressedApexVersionDifferentThanCapex) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ TemporaryDir previous_built_in_dir;
+ PrepareCompressedApex("com.android.apex.compressed.v2.capex",
+ previous_built_in_dir.path);
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Existing higher version decompressed APEX should be ignored and new
+ // pre-installed CAPEX should be decompressed and mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ ASSERT_EQ(GetProperty(kTestApexdStatusSysprop, ""), "starting");
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1"));
+ auto& db = GetApexDatabaseForTesting();
+ // Check that it was mounted from newly decompressed apex.
+ db.ForallMountedApexes("com.android.apex.compressed",
+ [&](const MountedApexData& data, bool latest) {
+ ASSERT_TRUE(latest);
+ ASSERT_EQ(data.full_path, decompressed_active_apex);
+ ASSERT_EQ(data.device_name,
+ "com.android.apex.compressed@1");
+ });
+}
+
+// Test that ota_apex is persisted until slot switch
+TEST_F(ApexdMountTest, OnStartOtaApexKeptUntilSlotSwitch) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Imagine current system has v1 capex and we have v2 incoming via ota
+ auto old_capex = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ auto ota_apex_path =
+ StringPrintf("%s/com.android.apex.compressed@2%s",
+ GetDecompressionDir().c_str(), kOtaApexPackageSuffix);
+ fs::copy(GetTestFile("com.android.apex.compressed.v2_original.apex"),
+ ota_apex_path.c_str());
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ // When we call OnStart for the first time, it will decompress v1 capex and
+ // activate it, while after second call it will decompress v2 capex and
+ // activate it. We need to make sure that activated APEXes are cleaned up
+ // after test finishes.
+ auto old_decompressed_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ auto new_decompressed_apex = StringPrintf(
+ "%s/com.android.apex.compressed@2%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(old_decompressed_apex);
+ UnmountOnTearDown(new_decompressed_apex);
+
+ // First try starting without slot switch. Since we are booting with
+ // old pre-installed capex, ota_apex should not be deleted
+ OnStart();
+ auto path_exists = PathExists(ota_apex_path);
+ ASSERT_TRUE(*path_exists);
+
+ // When we switch slot, the pre-installed APEX will match ota_apex
+ // and the ota_apex will end up getting renamed.
+ RemoveFileIfExists(old_capex);
+ AddPreInstalledApex("com.android.apex.compressed.v2.capex");
+ ApexFileRepository::GetInstance().Reset(GetDecompressionDir());
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+ OnStart();
+ path_exists = PathExists(ota_apex_path);
+ ASSERT_FALSE(*path_exists);
+}
+
+// Test scenario when decompressed version has same version but different
+// digest
+TEST_F(ApexdMountTest,
+ OnStartDecompressedApexVersionSameAsCapexDifferentDigest) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ // Push a CAPEX to system without decompressing it
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+ auto pre_installed_apex = ApexFile::Open(apex_path);
+ // Now push an APEX with different root digest as decompressed APEX
+ auto decompressed_apex_path = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ fs::copy(GetTestFile(
+ "com.android.apex.compressed.v1_different_digest_original.apex"),
+ decompressed_apex_path);
+ auto different_digest_apex = ApexFile::Open(decompressed_apex_path);
+ auto different_digest = GetRootDigest(*different_digest_apex);
+ ASSERT_NE(
+ pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
+ different_digest);
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Existing same version decompressed APEX with different root digest should
+ // be ignored and the pre-installed CAPEX should be decompressed again.
+ UnmountOnTearDown(decompressed_apex_path);
+
+ // Ensure decompressed apex has same digest as pre-installed
+ auto decompressed_apex = ApexFile::Open(decompressed_apex_path);
+ ASSERT_EQ(
+ pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
+ GetRootDigest(*decompressed_apex));
+ ASSERT_NE(GetRootDigest(*decompressed_apex), different_digest);
+}
+
+// Test when decompressed APEX has different key than CAPEX
+TEST_F(ApexdMountTest, OnStartDecompressedApexVersionSameAsCapexDifferentKey) {
+ MockCheckpointInterface checkpoint_interface;
+ // Need to call InitializeVold before calling OnStart
+ InitializeVold(&checkpoint_interface);
+
+ TemporaryDir previous_built_in_dir;
+ auto different_key_apex_path =
+ PrepareCompressedApex("com.android.apex.compressed_different_key.capex",
+ previous_built_in_dir.path);
+ // Place a same version capex in current built_in_dir, which has different key
+ auto apex_path = AddPreInstalledApex("com.android.apex.compressed.v1.capex");
+
+ ASSERT_RESULT_OK(
+ ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()}));
+
+ OnStart();
+
+ // Existing same version decompressed APEX should be ignored and new
+ // pre-installed CAPEX should be decompressed and mounted
+ std::string decompressed_active_apex = StringPrintf(
+ "%s/com.android.apex.compressed@1%s", GetDecompressionDir().c_str(),
+ kDecompressedApexPackageSuffix);
+ UnmountOnTearDown(decompressed_active_apex);
+
+ // Ensure decompressed apex has same digest as pre-installed
+ auto pre_installed_apex = ApexFile::Open(apex_path);
+ auto decompressed_apex = ApexFile::Open(decompressed_active_apex);
+ auto different_key_apex = ApexFile::Open(different_key_apex_path);
+ ASSERT_EQ(
+ pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
+ GetRootDigest(*decompressed_apex));
+ ASSERT_NE(
+ pre_installed_apex->GetManifest().capexmetadata().originalapexdigest(),
+ GetRootDigest(*different_key_apex));
+}
+
+TEST_F(ApexdMountTest, PopulateFromMountsChecksPathPrefix) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path = AddDataApex("apex.apexd_test_v2.apex");
+
+ // Mount an apex from decomrpession_dir
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex");
+ std::string decompressed_apex =
+ StringPrintf("%s/com.android.apex.compressed@1.decompressed.apex",
+ GetDecompressionDir().c_str());
+
+ // Mount an apex from some other directory
+ TemporaryDir td;
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), td.path);
+ std::string other_apex =
+ StringPrintf("%s/apex.apexd_test_different_app.apex", td.path);
+
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_path)));
+ ASSERT_TRUE(IsOk(ActivatePackage(decompressed_apex)));
+ ASSERT_TRUE(IsOk(ActivatePackage(other_apex)));
+
+ auto& db = GetApexDatabaseForTesting();
+ // Remember mount information for |other_apex|, since it won't be available in
+ // the database. We will need to tear it down manually.
+ std::optional<MountedApexData> other_apex_mount_data;
+ db.ForallMountedApexes(
+ "com.android.apex.test_package_2",
+ [&other_apex_mount_data](const MountedApexData& data, bool latest) {
+ if (latest) {
+ other_apex_mount_data.emplace(data);
+ }
+ });
+ UnmountOnTearDown(apex_path);
+ UnmountOnTearDown(decompressed_apex);
+ ASSERT_TRUE(other_apex_mount_data.has_value());
+ auto deleter = make_scope_guard([&other_apex_mount_data]() {
+ if (!other_apex_mount_data.has_value()) {
+ return;
+ }
+ if (umount2("/apex/com.android.apex.test_package_2", 0) != 0) {
+ PLOG(ERROR) << "Failed to unmount /apex/com.android.apex.test_package_2";
+ }
+ auto res = Unmount(*other_apex_mount_data, /* deferred= */ false);
+ if (!res.ok()) {
+ LOG(ERROR) << res.error();
+ }
+ });
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ // Clear the database before calling PopulateFromMounts
+ db.Reset();
+
+ // Populate from mount
+ db.PopulateFromMounts(GetDataDir(), GetDecompressionDir(), GetHashTreeDir());
+
+ // Count number of package and collect package names
+ int package_count = 0;
+ std::vector<std::string> mounted_paths;
+ db.ForallMountedApexes([&](const std::string& package,
+ const MountedApexData& data, bool latest) {
+ package_count++;
+ mounted_paths.push_back(data.full_path);
+ });
+ ASSERT_EQ(package_count, 2);
+ ASSERT_THAT(mounted_paths,
+ UnorderedElementsAre(apex_path, decompressed_apex));
+}
+
+TEST_F(ApexdMountTest, UnmountAll) {
+ AddPreInstalledApex("apex.apexd_test.apex");
+ std::string apex_path_2 =
+ AddPreInstalledApex("apex.apexd_test_different_app.apex");
+ std::string apex_path_3 = AddDataApex("apex.apexd_test_v2.apex");
+
+ // Mount an apex from decomrpession_dir
+ PrepareCompressedApex("com.android.apex.compressed.v1.capex");
+ std::string decompressed_apex =
+ StringPrintf("%s/com.android.apex.compressed@1.decompressed.apex",
+ GetDecompressionDir().c_str());
+
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_path_2)));
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_path_3)));
+ ASSERT_TRUE(IsOk(ActivatePackage(decompressed_apex)));
+ UnmountOnTearDown(apex_path_2);
+ UnmountOnTearDown(apex_path_3);
+ UnmountOnTearDown(decompressed_apex);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test_package",
+ "/apex/com.android.apex.test_package@2",
+ "/apex/com.android.apex.compressed",
+ "/apex/com.android.apex.compressed@1",
+ "/apex/com.android.apex.test_package_2",
+ "/apex/com.android.apex.test_package_2@1"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // UnmountAll expects apex database to empty, hence this reset.
+ db.Reset();
+
+ ASSERT_EQ(0, UnmountAll());
+
+ auto new_apex_mounts = GetApexMounts();
+ ASSERT_EQ(new_apex_mounts.size(), 0u);
+}
+
+TEST_F(ApexdMountTest, UnmountAllSharedLibsApex) {
+ ASSERT_EQ(mkdir("/apex/sharedlibs", 0755), 0);
+ ASSERT_EQ(mkdir("/apex/sharedlibs/lib", 0755), 0);
+ ASSERT_EQ(mkdir("/apex/sharedlibs/lib64", 0755), 0);
+ auto deleter = make_scope_guard([]() {
+ std::error_code ec;
+ fs::remove_all("/apex/sharedlibs", ec);
+ if (ec) {
+ LOG(ERROR) << "Failed to delete /apex/sharedlibs : " << ec;
+ }
+ });
+
+ std::string apex_path_1 = AddPreInstalledApex(
+ "com.android.apex.test.sharedlibs_generated.v1.libvX.apex");
+ std::string apex_path_2 =
+ AddDataApex("com.android.apex.test.sharedlibs_generated.v2.libvY.apex");
+
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_path_1)));
+ ASSERT_TRUE(IsOk(ActivatePackage(apex_path_2)));
+ UnmountOnTearDown(apex_path_1);
+ UnmountOnTearDown(apex_path_2);
+
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts,
+ UnorderedElementsAre("/apex/com.android.apex.test.sharedlibs@1",
+ "/apex/com.android.apex.test.sharedlibs@2"));
+
+ auto& db = GetApexDatabaseForTesting();
+ // UnmountAll expects apex database to empty, hence this reset.
+ db.Reset();
+
+ ASSERT_EQ(0, UnmountAll());
+
+ auto new_apex_mounts = GetApexMounts();
+ ASSERT_EQ(new_apex_mounts.size(), 0u);
+}
+
+class ApexActivationFailureTests : public ApexdMountTest {};
+
+TEST_F(ApexActivationFailureTests, BuildFingerprintDifferent) {
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ apex_session->SetBuildFingerprint("wrong fingerprint");
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+
+ OnStart();
+
+ apex_session = ApexSession::GetSession(123);
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("APEX build fingerprint has changed"));
+}
+
+TEST_F(ApexActivationFailureTests, ApexFileMissingInStagingDirectory) {
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+ // Delete the apex file in staging directory
+ DeleteDirContent(GetStagedDir(123));
+
+ OnStart();
+
+ apex_session = ApexSession::GetSession(123);
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("No APEX packages found"));
+}
+
+TEST_F(ApexActivationFailureTests, MultipleApexFileInStagingDirectory) {
+ auto apex_session = CreateStagedSession("apex.apexd_test.apex", 123);
+ CreateStagedSession("com.android.apex.compressed.v1_original.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+
+ OnStart();
+
+ apex_session = ApexSession::GetSession(123);
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("More than one APEX package found"));
+}
+
+TEST_F(ApexActivationFailureTests, PostInstallFailsForApex) {
+ auto apex_session =
+ CreateStagedSession("apex.apexd_test_corrupt_superblock_apex.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+
+ OnStart();
+
+ apex_session = ApexSession::GetSession(123);
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("Postinstall failed for session"));
+}
+
+TEST_F(ApexActivationFailureTests, CorruptedApexCannotBeStaged) {
+ auto apex_session = CreateStagedSession("corrupted_b146895998.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+
+ OnStart();
+
+ apex_session = ApexSession::GetSession(123);
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("Activation failed for packages"));
+}
+
+TEST_F(ApexActivationFailureTests, ActivatePackageImplFails) {
+ auto shim_path = AddPreInstalledApex("com.android.apex.cts.shim.apex");
+ auto& instance = ApexFileRepository::GetInstance();
+ ASSERT_RESULT_OK(instance.AddPreInstalledApex({GetBuiltInDir()}));
+
+ auto apex_session =
+ CreateStagedSession("com.android.apex.cts.shim.v2_wrong_sha.apex", 123);
+ apex_session->UpdateStateAndCommit(SessionState::STAGED);
+
+ UnmountOnTearDown(shim_path);
+ OnStart();
+
+ apex_session = ApexSession::GetSession(123);
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("Failed to activate packages"));
+ ASSERT_THAT(apex_session->GetErrorMessage(),
+ HasSubstr("has unexpected SHA512 hash"));
+}
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apexd_test_utils.h b/apexd/apexd_test_utils.h
index 0e43477d..5e3689d3 100644
--- a/apexd/apexd_test_utils.h
+++ b/apexd/apexd_test_utils.h
@@ -14,14 +14,34 @@
* limitations under the License.
*/
+#include <filesystem>
+#include <fstream>
+
+#include <asm-generic/errno-base.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <sched.h>
+#include <sys/mount.h>
+
+#include <android-base/errors.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <android/apex/ApexInfo.h>
#include <android/apex/ApexSessionInfo.h>
#include <binder/IServiceManager.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
+#include <selinux/android.h>
+#include "apex_file.h"
#include "session_state.pb.h"
+#include "com_android_apex.h"
+
+using android::base::Error;
+using android::base::Result;
using apex::proto::SessionState;
namespace android {
@@ -32,6 +52,7 @@ using ::testing::AllOf;
using ::testing::Eq;
using ::testing::ExplainMatchResult;
using ::testing::Field;
+using ::testing::Property;
template <typename T>
inline ::testing::AssertionResult IsOk(const android::base::Result<T>& result) {
@@ -85,6 +106,22 @@ MATCHER_P(ApexInfoEq, other, "") {
arg, result_listener);
}
+MATCHER_P(ApexFileEq, other, "") {
+ return ExplainMatchResult(
+ AllOf(Property("path", &ApexFile::GetPath, Eq(other.get().GetPath())),
+ Property("image_offset", &ApexFile::GetImageOffset,
+ Eq(other.get().GetImageOffset())),
+ Property("image_size", &ApexFile::GetImageSize,
+ Eq(other.get().GetImageSize())),
+ Property("fs_type", &ApexFile::GetFsType,
+ Eq(other.get().GetFsType())),
+ Property("public_key", &ApexFile::GetBundledPublicKey,
+ Eq(other.get().GetBundledPublicKey())),
+ Property("is_compressed", &ApexFile::IsCompressed,
+ Eq(other.get().IsCompressed()))),
+ arg, result_listener);
+}
+
inline ApexSessionInfo CreateSessionInfo(int session_id) {
ApexSessionInfo info;
info.sessionId = session_id;
@@ -128,5 +165,194 @@ inline void PrintTo(const ApexInfo& apex, std::ostream* os) {
*os << "}";
}
+inline Result<bool> CompareFiles(const std::string& filename1,
+ const std::string& filename2) {
+ std::ifstream file1(filename1, std::ios::binary);
+ std::ifstream file2(filename2, std::ios::binary);
+
+ if (file1.bad() || file2.bad()) {
+ return Error() << "Could not open one of the file";
+ }
+
+ std::istreambuf_iterator<char> begin1(file1);
+ std::istreambuf_iterator<char> begin2(file2);
+
+ return std::equal(begin1, std::istreambuf_iterator<char>(), begin2);
+}
+
+inline android::base::Result<std::string> GetCurrentMountNamespace() {
+ std::string result;
+ if (!android::base::Readlink("/proc/self/ns/mnt", &result)) {
+ return android::base::ErrnoError() << "Failed to read /proc/self/ns/mnt";
+ }
+ return result;
+}
+
+// A helper class to switch back to the original mount namespace of a process
+// upon exiting current scope.
+class MountNamespaceRestorer final {
+ public:
+ explicit MountNamespaceRestorer() {
+ original_namespace_.reset(open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC));
+ if (original_namespace_.get() < 0) {
+ PLOG(ERROR) << "Failed to open /proc/self/ns/mnt";
+ }
+ }
+
+ ~MountNamespaceRestorer() {
+ if (original_namespace_.get() != -1) {
+ if (setns(original_namespace_.get(), CLONE_NEWNS) == -1) {
+ PLOG(ERROR) << "Failed to switch back to " << original_namespace_.get();
+ }
+ }
+ }
+
+ private:
+ android::base::unique_fd original_namespace_;
+ DISALLOW_COPY_AND_ASSIGN(MountNamespaceRestorer);
+};
+
+inline std::vector<std::string> GetApexMounts() {
+ std::vector<std::string> apex_mounts;
+ std::string mount_info;
+ if (!android::base::ReadFileToString("/proc/self/mountinfo", &mount_info)) {
+ return apex_mounts;
+ }
+ for (const auto& line : android::base::Split(mount_info, "\n")) {
+ std::vector<std::string> tokens = android::base::Split(line, " ");
+ // line format:
+ // mnt_id parent_mnt_id major:minor source target option propagation_type
+ // ex) 33 260:19 / /apex rw,nosuid,nodev -
+ if (tokens.size() >= 7 && android::base::StartsWith(tokens[4], "/apex/")) {
+ apex_mounts.push_back(tokens[4]);
+ }
+ }
+ return apex_mounts;
+}
+
+// Sets up a test environment for unit testing logic around mounting/unmounting
+// apexes. For examples of usage see apexd_test.cpp
+inline android::base::Result<void> SetUpApexTestEnvironment() {
+ using android::base::ErrnoError;
+
+ // 1. Switch to new mount namespace.
+ if (unshare(CLONE_NEWNS) != 0) {
+ return ErrnoError() << "Failed to unshare";
+ }
+
+ // 2. Make everything private, so that changes don't propagate.
+ if (mount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) {
+ return ErrnoError() << "Failed to mount / as private";
+ }
+
+ // 3. Unmount all apexes. This needs to happen in two phases:
+ // Note: unlike regular unmount flow in apexd, we don't destroy dm and loop
+ // devices, since that would've propagated outside of the test environment.
+ std::vector<std::string> apex_mounts = GetApexMounts();
+
+ // 3a. First unmount all bind mounds (without @version_code).
+ for (const auto& mount : apex_mounts) {
+ if (mount.find('@') == std::string::npos) {
+ if (umount2(mount.c_str(), 0) != 0) {
+ return ErrnoError() << "Failed to unmount " << mount;
+ }
+ }
+ }
+
+ // 3.b Now unmount versioned mounts.
+ for (const auto& mount : apex_mounts) {
+ if (mount.find('@') != std::string::npos) {
+ if (umount2(mount.c_str(), 0) != 0) {
+ return ErrnoError() << "Failed to unmount " << mount;
+ }
+ }
+ }
+
+ static constexpr const char* kApexMountForTest = "/mnt/scratch/apex";
+
+ // Clean up in case previous test left directory behind.
+ if (access(kApexMountForTest, F_OK) == 0) {
+ if (umount2(kApexMountForTest, MNT_FORCE | UMOUNT_NOFOLLOW) != 0) {
+ PLOG(WARNING) << "Failed to unmount " << kApexMountForTest;
+ }
+ if (rmdir(kApexMountForTest) != 0) {
+ return ErrnoError() << "Failed to rmdir " << kApexMountForTest;
+ }
+ }
+
+ // 4. Create an empty tmpfs that will substitute /apex in tests.
+ if (mkdir(kApexMountForTest, 0755) != 0) {
+ return ErrnoError() << "Failed to mkdir " << kApexMountForTest;
+ }
+
+ if (mount("tmpfs", kApexMountForTest, "tmpfs", 0, nullptr) == -1) {
+ return ErrnoError() << "Failed to mount " << kApexMountForTest;
+ }
+
+ // 5. Overlay it over /apex via bind mount.
+ if (mount(kApexMountForTest, "/apex", nullptr, MS_BIND, nullptr) == -1) {
+ return ErrnoError() << "Failed to bind mount " << kApexMountForTest
+ << " over /apex";
+ }
+
+ // Just in case, run restorecon -R on /apex.
+ if (selinux_android_restorecon("/apex", SELINUX_ANDROID_RESTORECON_RECURSE) <
+ 0) {
+ return android::base::ErrnoError() << "Failed to restorecon /apex";
+ }
+
+ return {};
+}
+
+} // namespace apex
+} // namespace android
+
+namespace com {
+namespace android {
+namespace apex {
+
+namespace testing {
+MATCHER_P(ApexInfoXmlEq, other, "") {
+ using ::testing::AllOf;
+ using ::testing::Eq;
+ using ::testing::ExplainMatchResult;
+ using ::testing::Field;
+ using ::testing::Property;
+
+ return ExplainMatchResult(
+ AllOf(
+ Property("moduleName", &ApexInfo::getModuleName,
+ Eq(other.getModuleName())),
+ Property("modulePath", &ApexInfo::getModulePath,
+ Eq(other.getModulePath())),
+ Property("preinstalledModulePath",
+ &ApexInfo::getPreinstalledModulePath,
+ Eq(other.getPreinstalledModulePath())),
+ Property("versionCode", &ApexInfo::getVersionCode,
+ Eq(other.getVersionCode())),
+ Property("isFactory", &ApexInfo::getIsFactory,
+ Eq(other.getIsFactory())),
+ Property("isActive", &ApexInfo::getIsActive, Eq(other.getIsActive())),
+ Property("lastUpdateMillis", &ApexInfo::getLastUpdateMillis,
+ Eq(other.getLastUpdateMillis()))),
+ arg, result_listener);
+}
+
+} // namespace testing
+
+// Must be in com::android::apex namespace for gtest to pick it up.
+inline void PrintTo(const ApexInfo& apex, std::ostream* os) {
+ *os << "apex_info: {\n";
+ *os << " moduleName : " << apex.getModuleName() << "\n";
+ *os << " modulePath : " << apex.getModulePath() << "\n";
+ *os << " preinstalledModulePath : " << apex.getPreinstalledModulePath()
+ << "\n";
+ *os << " versionCode : " << apex.getVersionCode() << "\n";
+ *os << " isFactory : " << apex.getIsFactory() << "\n";
+ *os << " isActive : " << apex.getIsActive() << "\n";
+ *os << "}";
+}
+
} // namespace apex
} // namespace android
+} // namespace com
diff --git a/apexd/apexd_testdata/Android.bp b/apexd/apexd_testdata/Android.bp
index 1f74a532..0e7039a0 100644
--- a/apexd/apexd_testdata/Android.bp
+++ b/apexd/apexd_testdata/Android.bp
@@ -15,6 +15,10 @@
// These apex definitions will generate the prebuilt test data. The modules
// are disabled so as not to pollute the build.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
apex_key {
name: "com.android.apex.test_package.key",
public_key: "com.android.apex.test_package.avbpubkey",
@@ -22,6 +26,13 @@ apex_key {
installable: false,
}
+apex_key {
+ name: "com.android.apex.compressed.key",
+ public_key: "com.android.apex.compressed.avbpubkey",
+ private_key: "com.android.apex.compressed.pem",
+ installable: false,
+}
+
apex {
name: "apex.apexd_test",
manifest: "manifest.json",
@@ -33,13 +44,173 @@ apex {
}
apex {
+ name: "com.android.apex.compressed.v1",
+ manifest: "manifest_compressed.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ test_only_force_compression: true,
+ updatable: false,
+}
+
+apex {
+ name: "com.android.apex.compressed.v1_different_digest",
+ manifest: "manifest_compressed.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["hash_of_dev_null"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ test_only_force_compression: true,
+ updatable: false,
+}
+
+genrule {
+ // Generates a compressed apex which doesn't have an original_apex file in it
+ name: "gen_capex_without_apex",
+ out: ["com.android.apex.compressed.v1_without_apex.capex"],
+ srcs: [":com.android.apex.compressed.v1"],
+ tools: ["soong_zip"],
+ cmd: "unzip -q $(in) -d $(genDir) && rm $(genDir)/original_apex && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) -L 9 " +
+ "-o $(genDir)/com.android.apex.compressed.v1_without_apex.capex"
+}
+
+genrule {
+ // Generates a compressed apex which has different version of original_apex in it
+ name: "gen_capex_with_v2_apex",
+ out: ["com.android.apex.compressed.v1_with_v2_apex.capex"],
+ srcs: [":com.android.apex.compressed.v2"],
+ tools: ["soong_zip", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop version 1 $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) -L 9 " +
+ "-o $(genDir)/com.android.apex.compressed.v1_with_v2_apex.capex"
+}
+
+genrule {
+ // Generates a compressed apex which can be opened but not decompressed
+ name: "gen_capex_not_decompressible",
+ out: ["com.android.apex.compressed.v1_not_decompressible.capex"],
+ srcs: [":com.android.apex.compressed.v1"],
+ tools: ["soong_zip", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && echo '' > $(genDir)/original_apex && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) -L 9 " +
+ "-o $(genDir)/com.android.apex.compressed.v1_not_decompressible.capex"
+}
+
+genrule {
+ // Generates a capex which has same module name as com.android.apex.compressed, but
+ // is contains a different public key.
+ name: "gen_key_mismatch_capex",
+ out: ["com.android.apex.compressed_different_key.capex"],
+ srcs: [":apex.apexd_test_no_inst_key"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest", "apex_compression_tool"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop name com.android.apex.compressed $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/com.android.apex.compressed_different_key.apex && " +
+ "$(location apex_compression_tool) compress " +
+ "--apex_compression_tool_path='out/soong/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin' " +
+ "--input=$(genDir)/com.android.apex.compressed_different_key.apex " +
+ "--output=$(genDir)/com.android.apex.compressed_different_key.capex"
+}
+
+genrule {
+ // Generates a capex which has a different public key than original_apex
+ name: "gen_key_mismatch_with_original_capex",
+ out: ["com.android.apex.compressed_key_mismatch_with_original.capex"],
+ srcs: [":com.android.apex.compressed.v1"],
+ tools: ["soong_zip"],
+ cmd: "unzip -q $(in) -d $(genDir) && " + // unzip input
+ "echo 'different-key' >> $(genDir)/apex_pubkey && " + // modify the public key
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) -L 9 " +// repack the compressed APEX
+ "-o $(genDir)/com.android.apex.compressed_key_mismatch_with_original.capex",
+}
+
+genrule {
+ // Generates an apex which has a different manifest outside the filesystem
+ // image.
+ name: "gen_manifest_mismatch_compressed_apex_v2",
+ out: ["com.android.apex.compressed.v2_manifest_mismatch.apex"],
+ srcs: [":com.android.apex.compressed.v2_original"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop version 137 $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/com.android.apex.compressed.v2_manifest_mismatch.apex"
+}
+
+apex {
+ name: "com.android.apex.compressed.v1_original",
+ manifest: "manifest_compressed.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ compressible: false,
+ updatable: false,
+}
+
+apex {
+ name: "com.android.apex.compressed.v1_different_digest_original",
+ manifest: "manifest_compressed.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["hash_of_dev_null"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ compressible: false,
+ updatable: false,
+}
+
+apex {
+ name: "com.android.apex.compressed.v2",
+ manifest: "manifest_compressed_v2.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ test_only_force_compression: true,
+ updatable: false,
+}
+
+apex {
+ name: "com.android.apex.compressed.v2_original",
+ manifest: "manifest_compressed_v2.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ compressible: false,
+ updatable: false,
+}
+
+apex {
+ name: "apex.apexd_test_f2fs",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ min_sdk_version: "current",
+ payload_fs_type: "f2fs",
+}
+
+apex {
name: "apex.apexd_test_no_hashtree",
manifest: "manifest.json",
file_contexts: ":apex.test-file_contexts",
prebuilts: ["sample_prebuilt_file"],
key: "com.android.apex.test_package.key",
installable: false,
- test_only_no_hashtree: true,
+ generate_hashtree: false,
+ updatable: false,
}
// This APEX has same name and version as apex.apexd_test_no_hashtree, but has
@@ -55,7 +226,8 @@ apex {
],
key: "com.android.apex.test_package.key",
installable: false,
- test_only_no_hashtree: true,
+ generate_hashtree: false,
+ updatable: false,
}
apex {
@@ -65,6 +237,7 @@ apex {
prebuilts: ["sample_prebuilt_file"],
key: "com.android.apex.test_package.key",
installable: false,
+ updatable: false,
}
apex {
@@ -92,6 +265,7 @@ apex {
prebuilts: ["sample_prebuilt_file"],
key: "com.android.apex.test_package.key",
installable: false,
+ updatable: false,
}
apex_key {
@@ -109,6 +283,7 @@ apex {
key: "com.android.apex.test_package.preinstall.key",
binaries: ["apex_test_preInstallHook"],
installable: false,
+ updatable: false,
}
apex_key {
@@ -126,6 +301,7 @@ apex {
key: "com.android.apex.test_package.postinstall.key",
binaries: ["apex_test_postInstallHook"],
installable: false,
+ updatable: false,
}
apex_key {
@@ -143,6 +319,7 @@ apex {
key: "com.android.apex.test_package.prepostinstall.fail.key",
binaries: ["apex_test_prePostInstallHookFail"],
installable: false,
+ updatable: false,
}
apex_key {
@@ -159,6 +336,18 @@ apex {
prebuilts: ["sample_prebuilt_file"],
key: "com.android.apex.test_package.no_inst_key.key",
installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "apex.apexd_test_f2fs_no_inst_key",
+ manifest: "manifest_no_inst_key.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.test_package.no_inst_key.key",
+ installable: false,
+ payload_fs_type: "f2fs",
+ updatable: false,
}
apex_key {
@@ -175,6 +364,7 @@ apex {
prebuilts: ["sample_prebuilt_file"],
key: "com.android.apex.test_package_2.key",
installable: false,
+ updatable: false,
}
sh_binary {
@@ -199,6 +389,7 @@ apex {
prebuilts: ["sample_prebuilt_file"],
key: "com.android.apex.test_package.key",
installable: false,
+ updatable: false,
}
prebuilt_etc {
@@ -212,3 +403,156 @@ prebuilt_apex {
filename: "corrupted_b146895998.apex",
installable: false,
}
+
+// APEX for banned name test cannot be generated at build time.
+// This file can be generated manually by creating new apex target
+// with manifest name 'sharedlibs', and modify aapt2 to skip validating
+// package name from aapt::util::IsAndroidPackageName().
+prebuilt_apex {
+ name: "apex.banned_name",
+ src: "sharedlibs.apex",
+ filename: "sharedlibs.apex",
+ installable: false,
+}
+
+// A compressed apex that also provides shared libs.
+// Should be declined by ApexFile::Open.
+apex {
+ name: "com.android.apex.compressed_sharedlibs",
+ manifest: "manifest_compressed_sharedlibs.json",
+ file_contexts: ":apex.test-file_contexts",
+ prebuilts: ["sample_prebuilt_file"],
+ key: "com.android.apex.compressed.key",
+ installable: false,
+ test_only_force_compression: true,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_v1",
+ manifest: "manifest_rebootless.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+ // TODO(ioffe): we should have a separate field to hashtree presence.
+ min_sdk_version: "29", // test requires hashtree to be present.
+}
+
+apex {
+ name: "test.rebootless_apex_v2",
+ manifest: "manifest_rebootless_v2.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+ // TODO(ioffe): we should have a separate field to hashtree presence.
+ min_sdk_version: "29", // test requires hashtree to be present.
+}
+
+apex {
+ name: "test.rebootless_apex_v2_no_hashtree",
+ manifest: "manifest_rebootless_v2.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+ generate_hashtree: false,
+}
+
+apex {
+ name: "test.rebootless_apex_provides_sharedlibs",
+ manifest: "manifest_rebootless_provides_sharedlibs.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_provides_native_libs",
+ manifest: "manifest_rebootless_provides_native_libs.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_requires_shared_apex_libs",
+ manifest: "manifest_rebootless_requires_shared_apex_libs.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_jni_libs",
+ manifest: "manifest_rebootless_jni_libs.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_add_native_lib",
+ manifest: "manifest_rebootless_add_native_lib.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_remove_native_lib",
+ manifest: "manifest_rebootless_remove_native_lib.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "test.rebootless_apex_app_in_apex",
+ manifest: "manifest_rebootless_v2.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+ apps: ["AppInRebootlessApex"],
+ // TODO(ioffe): we should have a separate field to hashtree presence.
+ min_sdk_version: "29", // test requires hashtree to be present.
+}
+
+apex {
+ name: "test.rebootless_apex_priv_app_in_apex",
+ manifest: "manifest_rebootless_v2.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test_package.key",
+ installable: false,
+ updatable: false,
+ apps: ["PrivAppInRebootlessApex"],
+ // TODO(ioffe): we should have a separate field to hashtree presence.
+ min_sdk_version: "29", // test requires hashtree to be present.
+}
+
+android_app {
+ name: "AppInRebootlessApex",
+ sdk_version: "29",
+ manifest: "AppInRebootlessApex_AndroidManifest.xml",
+ apex_available: [
+ "test.rebootless_apex_app_in_apex",
+ ],
+}
+
+android_app {
+ name: "PrivAppInRebootlessApex",
+ sdk_version: "29",
+ privileged: true,
+ manifest: "AppInRebootlessApex_AndroidManifest.xml",
+ apex_available: [
+ "test.rebootless_apex_priv_app_in_apex",
+ ],
+}
diff --git a/tests/src/com/android/tests/apex/CellbroadcastHostTest.java b/apexd/apexd_testdata/AppInRebootlessApex_AndroidManifest.xml
index 97230422..242d1be4 100644
--- a/tests/src/com/android/tests/apex/CellbroadcastHostTest.java
+++ b/apexd/apexd_testdata/AppInRebootlessApex_AndroidManifest.xml
@@ -1,5 +1,6 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 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.
@@ -12,17 +13,8 @@
* 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 com.android.tests.apex;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CellbroadcastHostTest extends ApexE2EBaseHostTest {
-} \ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.rebootless_tests.app">
+</manifest>
diff --git a/apexd/apexd_testdata/com.android.apex.compressed.avbpubkey b/apexd/apexd_testdata/com.android.apex.compressed.avbpubkey
new file mode 100644
index 00000000..92767eac
--- /dev/null
+++ b/apexd/apexd_testdata/com.android.apex.compressed.avbpubkey
Binary files differ
diff --git a/apexd/apexd_testdata/com.android.apex.compressed.pem b/apexd/apexd_testdata/com.android.apex.compressed.pem
new file mode 100644
index 00000000..bc2c0a72
--- /dev/null
+++ b/apexd/apexd_testdata/com.android.apex.compressed.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAqvAUWzu1+JmgUDTMx22v3Wcdv05CwE3y7roVbIhIFtfQjvjY
+NNStwnl5r+2er0BmlNDWAPC20x46uF2kfSecIjoWMOAjMtkgWHYX6nZqE2BXo6Mj
+KnxC+D+zdj6FO+QCbAZ1JKlfy/zGyq37VTIQu1OF9a3FyusOP2qapWr4jYiZ2WLr
+gHhk/qvurN2GhpeTX7TMSEOVfg2Q7kKTxxXE5blw5lRaF9FzX4FMfU296qyXJ1qE
+8pLjcVwVjTjrAO69AQ3S+lZZXA5WJwbHqU5WaRL5k+XjXASyoxTRvOHOsQ3mxQ+B
+Ad227pk/x5lZLnnuc7d3Qe5cB2yJNDaENEptCA5VKaBG1QbRBL8ykD45w2OwIP7p
+2H58b/2tEgtjA4bpWEFqwVa8LXMBg2x56Sbo8YWmQL4FE14XAYyI3eAs171JZV6e
+nf9/ll+45oIXI2wY0jttfnYyGErLlbCIWYYByAnV5mwZ9eBrXl/xu65+MUKVnZOA
+2y1KJch1eXUjLqMRAW6DBwOmAjsJhuiF8u2gZlF/zgnzPzWbbvfMWi/aztdCV2Yb
+rRhKZT6i2A0a9o3lghwGpHJjXwJ23ELWmfWI6mxGGJyhrVRLvUlRp2a8QRmw6mcc
+sWVWdichcjvx20fIPHanzC/TtgKe/smQjZ1GQClPbqRvbhoxleklLDk+NWkCAwEA
+AQKCAgAJLwbY8/dN2OsdBAkwebsmGQEnIwxBCq8Pll3KS/QbjhK9a68p/3cqaJ5a
+DlklLz+TpTiqKkSYSRp8h0NsGfGwgRBqJdCeTb4IIqgcR6phSh7LQtuDz9NFRR7e
+LnO4CQL8TMiEZLkp23XOs9Q5+oudlNowndpvkXtdetu+IWYjLICfhkoCx/UdHZ2g
+GfK9Tm1zieIjy8W/VPlBY8BOxOOkN/dR2JxShUU+j1LNtJyMfCNO0PUtlyizEXBy
++ujdTvZGlTiNjJAWewqz5BBcD0JjOMrB4Gr7qaDzVA4EqNkS+B063x9eO0w1u3QK
+xDlfXtupyeSVXToCiBlC/d7SdGExJBqpebCKWLvdIXxDbgU5tNeKXcbHNp9zOduV
+RTulPxseqKgvO/wDc27F1AChG68GYyWcEm7kWQd6BVmOOwxRurssrTgchFk/HrY+
+Vzlbqs+CTyrYoyZuMF8W7E0U7pQtLx4VMv1AEfHyzLmbPgsdwmlPZ4On+yks4rGt
+jHGtUergxsAiiYBHZqXbmuK7UTg1wi5u8p4QgTUxC5jkKMetkgqGajWf5BDpf02E
++0P+58VGHkd3ACPFG4PcS8gnmjpx/EXicWq855w+nshr8Ff4Gs9LbUv0pg8s9mZA
+2JGfGyjxp8h9/06OrKq5pM5HVuOCZ0AgEHEf++AeBSq24mrgkQKCAQEA28JaiH83
+7NCiK8XJebgPgVGl/wwugsA+Ea3Kv/prQGmFW/IVJNAbMAREkzDi4IaVGySPHDpo
+OaDFFPOiC9zyveK8LdJyqaInEdN6nerGuM4ubA27DKP52MHfMBr6FDYgru5hcskw
+4gHGZAWhD242rzhFGgfTTDbRXRhqn93f/6+yHbjyjybs5vPM2gU/qPlwdjUPxXFo
+g3Nrx3TyrMtfUL/dhHniF+SiiVfu8hNqi4EuQ295jGtwMZl8U0/UVuQ6UQUMe6cT
+VfKuunxyCq2AOvd4XLODZ9BcZDtTDLIsXaPyVzJUPGxugqlid/i7SuV+c3unT0dD
+Ma2+7n6gdStuPQKCAQEAxyCeECT3Yhx1rB3SvloGVCMQY3ajs4/a3qIPLa18X8ZL
+K1qUevlq7mpJVbnI0KmbuyNzPyGkPbQtuGU0g8kvchD7nH3Pi9SabB2ereIi9YO0
+s5a+ZVVPdtD69oCfhhKh9BPrjsSDnci2ZjCYkxzKHVtSvlJQmWAaxcUCwUKPvXen
+LBQ4QNC9bnVj6v9XJwWy3YgrwpRetdBPU/jnSAjLOpjWKlXz4pokHpIR1kAsRY9O
+In2gkP27PwSBMEZ4qxKa0uujQ5L4odqtDm4O15fF1IQ0/y+sNTrZArg54X3MYG6J
+UyR9ReKXUL5nWYYSEsVJ6D9J8fiow21hiYnSt82inQKCAQEAlrW+QtgEcYtPfHeD
+Sc96CrUFA1nGV/MhXhxy7J//h8gWJk1qRLnXu2Fh6hPftB14CopS/wfrTII+RrUj
+D8GyU5k4drBZ5I4I/0eqUrydFkaIPaBZBD08bnPe7W3CzbOlTHK9L+xcctLGzPez
+UhLCu/36HfT56s25XYAON58BLKfAnnOlHZmTZHwUo/xvgSG4B8kyDLVO+L9iTgKd
+HvXGY2mBsIWqEbrB4TEF9MxuCEhKgwLjN/LCmbFqOvSHaiPQ/plYy0B0mT/6pngL
++ditFUN8Lw8JclvJ0Q+CUDWtCXcTDsu8S0gNrdweZKqXP7ENvIMz5cG4ikxeoc8D
+mfdz/QKCAQBZkzGnX4mtJ9JDV7Maj9Ky/Ib9xzvCpZ62cb5UNOtzBfeAjCGo5BQX
+JdbRal6Mhw+X8k2Ag7inTSsX/ObPtavTKxKUhf/cDgpdQkHERKqnONULyG7jlKnH
+cCDEzH12SWFzM5bORVZTnxt2ArxPyS6eYBtrpAm/xPymJIaluzR/7ZhU+s+HUJ82
+VjZZWv2wfx7ECuJsiGPGc+uLgbdArzwEowYMS1gHgoFnAxxk/b8sl1d1qn+VWZ5m
+rbcTqU/U3Oyqnvd1iWKxJHaevCGPGCYVAFf6x043L765O3hGGFncszyxGwQDcPfS
+iaRiIC53JSqm//lakRoRt12eClKw/h79AoIBAQC0uu/kwB//HdQbzJkejoBZDWpu
+2eTZKnLs/5hsU/CPfJnL2cJfStJIFh6WgutNoW++Xi79OfagEQx6hAyfl5HexV8V
+2PNDMgcgxauAObhkZtg8x5ZDAyrOiXVHR+T3cm8qQbVtrHdiX97hrg9tTj923baK
+TpI9qQw+2Jg627BR2t/rYIrZwdL2oqbznihIIPg6FkCT/53AfX8W1U9GWPnUyIkn
+KvRdtY6osOS/C/Dju/Eg6710umACRPLa5928A5q+1jY6TntRIG8Q/agL4IXjR/0E
+O0OkoRArOFt2NGN88Vqy7ses0VfP2UIpjjRP3/8xwUFmUo8vdoR5oy4kC/Sz
+-----END RSA PRIVATE KEY-----
diff --git a/apexd/apexd_testdata/manifest_compressed.json b/apexd/apexd_testdata/manifest_compressed.json
new file mode 100644
index 00000000..8c07cc82
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_compressed.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.compressed",
+ "version": 1
+}
diff --git a/apexd/apexd_testdata/manifest_compressed_sharedlibs.json b/apexd/apexd_testdata/manifest_compressed_sharedlibs.json
new file mode 100644
index 00000000..30d2835f
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_compressed_sharedlibs.json
@@ -0,0 +1,5 @@
+{
+ "name": "com.android.apex.compressed",
+ "version": 1,
+ "provideSharedApexLibs": true
+}
diff --git a/apexd/apexd_testdata/manifest_compressed_v2.json b/apexd/apexd_testdata/manifest_compressed_v2.json
new file mode 100644
index 00000000..c5adb514
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_compressed_v2.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.compressed",
+ "version": 2
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless.json b/apexd/apexd_testdata/manifest_rebootless.json
new file mode 100644
index 00000000..a37fa005
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless.json
@@ -0,0 +1,9 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar"
+ ]
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_add_native_lib.json b/apexd/apexd_testdata/manifest_rebootless_add_native_lib.json
new file mode 100644
index 00000000..eab172c9
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_add_native_lib.json
@@ -0,0 +1,10 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar",
+ "libnew"
+ ]
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_jni_libs.json b/apexd/apexd_testdata/manifest_rebootless_jni_libs.json
new file mode 100644
index 00000000..bf88d2eb
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_jni_libs.json
@@ -0,0 +1,12 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar"
+ ],
+ "jniLibs": [
+ "libjniwow"
+ ]
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_provides_native_libs.json b/apexd/apexd_testdata/manifest_rebootless_provides_native_libs.json
new file mode 100644
index 00000000..ef829b07
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_provides_native_libs.json
@@ -0,0 +1,12 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar"
+ ],
+ "provideNativeLibs": [
+ "libbaz"
+ ]
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_provides_sharedlibs.json b/apexd/apexd_testdata/manifest_rebootless_provides_sharedlibs.json
new file mode 100644
index 00000000..d91c21e4
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_provides_sharedlibs.json
@@ -0,0 +1,10 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar"
+ ],
+ "provideSharedApexLibs": true
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_remove_native_lib.json b/apexd/apexd_testdata/manifest_rebootless_remove_native_lib.json
new file mode 100644
index 00000000..0926e70f
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_remove_native_lib.json
@@ -0,0 +1,8 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libbar"
+ ]
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_requires_shared_apex_libs.json b/apexd/apexd_testdata/manifest_rebootless_requires_shared_apex_libs.json
new file mode 100644
index 00000000..faaadfda
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_requires_shared_apex_libs.json
@@ -0,0 +1,12 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 1,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar"
+ ],
+ "requireSharedApexLibs": [
+ "libabc"
+ ]
+}
diff --git a/apexd/apexd_testdata/manifest_rebootless_v2.json b/apexd/apexd_testdata/manifest_rebootless_v2.json
new file mode 100644
index 00000000..dfcb21fe
--- /dev/null
+++ b/apexd/apexd_testdata/manifest_rebootless_v2.json
@@ -0,0 +1,9 @@
+{
+ "name": "test.apex.rebootless",
+ "version": 2,
+ "supportsRebootlessUpdate": true,
+ "requireNativeLibs": [
+ "libfoo",
+ "libbar"
+ ]
+}
diff --git a/apexd/apexd_testdata/sharedlibs.apex b/apexd/apexd_testdata/sharedlibs.apex
new file mode 100644
index 00000000..cde11d51
--- /dev/null
+++ b/apexd/apexd_testdata/sharedlibs.apex
Binary files differ
diff --git a/apexd/apexd_utils.h b/apexd/apexd_utils.h
index 2759f9aa..23194ebf 100644
--- a/apexd/apexd_utils.h
+++ b/apexd/apexd_utils.h
@@ -18,9 +18,11 @@
#define ANDROID_APEXD_APEXD_UTILS_H_
#include <chrono>
+#include <cstdint>
#include <filesystem>
#include <string>
#include <thread>
+#include <type_traits>
#include <vector>
#include <dirent.h>
@@ -30,17 +32,14 @@
#include <android-base/chrono_utils.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/result.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <cutils/android_reboot.h>
+#include <selinux/android.h>
#include "apex_constants.h"
-#include "string_log.h"
-
-using android::base::ErrnoError;
-using android::base::Error;
-using android::base::Result;
namespace android {
namespace apex {
@@ -61,21 +60,18 @@ inline int WaitChild(pid_t pid) {
}
}
-// TODO(ioffe): change to Result<void>?
-inline int ForkAndRun(const std::vector<std::string>& args,
- std::string* error_msg) {
+inline android::base::Result<void> ForkAndRun(
+ const std::vector<std::string>& args) {
LOG(DEBUG) << "Forking : " << android::base::Join(args, " ");
std::vector<const char*> argv;
argv.resize(args.size() + 1, nullptr);
std::transform(args.begin(), args.end(), argv.begin(),
[](const std::string& in) { return in.c_str(); });
- // 3) Fork.
pid_t pid = fork();
if (pid == -1) {
// Fork failed.
- *error_msg = PStringLog() << "Unable to fork";
- return -1;
+ return android::base::ErrnoError() << "Unable to fork";
}
if (pid == 0) {
@@ -86,13 +82,13 @@ inline int ForkAndRun(const std::vector<std::string>& args,
int rc = WaitChild(pid);
if (rc != 0) {
- *error_msg = StringLog() << "Failed run: status=" << rc;
+ return android::base::Error() << "Failed run: status=" << rc;
}
- return rc;
+ return {};
}
template <typename Fn>
-Result<void> WalkDir(const std::string& path, Fn fn) {
+android::base::Result<void> WalkDir(const std::string& path, Fn fn) {
namespace fs = std::filesystem;
std::error_code ec;
auto it = fs::directory_iterator(path, ec);
@@ -102,14 +98,15 @@ Result<void> WalkDir(const std::string& path, Fn fn) {
it.increment(ec);
}
if (ec) {
- return Error() << "Can't open " << path
- << " for reading : " << ec.message();
+ return android::base::Error()
+ << "Can't open " << path << " for reading : " << ec.message();
}
return {};
}
template <typename FilterFn>
-Result<std::vector<std::string>> ReadDir(const std::string& path, FilterFn fn) {
+android::base::Result<std::vector<std::string>> ReadDir(const std::string& path,
+ FilterFn fn) {
namespace fs = std::filesystem;
std::vector<std::string> ret;
@@ -129,72 +126,70 @@ inline bool IsEmptyDirectory(const std::string& path) {
return res.ok() && res->empty();
}
-inline Result<void> createDirIfNeeded(const std::string& path, mode_t mode) {
+inline android::base::Result<void> CreateDirIfNeeded(const std::string& path,
+ mode_t mode) {
struct stat stat_data;
if (stat(path.c_str(), &stat_data) != 0) {
if (errno == ENOENT) {
if (mkdir(path.c_str(), mode) != 0) {
- return ErrnoError() << "Could not mkdir " << path;
+ return android::base::ErrnoError() << "Could not mkdir " << path;
}
} else {
- return ErrnoError() << "Could not stat " << path;
+ return android::base::ErrnoError() << "Could not stat " << path;
}
} else {
if (!S_ISDIR(stat_data.st_mode)) {
- return Error() << path << " exists and is not a directory.";
+ return android::base::Error()
+ << path << " exists and is not a directory.";
}
}
// Need to manually call chmod because mkdir will create a folder with
// permissions mode & ~umask.
if (chmod(path.c_str(), mode) != 0) {
- return ErrnoError() << "Could not chmod " << path;
+ return android::base::ErrnoError() << "Could not chmod " << path;
}
return {};
}
-inline Result<void> DeleteDirContent(const std::string& path) {
+inline android::base::Result<void> DeleteDirContent(const std::string& path) {
auto files = ReadDir(path, [](auto _) { return true; });
if (!files.ok()) {
- return Error() << "Failed to delete " << path << " : " << files.error();
+ return android::base::Error()
+ << "Failed to delete " << path << " : " << files.error();
}
for (const std::string& file : *files) {
- if (unlink(file.c_str()) != 0) {
- return ErrnoError() << "Failed to delete " << file;
+ std::error_code ec;
+ std::filesystem::remove_all(file, ec);
+ if (ec) {
+ return android::base::Error()
+ << "Failed to delete path " << file << " : " << ec.message();
}
}
return {};
}
-inline Result<void> DeleteDir(const std::string& path) {
+inline android::base::Result<void> DeleteDir(const std::string& path) {
namespace fs = std::filesystem;
std::error_code ec;
fs::remove_all(path, ec);
if (ec) {
- return Error() << "Failed to delete path " << path << " : " << ec.message();
+ return android::base::Error()
+ << "Failed to delete path " << path << " : " << ec.message();
}
return {};
}
-inline Result<ino_t> get_path_inode(const std::string& path) {
- struct stat buf;
- memset(&buf, 0, sizeof(buf));
- if (stat(path.c_str(), &buf) != 0) {
- return ErrnoError() << "Failed to stat " << path;
- } else {
- return buf.st_ino;
- }
-}
-
-inline Result<bool> PathExists(const std::string& path) {
+inline android::base::Result<bool> PathExists(const std::string& path) {
namespace fs = std::filesystem;
std::error_code ec;
if (!fs::exists(fs::path(path), ec)) {
if (ec) {
- return Error() << "Failed to access " << path << " : " << ec.message();
+ return android::base::Error()
+ << "Failed to access " << path << " : " << ec.message();
} else {
return false;
}
@@ -209,8 +204,8 @@ inline void Reboot() {
}
}
-inline Result<void> WaitForFile(const std::string& path,
- std::chrono::nanoseconds timeout) {
+inline android::base::Result<void> WaitForFile(
+ const std::string& path, std::chrono::nanoseconds timeout) {
android::base::Timer t;
bool has_slept = false;
while (t.duration() < timeout) {
@@ -224,10 +219,12 @@ inline Result<void> WaitForFile(const std::string& path,
std::this_thread::sleep_for(5ms);
has_slept = true;
}
- return ErrnoError() << "wait for '" << path << "' timed out and took " << t;
+ return android::base::ErrnoError()
+ << "wait for '" << path << "' timed out and took " << t;
}
-inline Result<std::vector<std::string>> GetSubdirs(const std::string& path) {
+inline android::base::Result<std::vector<std::string>> GetSubdirs(
+ const std::string& path) {
namespace fs = std::filesystem;
auto filter_fn = [](const std::filesystem::directory_entry& entry) {
std::error_code ec;
@@ -241,10 +238,140 @@ inline Result<std::vector<std::string>> GetSubdirs(const std::string& path) {
return ReadDir(path, filter_fn);
}
-inline Result<std::vector<std::string>> GetDeUserDirs() {
+inline android::base::Result<std::vector<std::string>> GetDeUserDirs() {
return GetSubdirs(kDeNDataDir);
}
+inline android::base::Result<std::vector<std::string>> FindFilesBySuffix(
+ const std::string& path, const std::vector<std::string>& suffix_list) {
+ auto filter_fn =
+ [&suffix_list](const std::filesystem::directory_entry& entry) {
+ for (const std::string& suffix : suffix_list) {
+ std::error_code ec;
+ auto name = entry.path().filename().string();
+ if (entry.is_regular_file(ec) &&
+ android::base::EndsWith(name, suffix)) {
+ return true; // suffix matches, take.
+ }
+ }
+ return false;
+ };
+ return ReadDir(path, filter_fn);
+}
+
+inline android::base::Result<std::vector<std::string>> FindApexes(
+ const std::vector<std::string>& paths) {
+ std::vector<std::string> result;
+ for (const auto& path : paths) {
+ auto exist = PathExists(path);
+ if (!exist.ok()) {
+ return exist.error();
+ }
+ if (!*exist) continue;
+
+ const auto& apexes = FindFilesBySuffix(path, {kApexPackageSuffix});
+ if (!apexes.ok()) {
+ return apexes;
+ }
+
+ result.insert(result.end(), apexes->begin(), apexes->end());
+ }
+ return result;
+}
+
+// Returns first path between |first_dir| and |second_dir| that correspond to a
+// existing directory. Returns error if neither |first_dir| nor |second_dir|
+// correspond to an existing directory.
+inline android::base::Result<std::string> FindFirstExistingDirectory(
+ const std::string& first_dir, const std::string& second_dir) {
+ struct stat stat_buf;
+ if (stat(first_dir.c_str(), &stat_buf) != 0) {
+ PLOG(WARNING) << "Failed to stat " << first_dir;
+ if (stat(second_dir.c_str(), &stat_buf) != 0) {
+ return android::base::ErrnoError() << "Failed to stat " << second_dir;
+ }
+ if (!S_ISDIR(stat_buf.st_mode)) {
+ return android::base::Error() << second_dir << " is not a directory";
+ }
+ return second_dir;
+ }
+
+ if (S_ISDIR(stat_buf.st_mode)) {
+ return first_dir;
+ }
+ LOG(WARNING) << first_dir << " is not a directory";
+
+ if (stat(second_dir.c_str(), &stat_buf) != 0) {
+ return android::base::ErrnoError() << "Failed to stat " << second_dir;
+ }
+ if (!S_ISDIR(stat_buf.st_mode)) {
+ return android::base::Error() << second_dir << " is not a directory";
+ }
+ return second_dir;
+}
+
+// Copies all entries under |from| directory to |to| directory, and then them.
+// Leaving |from| empty.
+inline android::base::Result<void> MoveDir(const std::string& from,
+ const std::string& to) {
+ struct stat stat_buf;
+ if (stat(to.c_str(), &stat_buf) != 0) {
+ return android::base::ErrnoError() << "Failed to stat " << to;
+ }
+ if (!S_ISDIR(stat_buf.st_mode)) {
+ return android::base::Error() << to << " is not a directory";
+ }
+
+ namespace fs = std::filesystem;
+ std::error_code ec;
+ auto it = fs::directory_iterator(from, ec);
+ if (ec) {
+ return android::base::Error()
+ << "Can't read " << from << " : " << ec.message();
+ }
+
+ for (const auto& end = fs::directory_iterator(); it != end;) {
+ auto from_path = it->path();
+ it.increment(ec);
+ if (ec) {
+ return android::base::Error()
+ << "Can't read " << from << " : " << ec.message();
+ }
+ auto to_path = to / from_path.filename();
+ fs::copy(from_path, to_path, fs::copy_options::recursive, ec);
+ if (ec) {
+ return android::base::Error() << "Failed to copy " << from_path << " to "
+ << to_path << " : " << ec.message();
+ }
+ fs::remove_all(from_path, ec);
+ if (ec) {
+ return android::base::Error()
+ << "Failed to delete " << from_path << " : " << ec.message();
+ }
+ }
+ return {};
+}
+
+inline android::base::Result<uintmax_t> GetFileSize(
+ const std::string& file_path) {
+ std::error_code ec;
+ auto value = std::filesystem::file_size(file_path, ec);
+ if (ec) {
+ return android::base::Error() << "Failed to get file size of " << file_path
+ << " : " << ec.message();
+ }
+
+ return value;
+}
+
+inline android::base::Result<void> RestoreconPath(const std::string& path) {
+ unsigned int seflags = SELINUX_ANDROID_RESTORECON_RECURSE;
+ if (selinux_android_restorecon(path.c_str(), seflags) < 0) {
+ return android::base::ErrnoError() << "Failed to restorecon " << path;
+ }
+ return {};
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_utils_test.cpp b/apexd/apexd_utils_test.cpp
new file mode 100644
index 00000000..cbbc45e1
--- /dev/null
+++ b/apexd/apexd_utils_test.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2020 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 <filesystem>
+#include <fstream>
+#include <new>
+#include <string>
+
+#include <errno.h>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+
+#include "apexd.h"
+#include "apexd_test_utils.h"
+#include "apexd_utils.h"
+
+namespace android {
+namespace apex {
+namespace {
+
+namespace fs = std::filesystem;
+
+using android::apex::testing::IsOk;
+using android::base::Basename;
+using android::base::Join;
+using android::base::StringPrintf;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+
+// TODO(b/170327382): add unit tests for apexd_utils.h
+
+TEST(ApexdUtilTest, DeleteDirContent) {
+ TemporaryDir root_dir;
+ TemporaryFile child_file_1(root_dir.path);
+ TemporaryFile child_file_2(root_dir.path);
+ std::string child_dir = StringPrintf("%s/child-dir", root_dir.path);
+ CreateDirIfNeeded(child_dir, 0755);
+ TemporaryFile nested_file(child_dir);
+
+ auto content = ReadDir(root_dir.path, [](auto _) { return true; });
+ IsOk(content);
+ ASSERT_EQ(content->size(), 3u);
+
+ auto del_result = DeleteDirContent(root_dir.path);
+ IsOk(del_result);
+ content = ReadDir(root_dir.path, [](auto _) { return true; });
+ IsOk(content);
+ ASSERT_EQ(content->size(), 0u);
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryBothExist) {
+ TemporaryDir first_dir;
+ TemporaryDir second_dir;
+ auto result = FindFirstExistingDirectory(first_dir.path, second_dir.path);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_EQ(*result, first_dir.path);
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryOnlyFirstExist) {
+ TemporaryDir first_dir;
+ auto second_dir = "/data/local/tmp/does/not/exist";
+ auto result = FindFirstExistingDirectory(first_dir.path, second_dir);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_EQ(*result, first_dir.path);
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryOnlySecondExist) {
+ auto first_dir = "/data/local/tmp/does/not/exist";
+ TemporaryDir second_dir;
+ auto result = FindFirstExistingDirectory(first_dir, second_dir.path);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_EQ(*result, second_dir.path);
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryNoneExist) {
+ auto first_dir = "/data/local/tmp/does/not/exist";
+ auto second_dir = "/data/local/tmp/also/does/not/exist";
+ auto result = FindFirstExistingDirectory(first_dir, second_dir);
+ ASSERT_FALSE(IsOk(result));
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryFirstFileSecondDir) {
+ TemporaryFile first_file;
+ TemporaryDir second_dir;
+ auto result = FindFirstExistingDirectory(first_file.path, second_dir.path);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_EQ(*result, second_dir.path);
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryFirstDirSecondFile) {
+ TemporaryDir first_dir;
+ TemporaryFile second_file;
+ auto result = FindFirstExistingDirectory(first_dir.path, second_file.path);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_EQ(*result, first_dir.path);
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryBothFiles) {
+ TemporaryFile first_file;
+ TemporaryFile second_file;
+ auto result = FindFirstExistingDirectory(first_file.path, second_file.path);
+ ASSERT_FALSE(IsOk(result));
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryFirstFileSecondDoesNotExist) {
+ TemporaryFile first_file;
+ auto second_dir = "/data/local/tmp/does/not/exist";
+ auto result = FindFirstExistingDirectory(first_file.path, second_dir);
+ ASSERT_FALSE(IsOk(result));
+}
+
+TEST(ApexdUtilTest, FindFirstExistingDirectoryFirsDoesNotExistSecondFile) {
+ auto first_dir = "/data/local/tmp/does/not/exist";
+ TemporaryFile second_file;
+ auto result = FindFirstExistingDirectory(first_dir, second_file.path);
+ ASSERT_FALSE(IsOk(result));
+}
+
+TEST(ApexdUtilTest, MoveDir) {
+ TemporaryDir from;
+ TemporaryDir to;
+
+ TemporaryFile from_1(from.path);
+ auto from_subdir = StringPrintf("%s/subdir", from.path);
+ if (mkdir(from_subdir.c_str(), 07000) != 0) {
+ FAIL() << "Failed to mkdir " << from_subdir << " : " << strerror(errno);
+ }
+ TemporaryFile from_2(from_subdir);
+
+ auto result = MoveDir(from.path, to.path);
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_TRUE(fs::is_empty(from.path));
+
+ std::vector<std::string> content;
+ for (const auto& it : fs::recursive_directory_iterator(to.path)) {
+ content.push_back(it.path());
+ }
+
+ static const std::vector<std::string> expected = {
+ StringPrintf("%s/%s", to.path, Basename(from_1.path).c_str()),
+ StringPrintf("%s/subdir", to.path),
+ StringPrintf("%s/subdir/%s", to.path, Basename(from_2.path).c_str()),
+ };
+ ASSERT_THAT(content, UnorderedElementsAreArray(expected));
+}
+
+TEST(ApexdUtilTest, MoveDirFromIsNotDirectory) {
+ TemporaryFile from;
+ TemporaryDir to;
+ ASSERT_FALSE(IsOk(MoveDir(from.path, to.path)));
+}
+
+TEST(ApexdUtilTest, MoveDirToIsNotDirectory) {
+ TemporaryDir from;
+ TemporaryFile to;
+ TemporaryFile from_1(from.path);
+ ASSERT_FALSE(IsOk(MoveDir(from.path, to.path)));
+}
+
+TEST(ApexdUtilTest, MoveDirFromDoesNotExist) {
+ TemporaryDir to;
+ ASSERT_FALSE(IsOk(MoveDir("/data/local/tmp/does/not/exist", to.path)));
+}
+
+TEST(ApexdUtilTest, MoveDirToDoesNotExist) {
+ namespace fs = std::filesystem;
+
+ TemporaryDir from;
+ TemporaryFile from_1(from.path);
+ auto from_subdir = StringPrintf("%s/subdir", from.path);
+ if (mkdir(from_subdir.c_str(), 07000) != 0) {
+ FAIL() << "Failed to mkdir " << from_subdir << " : " << strerror(errno);
+ }
+ TemporaryFile from_2(from_subdir);
+
+ ASSERT_FALSE(IsOk(MoveDir(from.path, "/data/local/tmp/does/not/exist")));
+
+ // Check that |from| directory is not empty.
+ std::vector<std::string> content;
+ for (const auto& it : fs::recursive_directory_iterator(from.path)) {
+ content.push_back(it.path());
+ }
+
+ ASSERT_THAT(content,
+ UnorderedElementsAre(from_1.path, from_subdir, from_2.path));
+}
+
+TEST(ApexdUtilTest, FindFilesBySuffix) {
+ TemporaryDir td;
+
+ // create files with different suffix
+ const std::string first_filename = StringPrintf("%s/first.a", td.path);
+ const std::string second_filename = StringPrintf("%s/second.b", td.path);
+ const std::string third_filename = StringPrintf("%s/third.c", td.path);
+ const std::string fourth_filename = StringPrintf("%s/fourth.c", td.path);
+
+ std::ofstream first_file(first_filename);
+ std::ofstream second_file(second_filename);
+ std::ofstream third_file(third_filename);
+ std::ofstream fourth_file(fourth_filename);
+
+ auto result = FindFilesBySuffix(td.path, {".b", ".c"});
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_THAT(*result, UnorderedElementsAre(second_filename, third_filename,
+ fourth_filename));
+}
+
+TEST(ApexdTestUtilsTest, MountNamespaceRestorer) {
+ auto original_namespace = GetCurrentMountNamespace();
+ ASSERT_RESULT_OK(original_namespace);
+ {
+ MountNamespaceRestorer restorer;
+ // Switch to new mount namespace.
+ ASSERT_NE(-1, unshare(CLONE_NEWNS));
+ auto current_namespace = GetCurrentMountNamespace();
+ ASSERT_RESULT_OK(current_namespace);
+ ASSERT_NE(original_namespace, current_namespace);
+ }
+ // Check that we switched back to the original namespace upon exiting the
+ // scope.
+ auto current_namespace = GetCurrentMountNamespace();
+ ASSERT_RESULT_OK(current_namespace);
+ ASSERT_EQ(*original_namespace, *current_namespace);
+}
+
+TEST(ApexdTestUtilsTest, SetUpApexTestEnvironment) {
+ auto original_apex_mounts = GetApexMounts();
+ ASSERT_GT(original_apex_mounts.size(), 0u);
+ auto original_dir_content = ReadDir("/apex", [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(original_dir_content));
+ {
+ MountNamespaceRestorer restorer;
+ ASSERT_TRUE(IsOk(SetUpApexTestEnvironment()));
+ // Check /apex is apex_mnt_dir.
+ char* context;
+ ASSERT_GT(getfilecon("/apex", &context), 0);
+ EXPECT_EQ(std::string(context), "u:object_r:apex_mnt_dir:s0");
+ freecon(context);
+ // Check no apexes are mounted in our test environment.
+ auto new_apex_mounts = GetApexMounts();
+ ASSERT_EQ(new_apex_mounts.size(), 0u);
+ // Check that /apex is empty.
+ auto dir_content = ReadDir("/apex", [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(dir_content));
+ ASSERT_EQ(dir_content->size(), 0u)
+ << "Found following entries: " << Join(*dir_content, ',');
+ // Check that we can still access /data.
+ std::string test_dir = android::base::GetExecutableDirectory();
+ ASSERT_TRUE(android::base::StartsWith(test_dir, "/data"));
+ TemporaryFile tf(test_dir);
+ // Check that we can write.
+ ASSERT_TRUE(android::base::WriteStringToFile("secret", tf.path));
+ // And check that we can still read it
+ std::string content;
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &content));
+ ASSERT_EQ(content, "secret");
+ }
+ auto apex_mounts = GetApexMounts();
+ ASSERT_THAT(apex_mounts, UnorderedElementsAreArray(original_apex_mounts));
+ auto apex_dir_content = ReadDir("/apex", [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(apex_dir_content));
+ ASSERT_EQ(apex_dir_content->size(), original_dir_content->size());
+}
+
+} // namespace
+} // namespace apex
+} // namespace android
diff --git a/apexd/apexd_verity.cpp b/apexd/apexd_verity.cpp
index a4be3363..0008f6d2 100644
--- a/apexd/apexd_verity.cpp
+++ b/apexd/apexd_verity.cpp
@@ -29,6 +29,7 @@
#include "apex_file.h"
#include "apexd_utils.h"
+using android::base::Dirname;
using android::base::ErrnoError;
using android::base::Error;
using android::base::ReadFully;
@@ -59,7 +60,8 @@ std::vector<uint8_t> HexToBin(const std::string& hex) {
Result<void> GenerateHashTree(const ApexFile& apex,
const ApexVerityData& verity_data,
const std::string& hashtree_file) {
- unique_fd fd(TEMP_FAILURE_RETRY(open(apex.GetPath().c_str(), O_RDONLY)));
+ unique_fd fd(
+ TEMP_FAILURE_RETRY(open(apex.GetPath().c_str(), O_RDONLY | O_CLOEXEC)));
if (fd.get() == -1) {
return ErrnoError() << "Failed to open " << apex.GetPath();
}
@@ -78,7 +80,10 @@ Result<void> GenerateHashTree(const ApexFile& apex,
return Error() << "Invalid image size " << image_size;
}
- if (lseek(fd, apex.GetImageOffset(), SEEK_SET) == -1) {
+ if (!apex.GetImageOffset()) {
+ return Error() << "Cannot generate HashTree without image offset";
+ }
+ if (lseek(fd, apex.GetImageOffset().value(), SEEK_SET) == -1) {
return ErrnoError() << "Failed to seek";
}
@@ -105,8 +110,8 @@ Result<void> GenerateHashTree(const ApexFile& apex,
return Error() << "Failed to build hashtree: root digest mismatch";
}
- unique_fd out_fd(TEMP_FAILURE_RETRY(
- open(hashtree_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600)));
+ unique_fd out_fd(TEMP_FAILURE_RETRY(open(
+ hashtree_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)));
if (!builder->WriteHashTreeToFd(out_fd, 0)) {
return Error() << "Failed to write hashtree to " << hashtree_file;
}
@@ -115,7 +120,8 @@ Result<void> GenerateHashTree(const ApexFile& apex,
Result<std::string> CalculateRootDigest(const std::string& hashtree_file,
const ApexVerityData& verity_data) {
- unique_fd fd(TEMP_FAILURE_RETRY(open(hashtree_file.c_str(), O_RDONLY)));
+ unique_fd fd(
+ TEMP_FAILURE_RETRY(open(hashtree_file.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd.get() == -1) {
return ErrnoError() << "Failed to open " << hashtree_file;
}
@@ -149,7 +155,11 @@ Result<std::string> CalculateRootDigest(const std::string& hashtree_file,
Result<PrepareHashTreeResult> PrepareHashTree(
const ApexFile& apex, const ApexVerityData& verity_data,
const std::string& hashtree_file) {
- if (auto st = createDirIfNeeded(kApexHashTreeDir, 0700); !st.ok()) {
+ if (apex.IsCompressed()) {
+ return Error() << "Cannot prepare HashTree of compressed APEX";
+ }
+
+ if (auto st = CreateDirIfNeeded(Dirname(hashtree_file), 0700); !st.ok()) {
return st.error();
}
bool should_regenerate_hashtree = false;
diff --git a/apexd/apexd_verity_test.cpp b/apexd/apexd_verity_test.cpp
index cf2317c8..4e53ccbf 100644
--- a/apexd/apexd_verity_test.cpp
+++ b/apexd/apexd_verity_test.cpp
@@ -25,7 +25,6 @@
#include <gtest/gtest.h>
#include "apex_file.h"
-#include "apex_preinstalled_data.h"
#include "apexd_test_utils.h"
#include "apexd_verity.h"
@@ -45,12 +44,11 @@ static std::string GetTestFile(const std::string& name) {
}
TEST(ApexdVerityTest, ReusesHashtree) {
- ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
TemporaryDir td;
auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
ASSERT_TRUE(IsOk(apex));
- auto verity_data = apex->VerifyApexVerity();
+ auto verity_data = apex->VerifyApexVerity(apex->GetBundledPublicKey());
ASSERT_TRUE(IsOk(verity_data));
auto hashtree_file = StringPrintf("%s/hashtree", td.path);
@@ -78,12 +76,11 @@ TEST(ApexdVerityTest, ReusesHashtree) {
}
TEST(ApexdVerityTest, RegenerateHashree) {
- ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
TemporaryDir td;
auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
ASSERT_TRUE(IsOk(apex));
- auto verity_data = apex->VerifyApexVerity();
+ auto verity_data = apex->VerifyApexVerity(apex->GetBundledPublicKey());
ASSERT_TRUE(IsOk(verity_data));
auto hashtree_file = StringPrintf("%s/hashtree", td.path);
@@ -98,7 +95,7 @@ TEST(ApexdVerityTest, RegenerateHashree) {
auto apex2 =
ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree_2.apex"));
ASSERT_TRUE(IsOk(apex2));
- auto verity_data2 = apex2->VerifyApexVerity();
+ auto verity_data2 = apex2->VerifyApexVerity(apex2->GetBundledPublicKey());
ASSERT_TRUE(IsOk(verity_data2));
// Now call PrepareHashTree again. Since digest doesn't match, hashtree
@@ -115,5 +112,20 @@ TEST(ApexdVerityTest, RegenerateHashree) {
ASSERT_NE(first_hashtree, second_hashtree) << hashtree_file << " was reused";
}
+TEST(ApexdVerityTest, CannotPrepareHashTreeForCompressedApex) {
+ TemporaryDir td;
+
+ auto apex =
+ ApexFile::Open(GetTestFile("com.android.apex.compressed.v1.capex"));
+ ASSERT_TRUE(IsOk(apex));
+ std::string hash_tree;
+ ApexVerityData verity_data;
+ auto result = PrepareHashTree(*apex, verity_data, hash_tree);
+ ASSERT_FALSE(IsOk(result));
+ ASSERT_THAT(
+ result.error().message(),
+ ::testing::HasSubstr("Cannot prepare HashTree of compressed APEX"));
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index b8aced60..4f3d2fc3 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -36,13 +36,14 @@
#include <utils/String16.h>
#include "apex_file.h"
-#include "apex_preinstalled_data.h"
+#include "apex_file_repository.h"
#include "apexd.h"
#include "apexd_session.h"
#include "string_log.h"
#include <android/apex/BnApexService.h>
+using android::base::Join;
using android::base::Result;
namespace android {
@@ -78,10 +79,10 @@ class ApexService : public BnApexService {
BinderStatus getSessions(std::vector<ApexSessionInfo>* aidl_return) override;
BinderStatus getStagedSessionInfo(
int session_id, ApexSessionInfo* apex_session_info) override;
- BinderStatus activatePackage(const std::string& packagePath) override;
- BinderStatus deactivatePackage(const std::string& packagePath) override;
+ BinderStatus activatePackage(const std::string& package_path) override;
+ BinderStatus deactivatePackage(const std::string& package_path) override;
BinderStatus getActivePackages(std::vector<ApexInfo>* aidl_return) override;
- BinderStatus getActivePackage(const std::string& packageName,
+ BinderStatus getActivePackage(const std::string& package_name,
ApexInfo* aidl_return) override;
BinderStatus getAllPackages(std::vector<ApexInfo>* aidl_return) override;
BinderStatus preinstallPackages(
@@ -92,14 +93,26 @@ class ApexService : public BnApexService {
BinderStatus revertActiveSessions() override;
BinderStatus resumeRevertIfNeeded() override;
BinderStatus snapshotCeData(int user_id, int rollback_id,
- const std::string& apex_name,
- int64_t* _aidl_return) override;
+ const std::string& apex_name) override;
BinderStatus restoreCeData(int user_id, int rollback_id,
const std::string& apex_name) override;
BinderStatus destroyDeSnapshots(int rollback_id) override;
+ BinderStatus destroyCeSnapshots(int user_id, int rollback_id) override;
BinderStatus destroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids) override;
BinderStatus remountPackages() override;
+ BinderStatus recollectPreinstalledData(
+ const std::vector<std::string>& paths) override;
+ BinderStatus recollectDataApex(const std::string& path,
+ const std::string& decompression_dir) override;
+ BinderStatus markBootCompleted() override;
+ BinderStatus calculateSizeForCompressedApex(
+ const CompressedApexInfoList& compressed_apex_info_list,
+ int64_t* required_size) override;
+ BinderStatus reserveSpaceForCompressedApex(
+ const CompressedApexInfoList& compressed_apex_info_list) override;
+ BinderStatus installAndActivatePackage(const std::string& package_path,
+ ApexInfo* aidl_return) override;
status_t dump(int fd, const Vector<String16>& args) override;
@@ -120,39 +133,37 @@ BinderStatus CheckDebuggable(const std::string& name) {
}
BinderStatus ApexService::stagePackages(const std::vector<std::string>& paths) {
- BinderStatus debugCheck = CheckDebuggable("stagePackages");
- if (!debugCheck.isOk()) {
- return debugCheck;
+ BinderStatus debug_check = CheckDebuggable("stagePackages");
+ if (!debug_check.isOk()) {
+ return debug_check;
}
LOG(DEBUG) << "stagePackages() received by ApexService, paths "
<< android::base::Join(paths, ',');
- Result<void> res = ::android::apex::stagePackages(paths);
+ Result<void> res = ::android::apex::StagePackages(paths);
if (res.ok()) {
return BinderStatus::ok();
}
- // TODO: Get correct binder error status.
LOG(ERROR) << "Failed to stage " << android::base::Join(paths, ',') << ": "
<< res.error();
return BinderStatus::fromExceptionCode(
- BinderStatus::EX_ILLEGAL_ARGUMENT,
+ BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
BinderStatus ApexService::unstagePackages(
const std::vector<std::string>& paths) {
- Result<void> res = ::android::apex::unstagePackages(paths);
+ Result<void> res = ::android::apex::UnstagePackages(paths);
if (res.ok()) {
return BinderStatus::ok();
}
- // TODO: Get correct binder error status.
LOG(ERROR) << "Failed to unstage " << android::base::Join(paths, ',') << ": "
<< res.error();
return BinderStatus::fromExceptionCode(
- BinderStatus::EX_ILLEGAL_ARGUMENT,
+ BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
@@ -162,7 +173,7 @@ BinderStatus ApexService::submitStagedSession(const ApexSessionParams& params,
<< params.sessionId << " child sessions: ["
<< android::base::Join(params.childSessionIds, ',') << "]";
- Result<std::vector<ApexFile>> packages = ::android::apex::submitStagedSession(
+ Result<std::vector<ApexFile>> packages = ::android::apex::SubmitStagedSession(
params.sessionId, params.childSessionIds, params.hasRollbackEnabled,
params.isRollback, params.rollbackId);
if (!packages.ok()) {
@@ -186,7 +197,7 @@ BinderStatus ApexService::submitStagedSession(const ApexSessionParams& params,
BinderStatus ApexService::markStagedSessionReady(int session_id) {
LOG(DEBUG) << "markStagedSessionReady() received by ApexService, session id "
<< session_id;
- Result<void> success = ::android::apex::markStagedSessionReady(session_id);
+ Result<void> success = ::android::apex::MarkStagedSessionReady(session_id);
if (!success.ok()) {
LOG(ERROR) << "Failed to mark session id " << session_id
<< " as ready: " << success.error();
@@ -201,7 +212,7 @@ BinderStatus ApexService::markStagedSessionSuccessful(int session_id) {
LOG(DEBUG)
<< "markStagedSessionSuccessful() received by ApexService, session id "
<< session_id;
- Result<void> ret = ::android::apex::markStagedSessionSuccessful(session_id);
+ Result<void> ret = ::android::apex::MarkStagedSessionSuccessful(session_id);
if (!ret.ok()) {
LOG(ERROR) << "Failed to mark session " << session_id
<< " as SUCCESS: " << ret.error();
@@ -212,6 +223,43 @@ BinderStatus ApexService::markStagedSessionSuccessful(int session_id) {
return BinderStatus::ok();
}
+BinderStatus ApexService::markBootCompleted() {
+ ::android::apex::OnBootCompleted();
+ return BinderStatus::ok();
+}
+
+BinderStatus ApexService::calculateSizeForCompressedApex(
+ const CompressedApexInfoList& compressed_apex_info_list,
+ int64_t* required_size) {
+ *required_size = 0;
+ const auto& instance = ApexFileRepository::GetInstance();
+ for (const auto& apex_info : compressed_apex_info_list.apexInfos) {
+ auto should_allocate_space = ShouldAllocateSpaceForDecompression(
+ apex_info.moduleName, apex_info.versionCode, instance);
+ if (!should_allocate_space.ok() || *should_allocate_space) {
+ *required_size += apex_info.decompressedSize;
+ }
+ }
+ return BinderStatus::ok();
+}
+
+BinderStatus ApexService::reserveSpaceForCompressedApex(
+ const CompressedApexInfoList& compressed_apex_info_list) {
+ int64_t required_size;
+ if (auto res = calculateSizeForCompressedApex(compressed_apex_info_list,
+ &required_size);
+ !res.isOk()) {
+ return res;
+ }
+ if (auto res = ReserveSpaceForCompressedApex(required_size, kOtaReservedDir);
+ !res.ok()) {
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(res.error().message().c_str()));
+ }
+ return BinderStatus::ok();
+}
+
static void ClearSessionInfo(ApexSessionInfo* session_info) {
session_info->sessionId = -1;
session_info->isUnknown = false;
@@ -225,13 +273,14 @@ static void ClearSessionInfo(ApexSessionInfo* session_info) {
session_info->isRevertFailed = false;
}
-void convertToApexSessionInfo(const ApexSession& session,
+void ConvertToApexSessionInfo(const ApexSession& session,
ApexSessionInfo* session_info) {
using SessionState = ::apex::proto::SessionState;
ClearSessionInfo(session_info);
session_info->sessionId = session.GetId();
session_info->crashingNativeProcess = session.GetCrashingNativeProcess();
+ session_info->errorMessage = session.GetErrorMessage();
switch (session.GetState()) {
case SessionState::VERIFIED:
@@ -265,23 +314,24 @@ void convertToApexSessionInfo(const ApexSession& session,
}
}
-static ApexInfo getApexInfo(const ApexFile& package) {
+static ApexInfo GetApexInfo(const ApexFile& package) {
+ auto& instance = ApexFileRepository::GetInstance();
ApexInfo out;
out.moduleName = package.GetManifest().name();
out.modulePath = package.GetPath();
out.versionCode = package.GetManifest().version();
out.versionName = package.GetManifest().versionname();
- out.isFactory = package.IsBuiltin();
+ out.isFactory = instance.IsPreInstalledApex(package);
out.isActive = false;
- Result<std::string> preinstalledPath =
- getApexPreinstalledPath(package.GetManifest().name());
- if (preinstalledPath.ok()) {
- out.preinstalledModulePath = *preinstalledPath;
+ Result<std::string> preinstalled_path =
+ instance.GetPreinstalledPath(package.GetManifest().name());
+ if (preinstalled_path.ok()) {
+ out.preinstalledModulePath = *preinstalled_path;
}
return out;
}
-static std::string toString(const ApexInfo& package) {
+static std::string ToString(const ApexInfo& package) {
std::string msg = StringLog()
<< "Module: " << package.moduleName
<< " Version: " << package.versionCode
@@ -297,9 +347,9 @@ BinderStatus ApexService::getSessions(
std::vector<ApexSessionInfo>* aidl_return) {
auto sessions = ApexSession::GetSessions();
for (const auto& session : sessions) {
- ApexSessionInfo sessionInfo;
- convertToApexSessionInfo(session, &sessionInfo);
- aidl_return->push_back(sessionInfo);
+ ApexSessionInfo session_info;
+ ConvertToApexSessionInfo(session, &session_info);
+ aidl_return->push_back(session_info);
}
return BinderStatus::ok();
@@ -317,82 +367,80 @@ BinderStatus ApexService::getStagedSessionInfo(
return BinderStatus::ok();
}
- convertToApexSessionInfo(*session, apex_session_info);
+ ConvertToApexSessionInfo(*session, apex_session_info);
return BinderStatus::ok();
}
-BinderStatus ApexService::activatePackage(const std::string& packagePath) {
- BinderStatus debugCheck = CheckDebuggable("activatePackage");
- if (!debugCheck.isOk()) {
- return debugCheck;
+BinderStatus ApexService::activatePackage(const std::string& package_path) {
+ BinderStatus debug_check = CheckDebuggable("activatePackage");
+ if (!debug_check.isOk()) {
+ return debug_check;
}
LOG(DEBUG) << "activatePackage() received by ApexService, path "
- << packagePath;
+ << package_path;
- Result<void> res = ::android::apex::activatePackage(packagePath);
+ Result<void> res = ::android::apex::ActivatePackage(package_path);
if (res.ok()) {
return BinderStatus::ok();
}
- // TODO: Get correct binder error status.
- LOG(ERROR) << "Failed to activate " << packagePath << ": " << res.error();
+ LOG(ERROR) << "Failed to activate " << package_path << ": " << res.error();
return BinderStatus::fromExceptionCode(
- BinderStatus::EX_ILLEGAL_ARGUMENT,
+ BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
-BinderStatus ApexService::deactivatePackage(const std::string& packagePath) {
- BinderStatus debugCheck = CheckDebuggable("deactivatePackage");
- if (!debugCheck.isOk()) {
- return debugCheck;
+BinderStatus ApexService::deactivatePackage(const std::string& package_path) {
+ BinderStatus debug_check = CheckDebuggable("deactivatePackage");
+ if (!debug_check.isOk()) {
+ return debug_check;
}
LOG(DEBUG) << "deactivatePackage() received by ApexService, path "
- << packagePath;
+ << package_path;
- Result<void> res = ::android::apex::deactivatePackage(packagePath);
+ Result<void> res = ::android::apex::DeactivatePackage(package_path);
if (res.ok()) {
return BinderStatus::ok();
}
- // TODO: Get correct binder error status.
- LOG(ERROR) << "Failed to deactivate " << packagePath << ": " << res.error();
+ LOG(ERROR) << "Failed to deactivate " << package_path << ": " << res.error();
return BinderStatus::fromExceptionCode(
- BinderStatus::EX_ILLEGAL_ARGUMENT,
+ BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
BinderStatus ApexService::getActivePackages(
std::vector<ApexInfo>* aidl_return) {
- auto packages = ::android::apex::getActivePackages();
+ auto packages = ::android::apex::GetActivePackages();
for (const auto& package : packages) {
- ApexInfo apexInfo = getApexInfo(package);
- apexInfo.isActive = true;
- aidl_return->push_back(std::move(apexInfo));
+ ApexInfo apex_info = GetApexInfo(package);
+ apex_info.isActive = true;
+ aidl_return->push_back(std::move(apex_info));
}
return BinderStatus::ok();
}
-BinderStatus ApexService::getActivePackage(const std::string& packageName,
+BinderStatus ApexService::getActivePackage(const std::string& package_name,
ApexInfo* aidl_return) {
- Result<ApexFile> apex = ::android::apex::getActivePackage(packageName);
+ Result<ApexFile> apex = ::android::apex::GetActivePackage(package_name);
if (apex.ok()) {
- *aidl_return = getApexInfo(*apex);
+ *aidl_return = GetApexInfo(*apex);
aidl_return->isActive = true;
}
return BinderStatus::ok();
}
BinderStatus ApexService::getAllPackages(std::vector<ApexInfo>* aidl_return) {
- const auto& active = ::android::apex::getActivePackages();
- const auto& factory = ::android::apex::getFactoryPackages();
+ const auto& active = ::android::apex::GetActivePackages();
+ const auto& factory = ::android::apex::GetFactoryPackages();
for (const ApexFile& pkg : active) {
- ApexInfo apex_info = getApexInfo(pkg);
+ ApexInfo apex_info = GetApexInfo(pkg);
apex_info.isActive = true;
aidl_return->push_back(std::move(apex_info));
}
@@ -401,55 +449,70 @@ BinderStatus ApexService::getAllPackages(std::vector<ApexInfo>* aidl_return) {
return o.GetPath() == pkg.GetPath();
};
if (std::find_if(active.begin(), active.end(), same_path) == active.end()) {
- aidl_return->push_back(getApexInfo(pkg));
+ aidl_return->push_back(GetApexInfo(pkg));
}
}
return BinderStatus::ok();
}
+BinderStatus ApexService::installAndActivatePackage(
+ const std::string& package_path, ApexInfo* aidl_return) {
+ LOG(DEBUG) << "installAndActivatePackage() received by ApexService, path: "
+ << package_path;
+ auto res = InstallPackage(package_path);
+ if (!res.ok()) {
+ LOG(ERROR) << "Failed to install package " << package_path << " : "
+ << res.error();
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(res.error().message().c_str()));
+ }
+ *aidl_return = GetApexInfo(*res);
+ aidl_return->isActive = true;
+ return BinderStatus::ok();
+}
+
BinderStatus ApexService::preinstallPackages(
const std::vector<std::string>& paths) {
- BinderStatus debugCheck = CheckDebuggable("preinstallPackages");
- if (!debugCheck.isOk()) {
- return debugCheck;
+ BinderStatus debug_check = CheckDebuggable("preinstallPackages");
+ if (!debug_check.isOk()) {
+ return debug_check;
}
- Result<void> res = ::android::apex::preinstallPackages(paths);
+ Result<void> res = ::android::apex::PreinstallPackages(paths);
if (res.ok()) {
return BinderStatus::ok();
}
- // TODO: Get correct binder error status.
LOG(ERROR) << "Failed to preinstall packages "
<< android::base::Join(paths, ',') << ": " << res.error();
return BinderStatus::fromExceptionCode(
- BinderStatus::EX_ILLEGAL_ARGUMENT,
+ BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
BinderStatus ApexService::postinstallPackages(
const std::vector<std::string>& paths) {
- BinderStatus debugCheck = CheckDebuggable("postinstallPackages");
- if (!debugCheck.isOk()) {
- return debugCheck;
+ BinderStatus debug_check = CheckDebuggable("postinstallPackages");
+ if (!debug_check.isOk()) {
+ return debug_check;
}
- Result<void> res = ::android::apex::postinstallPackages(paths);
+ Result<void> res = ::android::apex::PostinstallPackages(paths);
if (res.ok()) {
return BinderStatus::ok();
}
- // TODO: Get correct binder error status.
LOG(ERROR) << "Failed to postinstall packages "
<< android::base::Join(paths, ',') << ": " << res.error();
return BinderStatus::fromExceptionCode(
- BinderStatus::EX_ILLEGAL_ARGUMENT,
+ BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
BinderStatus ApexService::abortStagedSession(int session_id) {
LOG(DEBUG) << "abortStagedSession() received by ApexService.";
- Result<void> res = ::android::apex::abortStagedSession(session_id);
+ Result<void> res = ::android::apex::AbortStagedSession(session_id);
if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_ILLEGAL_ARGUMENT,
@@ -460,7 +523,7 @@ BinderStatus ApexService::abortStagedSession(int session_id) {
BinderStatus ApexService::revertActiveSessions() {
LOG(DEBUG) << "revertActiveSessions() received by ApexService.";
- Result<void> res = ::android::apex::revertActiveSessions("");
+ Result<void> res = ::android::apex::RevertActiveSessions("", "");
if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_ILLEGAL_ARGUMENT,
@@ -470,13 +533,13 @@ BinderStatus ApexService::revertActiveSessions() {
}
BinderStatus ApexService::resumeRevertIfNeeded() {
- BinderStatus debugCheck = CheckDebuggable("resumeRevertIfNeeded");
- if (!debugCheck.isOk()) {
- return debugCheck;
+ BinderStatus debug_check = CheckDebuggable("resumeRevertIfNeeded");
+ if (!debug_check.isOk()) {
+ return debug_check;
}
LOG(DEBUG) << "resumeRevertIfNeeded() received by ApexService.";
- Result<void> res = ::android::apex::resumeRevertIfNeeded();
+ Result<void> res = ::android::apex::ResumeRevertIfNeeded();
if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_ILLEGAL_ARGUMENT,
@@ -486,17 +549,15 @@ BinderStatus ApexService::resumeRevertIfNeeded() {
}
BinderStatus ApexService::snapshotCeData(int user_id, int rollback_id,
- const std::string& apex_name,
- int64_t* _aidl_return) {
+ const std::string& apex_name) {
LOG(DEBUG) << "snapshotCeData() received by ApexService.";
- Result<ino_t> res =
- ::android::apex::snapshotCeData(user_id, rollback_id, apex_name);
+ Result<void> res =
+ ::android::apex::SnapshotCeData(user_id, rollback_id, apex_name);
if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
}
- *_aidl_return = static_cast<uint64_t>(*res);
return BinderStatus::ok();
}
@@ -504,7 +565,7 @@ BinderStatus ApexService::restoreCeData(int user_id, int rollback_id,
const std::string& apex_name) {
LOG(DEBUG) << "restoreCeData() received by ApexService.";
Result<void> res =
- ::android::apex::restoreCeData(user_id, rollback_id, apex_name);
+ ::android::apex::RestoreCeData(user_id, rollback_id, apex_name);
if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_SERVICE_SPECIFIC,
@@ -515,8 +576,19 @@ BinderStatus ApexService::restoreCeData(int user_id, int rollback_id,
BinderStatus ApexService::destroyDeSnapshots(int rollback_id) {
LOG(DEBUG) << "destroyDeSnapshots() received by ApexService.";
- Result<void> res = ::android::apex::destroyDeSnapshots(rollback_id);
- if (!res) {
+ Result<void> res = ::android::apex::DestroyDeSnapshots(rollback_id);
+ if (!res.ok()) {
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(res.error().message().c_str()));
+ }
+ return BinderStatus::ok();
+}
+
+BinderStatus ApexService::destroyCeSnapshots(int user_id, int rollback_id) {
+ LOG(DEBUG) << "destroyCeSnapshots() received by ApexService.";
+ Result<void> res = ::android::apex::DestroyCeSnapshots(user_id, rollback_id);
+ if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
@@ -527,9 +599,9 @@ BinderStatus ApexService::destroyDeSnapshots(int rollback_id) {
BinderStatus ApexService::destroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids) {
LOG(DEBUG) << "destroyCeSnapshotsNotSpecified() received by ApexService.";
- Result<void> res = ::android::apex::destroyCeSnapshotsNotSpecified(
+ Result<void> res = ::android::apex::DestroyCeSnapshotsNotSpecified(
user_id, retain_rollback_ids);
- if (!res) {
+ if (!res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
@@ -545,7 +617,47 @@ BinderStatus ApexService::remountPackages() {
if (auto root = CheckCallerIsRoot("remountPackages"); !root.isOk()) {
return root;
}
- if (auto res = ::android::apex::remountPackages(); !res.ok()) {
+ if (auto res = ::android::apex::RemountPackages(); !res.ok()) {
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(res.error().message().c_str()));
+ }
+ return BinderStatus::ok();
+}
+
+BinderStatus ApexService::recollectPreinstalledData(
+ const std::vector<std::string>& paths) {
+ LOG(DEBUG) << "recollectPreinstalledData() received by ApexService, paths: "
+ << Join(paths, ',');
+ if (auto debug = CheckDebuggable("recollectPreinstalledData");
+ !debug.isOk()) {
+ return debug;
+ }
+ if (auto root = CheckCallerIsRoot("recollectPreinstalledData");
+ !root.isOk()) {
+ return root;
+ }
+ ApexFileRepository& instance = ApexFileRepository::GetInstance();
+ if (auto res = instance.AddPreInstalledApex(paths); !res.ok()) {
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(res.error().message().c_str()));
+ }
+ return BinderStatus::ok();
+}
+
+BinderStatus ApexService::recollectDataApex(
+ const std::string& path, const std::string& decompression_dir) {
+ LOG(DEBUG) << "recollectDataApex() received by ApexService, paths " << path
+ << " and " << decompression_dir;
+ if (auto debug = CheckDebuggable("recollectDataApex"); !debug.isOk()) {
+ return debug;
+ }
+ if (auto root = CheckCallerIsRoot("recollectDataApex"); !root.isOk()) {
+ return root;
+ }
+ ApexFileRepository& instance = ApexFileRepository::GetInstance();
+ if (auto res = instance.AddDataApex(path); !res.ok()) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));
@@ -565,16 +677,18 @@ status_t ApexService::onTransact(uint32_t _aidl_code, const Parcel& _aidl_data,
for (int i = 0; i < argc && _aidl_data.dataAvail() > 0; i++) {
args.add(_aidl_data.readString16());
}
- sp<IBinder> unusedCallback;
- sp<IResultReceiver> resultReceiver;
+ sp<IBinder> unused_callback;
+ sp<IResultReceiver> result_receiver;
status_t status;
- if ((status = _aidl_data.readNullableStrongBinder(&unusedCallback)) != OK)
+ if ((status = _aidl_data.readNullableStrongBinder(&unused_callback)) !=
+ OK)
return status;
- if ((status = _aidl_data.readNullableStrongBinder(&resultReceiver)) != OK)
+ if ((status = _aidl_data.readNullableStrongBinder(&result_receiver)) !=
+ OK)
return status;
status = shellCommand(in, out, err, args);
- if (resultReceiver != nullptr) {
- resultReceiver->send(status);
+ if (result_receiver != nullptr) {
+ result_receiver->send(status);
}
return OK;
}
@@ -593,7 +707,7 @@ status_t ApexService::dump(int fd, const Vector<String16>& /*args*/) {
return BAD_VALUE;
} else {
for (const auto& item : list) {
- std::string msg = toString(item);
+ std::string msg = ToString(item);
dprintf(fd, "%s", msg.c_str());
}
}
@@ -611,14 +725,19 @@ status_t ApexService::dump(int fd, const Vector<String16>& /*args*/) {
}
}
std::string revert_reason = "";
- std::string crashing_native_process = session.GetCrashingNativeProcess();
+ const auto& crashing_native_process = session.GetCrashingNativeProcess();
if (!crashing_native_process.empty()) {
revert_reason = " Revert Reason: " + crashing_native_process;
}
+ std::string error_message_dump = "";
+ const auto& error_message = session.GetErrorMessage();
+ if (!error_message.empty()) {
+ error_message_dump = " Error Message: " + error_message;
+ }
std::string msg =
StringLog() << "Session ID: " << session.GetId() << child_ids_str
<< " State: " << SessionState_State_Name(session.GetState())
- << revert_reason << std::endl;
+ << revert_reason << error_message_dump << std::endl;
dprintf(fd, "%s", msg.c_str());
}
@@ -637,25 +756,25 @@ status_t ApexService::shellCommand(int in, int out, int err,
}
log << "ApexService:" << std::endl
<< " help - display this help" << std::endl
- << " stagePackages [packagePath1] ([packagePath2]...) - stage "
+ << " stagePackages [package_path1] ([package_path2]...) - stage "
"multiple packages from the given path"
<< std::endl
- << " getActivePackage [packageName] - return info for active package "
+ << " getActivePackage [package_name] - return info for active package "
"with given name, if present"
<< std::endl
<< " getAllPackages - return the list of all packages" << std::endl
<< " getActivePackages - return the list of active packages"
<< std::endl
- << " activatePackage [packagePath] - activate package from the "
+ << " activatePackage [package_path] - activate package from the "
"given path"
<< std::endl
- << " deactivatePackage [packagePath] - deactivate package from the "
+ << " deactivatePackage [package_path] - deactivate package from the "
"given path"
<< std::endl
- << " preinstallPackages [packagePath1] ([packagePath2]...) - run "
+ << " preinstallPackages [package_path1] ([package_path2]...) - run "
"pre-install hooks of the given packages"
<< std::endl
- << " postinstallPackages [packagePath1] ([packagePath2]...) - run "
+ << " postinstallPackages [package_path1] ([package_path2]...) - run "
"post-install hooks of the given packages"
<< std::endl
<< " getStagedSessionInfo [sessionId] - displays information about a "
@@ -687,7 +806,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
if (cmd == String16("stagePackages")) {
if (args.size() < 2) {
- print_help(err, "stagePackages requires at least one packagePath");
+ print_help(err, "stagePackages requires at least one package_path");
return BAD_VALUE;
}
std::vector<std::string> pkgs;
@@ -713,7 +832,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
BinderStatus status = getAllPackages(&list);
if (status.isOk()) {
for (const auto& item : list) {
- std::string msg = toString(item);
+ std::string msg = ToString(item);
dprintf(out, "%s", msg.c_str());
}
return OK;
@@ -733,7 +852,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
BinderStatus status = getActivePackages(&list);
if (status.isOk()) {
for (const auto& item : list) {
- std::string msg = toString(item);
+ std::string msg = ToString(item);
dprintf(out, "%s", msg.c_str());
}
return OK;
@@ -753,7 +872,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
ApexInfo package;
BinderStatus status = getActivePackage(String8(args[1]).string(), &package);
if (status.isOk()) {
- std::string msg = toString(package);
+ std::string msg = ToString(package);
dprintf(out, "%s", msg.c_str());
return OK;
}
@@ -768,7 +887,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
if (cmd == String16("activatePackage")) {
if (args.size() != 2) {
- print_help(err, "activatePackage requires one packagePath");
+ print_help(err, "activatePackage requires one package_path");
return BAD_VALUE;
}
BinderStatus status = activatePackage(String8(args[1]).string());
@@ -783,7 +902,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
if (cmd == String16("deactivatePackage")) {
if (args.size() != 2) {
- print_help(err, "deactivatePackage requires one packagePath");
+ print_help(err, "deactivatePackage requires one package_path");
return BAD_VALUE;
}
BinderStatus status = deactivatePackage(String8(args[1]).string());
@@ -856,7 +975,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
BinderStatus status = submitStagedSession(params, &list);
if (status.isOk()) {
for (const auto& item : list.apexInfos) {
- std::string msg = toString(item);
+ std::string msg = ToString(item);
dprintf(out, "%s", msg.c_str());
}
return OK;
@@ -872,7 +991,7 @@ status_t ApexService::shellCommand(int in, int out, int err,
if (args.size() < 2) {
print_help(err,
"preinstallPackages/postinstallPackages requires at least"
- " one packagePath");
+ " one package_path");
return BAD_VALUE;
}
std::vector<std::string> pkgs;
@@ -929,15 +1048,15 @@ void CreateAndRegisterService() {
sp<ProcessState> ps(ProcessState::self());
// Create binder service and register with LazyServiceRegistrar
- sp<ApexService> apexService = new ApexService();
- auto lazyRegistrar = LazyServiceRegistrar::getInstance();
- lazyRegistrar.forcePersist(true);
- lazyRegistrar.registerService(apexService, kApexServiceName);
+ sp<ApexService> apex_service = sp<ApexService>::make();
+ auto lazy_registrar = LazyServiceRegistrar::getInstance();
+ lazy_registrar.forcePersist(true);
+ lazy_registrar.registerService(apex_service, kApexServiceName);
}
void AllowServiceShutdown() {
- auto lazyRegistrar = LazyServiceRegistrar::getInstance();
- lazyRegistrar.forcePersist(false);
+ auto lazy_registrar = LazyServiceRegistrar::getInstance();
+ lazy_registrar.forcePersist(false);
}
void StartThreadPool() {
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index 4c054f10..d0d4633e 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -59,8 +59,8 @@
#include "apexd_session.h"
#include "apexd_test_utils.h"
#include "apexd_utils.h"
-
#include "session_state.pb.h"
+#include "string_log.h"
using apex::proto::SessionState;
@@ -73,6 +73,7 @@ using android::apex::testing::ApexInfoEq;
using android::apex::testing::CreateSessionInfo;
using android::apex::testing::IsOk;
using android::apex::testing::SessionInfoEq;
+using android::base::ErrnoError;
using android::base::Join;
using android::base::ReadFully;
using android::base::StartsWith;
@@ -82,6 +83,7 @@ using android::dm::DeviceMapper;
using android::fs_mgr::Fstab;
using android::fs_mgr::GetEntryForMountPoint;
using android::fs_mgr::ReadFstabFromFile;
+using ::apex::proto::ApexManifest;
using ::testing::Contains;
using ::testing::EndsWith;
using ::testing::HasSubstr;
@@ -123,6 +125,7 @@ class ApexServiceTest : public ::testing::Test {
vold_service_->supportsCheckpoint(&supports_fs_checkpointing_);
ASSERT_TRUE(IsOk(status));
CleanUp();
+ service_->recollectPreinstalledData(kApexPackageBuiltinDirs);
}
void TearDown() override { CleanUp(); }
@@ -296,9 +299,8 @@ class ApexServiceTest : public ::testing::Test {
"-f",
file,
};
- std::string error_msg;
- int res = ForkAndRun(args, &error_msg);
- CHECK_EQ(0, res) << error_msg;
+ auto res = ForkAndRun(args);
+ CHECK(res.ok()) << res.error();
std::string data;
CHECK(android::base::ReadFileToString(file, &data));
@@ -447,21 +449,10 @@ class ApexServiceTest : public ::testing::Test {
private:
void CleanUp() {
- auto status = WalkDir(kApexDataDir, [](const fs::directory_entry& p) {
- std::error_code ec;
- fs::file_status status = p.status(ec);
- ASSERT_FALSE(ec) << "Failed to stat " << p.path() << " : "
- << ec.message();
- if (fs::is_directory(status)) {
- fs::remove_all(p.path(), ec);
- } else {
- fs::remove(p.path(), ec);
- }
- ASSERT_FALSE(ec) << "Failed to delete " << p.path() << " : "
- << ec.message();
- });
- fs::remove_all(kApexSessionsDir);
- ASSERT_TRUE(IsOk(status));
+ DeleteDirContent(kActiveApexPackagesDataDir);
+ DeleteDirContent(kApexBackupDir);
+ DeleteDirContent(kApexHashTreeDir);
+ DeleteDirContent(ApexSession::GetSessionsDir());
DeleteIfExists("/data/misc_ce/0/apexdata/apex.apexd_test");
DeleteIfExists("/data/misc_ce/0/apexrollback/123456");
@@ -526,7 +517,8 @@ Result<void> ReadDevice(const std::string& block_device) {
static constexpr size_t kBufSize = 1024 * kBlockSize;
std::vector<uint8_t> buffer(kBufSize);
- unique_fd fd(TEMP_FAILURE_RETRY(open(block_device.c_str(), O_RDONLY)));
+ unique_fd fd(
+ TEMP_FAILURE_RETRY(open(block_device.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd.get() == -1) {
return ErrnoError() << "Can't open " << block_device;
}
@@ -642,7 +634,7 @@ TEST_F(ApexServiceTest, StageFailKey) {
// May contain one of two errors.
std::string error = st.exceptionMessage().c_str();
- ASSERT_THAT(error, HasSubstr("No preinstalled data found for package "
+ ASSERT_THAT(error, HasSubstr("No preinstalled apex found for package "
"com.android.apex.test_package.no_inst_key"));
}
@@ -718,7 +710,7 @@ TEST_F(ApexServiceTest, SubmitStagedSessionFailDoesNotLeakTempVerityDevices) {
}
}
-TEST_F(ApexServiceTest, StageSuccess_ClearsPreviouslyActivePackage) {
+TEST_F(ApexServiceTest, StageSuccessClearsPreviouslyActivePackage) {
PrepareTestApexForInstall installer1(GetTestFile("apex.apexd_test_v2.apex"));
PrepareTestApexForInstall installer2(
GetTestFile("apex.apexd_test_different_app.apex"));
@@ -782,7 +774,6 @@ TEST_F(ApexServiceTest, MultiStageSuccess) {
}
ASSERT_EQ(std::string("com.android.apex.test_package"), installer.package);
- // TODO: Add second test. Right now, just use a separate version.
PrepareTestApexForInstall installer2(GetTestFile("apex.apexd_test_v2.apex"));
if (!installer2.Prepare()) {
return;
@@ -840,18 +831,10 @@ TEST_F(ApexServiceTest, SnapshotCeData) {
ASSERT_TRUE(
RegularFileExists("/data/misc_ce/0/apexdata/apex.apexd_test/hello.txt"));
- int64_t result;
- service_->snapshotCeData(0, 123456, "apex.apexd_test", &result);
+ service_->snapshotCeData(0, 123456, "apex.apexd_test");
ASSERT_TRUE(RegularFileExists(
"/data/misc_ce/0/apexrollback/123456/apex.apexd_test/hello.txt"));
-
- // Check that the return value is the inode of the snapshot directory.
- struct stat buf;
- memset(&buf, 0, sizeof(buf));
- ASSERT_EQ(0,
- stat("/data/misc_ce/0/apexrollback/123456/apex.apexd_test", &buf));
- ASSERT_EQ(int64_t(buf.st_ino), result);
}
TEST_F(ApexServiceTest, RestoreCeData) {
@@ -878,7 +861,7 @@ TEST_F(ApexServiceTest, RestoreCeData) {
DirExists("/data/misc_ce/0/apexrollback/123456/apex.apexd_test"));
}
-TEST_F(ApexServiceTest, DestroyDeSnapshots_DeSys) {
+TEST_F(ApexServiceTest, DestroyDeSnapshotsDeSys) {
CreateDir("/data/misc/apexrollback/123456");
CreateDir("/data/misc/apexrollback/123456/my.apex");
CreateFile("/data/misc/apexrollback/123456/my.apex/hello.txt");
@@ -896,7 +879,7 @@ TEST_F(ApexServiceTest, DestroyDeSnapshots_DeSys) {
ASSERT_FALSE(DirExists("/data/misc/apexrollback/123456"));
}
-TEST_F(ApexServiceTest, DestroyDeSnapshots_DeUser) {
+TEST_F(ApexServiceTest, DestroyDeSnapshotsDeUser) {
CreateDir("/data/misc_de/0/apexrollback/123456");
CreateDir("/data/misc_de/0/apexrollback/123456/my.apex");
CreateFile("/data/misc_de/0/apexrollback/123456/my.apex/hello.txt");
@@ -914,6 +897,31 @@ TEST_F(ApexServiceTest, DestroyDeSnapshots_DeUser) {
ASSERT_FALSE(DirExists("/data/misc_de/0/apexrollback/123456"));
}
+TEST_F(ApexServiceTest, DestroyCeSnapshots) {
+ CreateDir("/data/misc_ce/0/apexrollback/123456");
+ CreateDir("/data/misc_ce/0/apexrollback/123456/apex.apexd_test");
+ CreateFile("/data/misc_ce/0/apexrollback/123456/apex.apexd_test/file.txt");
+
+ CreateDir("/data/misc_ce/0/apexrollback/77777");
+ CreateDir("/data/misc_ce/0/apexrollback/77777/apex.apexd_test");
+ CreateFile("/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt");
+
+ ASSERT_TRUE(RegularFileExists(
+ "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/file.txt"));
+ ASSERT_TRUE(RegularFileExists(
+ "/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt"));
+
+ android::binder::Status st = service_->destroyCeSnapshots(0, 123456);
+ ASSERT_TRUE(IsOk(st));
+ // Should be OK if the directory doesn't exist.
+ st = service_->destroyCeSnapshots(1, 123456);
+ ASSERT_TRUE(IsOk(st));
+
+ ASSERT_TRUE(RegularFileExists(
+ "/data/misc_ce/0/apexrollback/77777/apex.apexd_test/thing.txt"));
+ ASSERT_FALSE(DirExists("/data/misc_ce/0/apexrollback/123456"));
+}
+
TEST_F(ApexServiceTest, DestroyCeSnapshotsNotSpecified) {
CreateDir("/data/misc_ce/0/apexrollback/123456");
CreateDir("/data/misc_ce/0/apexrollback/123456/apex.apexd_test");
@@ -1131,7 +1139,8 @@ TEST_F(ApexServiceActivationSuccessTest, ShowsUpInMountedApexDatabase) {
<< GetDebugStr(installer_.get());
MountedApexDatabase db;
- db.PopulateFromMounts();
+ db.PopulateFromMounts(kActiveApexPackagesDataDir, kApexDecompressedDir,
+ kApexHashTreeDir);
std::optional<MountedApexData> mounted_apex;
db.ForallMountedApexes(installer_->package,
@@ -1257,7 +1266,8 @@ TEST_F(ApexServiceNoHashtreeApexActivationTest, ShowsUpInMountedApexDatabase) {
<< GetDebugStr(installer_.get());
MountedApexDatabase db;
- db.PopulateFromMounts();
+ db.PopulateFromMounts(kActiveApexPackagesDataDir, kApexDecompressedDir,
+ kApexHashTreeDir);
std::optional<MountedApexData> mounted_apex;
db.ForallMountedApexes(installer_->package,
@@ -1327,7 +1337,7 @@ TEST_F(ApexServiceTest, NoHashtreeApexStagePackagesMovesHashtree) {
auto read_fn = [](const std::string& path) -> std::vector<uint8_t> {
static constexpr size_t kBufSize = 4096;
std::vector<uint8_t> buffer(kBufSize);
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY)));
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd.get() == -1) {
PLOG(ERROR) << "Failed to open " << path;
ADD_FAILURE();
@@ -1379,53 +1389,78 @@ TEST_F(ApexServiceTest, NoHashtreeApexStagePackagesMovesHashtree) {
}
TEST_F(ApexServiceTest, GetFactoryPackages) {
- Result<std::vector<ApexInfo>> factoryPackages = GetFactoryPackages();
- ASSERT_TRUE(IsOk(factoryPackages));
- ASSERT_TRUE(factoryPackages->size() > 0);
+ Result<std::vector<ApexInfo>> factory_packages = GetFactoryPackages();
+ ASSERT_TRUE(IsOk(factory_packages));
+ ASSERT_TRUE(factory_packages->size() > 0);
+
+ std::vector<std::string> builtin_dirs;
+ for (const auto& d : kApexPackageBuiltinDirs) {
+ std::string realpath;
+ if (android::base::Realpath(d, &realpath)) {
+ builtin_dirs.push_back(realpath);
+ }
+ // realpath might fail in case when dir is a non-existing path. We can
+ // ignore non-existing paths.
+ }
+
+ // Decompressed APEX is also considred factory package
+ builtin_dirs.push_back(kApexDecompressedDir);
- for (const ApexInfo& package : *factoryPackages) {
- ASSERT_TRUE(isPathForBuiltinApexes(package.modulePath));
+ for (const ApexInfo& package : *factory_packages) {
+ bool is_builtin = false;
+ for (const auto& dir : builtin_dirs) {
+ if (StartsWith(package.modulePath, dir)) {
+ is_builtin = true;
+ }
+ }
+ ASSERT_TRUE(is_builtin);
}
}
TEST_F(ApexServiceTest, NoPackagesAreBothActiveAndInactive) {
- Result<std::vector<ApexInfo>> activePackages = GetActivePackages();
- ASSERT_TRUE(IsOk(activePackages));
- ASSERT_TRUE(activePackages->size() > 0);
- Result<std::vector<ApexInfo>> inactivePackages = GetInactivePackages();
- ASSERT_TRUE(IsOk(inactivePackages));
- std::vector<std::string> activePackagesStrings =
- GetPackagesStrings(*activePackages);
- std::vector<std::string> inactivePackagesStrings =
- GetPackagesStrings(*inactivePackages);
- std::sort(activePackagesStrings.begin(), activePackagesStrings.end());
- std::sort(inactivePackagesStrings.begin(), inactivePackagesStrings.end());
+ Result<std::vector<ApexInfo>> active_packages = GetActivePackages();
+ ASSERT_TRUE(IsOk(active_packages));
+ ASSERT_TRUE(active_packages->size() > 0);
+ Result<std::vector<ApexInfo>> inactive_packages = GetInactivePackages();
+ ASSERT_TRUE(IsOk(inactive_packages));
+ std::vector<std::string> active_packages_strings =
+ GetPackagesStrings(*active_packages);
+ std::vector<std::string> inactive_packages_strings =
+ GetPackagesStrings(*inactive_packages);
+ std::sort(active_packages_strings.begin(), active_packages_strings.end());
+ std::sort(inactive_packages_strings.begin(), inactive_packages_strings.end());
std::vector<std::string> intersection;
std::set_intersection(
- activePackagesStrings.begin(), activePackagesStrings.end(),
- inactivePackagesStrings.begin(), inactivePackagesStrings.end(),
+ active_packages_strings.begin(), active_packages_strings.end(),
+ inactive_packages_strings.begin(), inactive_packages_strings.end(),
std::back_inserter(intersection));
ASSERT_THAT(intersection, SizeIs(0));
}
TEST_F(ApexServiceTest, GetAllPackages) {
- Result<std::vector<ApexInfo>> allPackages = GetAllPackages();
- ASSERT_TRUE(IsOk(allPackages));
- ASSERT_TRUE(allPackages->size() > 0);
- Result<std::vector<ApexInfo>> activePackages = GetActivePackages();
- std::vector<std::string> activeStrings = GetPackagesStrings(*activePackages);
- Result<std::vector<ApexInfo>> factoryPackages = GetFactoryPackages();
- std::vector<std::string> factoryStrings =
- GetPackagesStrings(*factoryPackages);
- for (ApexInfo& apexInfo : *allPackages) {
- std::string packageString = GetPackageString(apexInfo);
- bool shouldBeActive = std::find(activeStrings.begin(), activeStrings.end(),
- packageString) != activeStrings.end();
- bool shouldBeFactory =
- std::find(factoryStrings.begin(), factoryStrings.end(),
- packageString) != factoryStrings.end();
- ASSERT_EQ(shouldBeActive, apexInfo.isActive);
- ASSERT_EQ(shouldBeFactory, apexInfo.isFactory);
+ Result<std::vector<ApexInfo>> all_packages = GetAllPackages();
+ ASSERT_TRUE(IsOk(all_packages));
+ ASSERT_TRUE(all_packages->size() > 0);
+ Result<std::vector<ApexInfo>> active_packages = GetActivePackages();
+ std::vector<std::string> active_strings =
+ GetPackagesStrings(*active_packages);
+ Result<std::vector<ApexInfo>> factory_packages = GetFactoryPackages();
+ std::vector<std::string> factory_strings =
+ GetPackagesStrings(*factory_packages);
+ for (ApexInfo& apexInfo : *all_packages) {
+ std::string package_string = GetPackageString(apexInfo);
+ bool should_be_active =
+ std::find(active_strings.begin(), active_strings.end(),
+ package_string) != active_strings.end();
+ bool should_be_factory =
+ std::find(factory_strings.begin(), factory_strings.end(),
+ package_string) != factory_strings.end();
+ ASSERT_EQ(should_be_active, apexInfo.isActive)
+ << package_string << " should " << (should_be_active ? "" : "not ")
+ << "be active";
+ ASSERT_EQ(should_be_factory, apexInfo.isFactory)
+ << package_string << " should " << (should_be_factory ? "" : "not ")
+ << "be factory";
}
}
@@ -1983,6 +2018,42 @@ TEST_F(ApexServiceTest, AbortStagedSessionActivatedFail) {
SessionInfoEq(expected2)));
}
+// Only finalized sessions should be deleted on DeleteFinalizedSessions()
+TEST_F(ApexServiceTest, DeleteFinalizedSessions) {
+ // Fetch list of all session state
+ std::vector<SessionState::State> states;
+ for (int i = SessionState::State_MIN; i < SessionState::State_MAX; i++) {
+ if (!SessionState::State_IsValid(i)) {
+ continue;
+ }
+ states.push_back(SessionState::State(i));
+ }
+
+ // For every session state, create a new session. This is to verify we only
+ // delete sessions in final state.
+ auto nonFinalSessions = 0u;
+ for (auto i = 0u; i < states.size(); i++) {
+ auto session = ApexSession::CreateSession(230 + i);
+ SessionState::State state = states[i];
+ ASSERT_TRUE(IsOk(session->UpdateStateAndCommit(state)));
+ if (!session->IsFinalized()) {
+ nonFinalSessions++;
+ }
+ }
+ std::vector<ApexSession> sessions = ApexSession::GetSessions();
+ ASSERT_EQ(states.size(), sessions.size());
+
+ // Now try cleaning up all finalized sessions
+ ApexSession::DeleteFinalizedSessions();
+ sessions = ApexSession::GetSessions();
+ ASSERT_EQ(nonFinalSessions, sessions.size());
+
+ // Verify only finalized sessions have been deleted
+ for (auto& session : sessions) {
+ ASSERT_FALSE(session.IsFinalized());
+ }
+}
+
TEST_F(ApexServiceTest, BackupActivePackages) {
if (supports_fs_checkpointing_) {
GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled";
@@ -2039,7 +2110,7 @@ TEST_F(ApexServiceTest, BackupActivePackagesClearsPreviousBackup) {
}
// Make sure /data/apex/backups exists.
- ASSERT_TRUE(IsOk(createDirIfNeeded(std::string(kApexBackupDir), 0700)));
+ ASSERT_TRUE(IsOk(CreateDirIfNeeded(std::string(kApexBackupDir), 0700)));
// Create some bogus files in /data/apex/backups.
std::ofstream old_backup(StringPrintf("%s/file1", kApexBackupDir));
ASSERT_TRUE(old_backup.good());
@@ -2083,7 +2154,7 @@ TEST_F(ApexServiceTest, BackupActivePackagesZeroActivePackages) {
// Make sure that /data/apex/active exists and is empty
ASSERT_TRUE(
- IsOk(createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755)));
+ IsOk(CreateDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755)));
auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir);
ASSERT_TRUE(IsOk(active_pkgs));
ASSERT_EQ(0u, active_pkgs->size());
@@ -2098,7 +2169,7 @@ TEST_F(ApexServiceTest, BackupActivePackagesZeroActivePackages) {
ASSERT_EQ(0u, backups->size());
}
-TEST_F(ApexServiceTest, ActivePackagesFolderDoesNotExist) {
+TEST_F(ApexServiceTest, ActivePackagesDirEmpty) {
PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex"),
"/data/app-staging/session_41",
"staging_data_file");
@@ -2107,10 +2178,8 @@ TEST_F(ApexServiceTest, ActivePackagesFolderDoesNotExist) {
return;
}
- // Make sure that /data/apex/active does not exist
- std::error_code ec;
- fs::remove_all(fs::path(kActiveApexPackagesDataDir), ec);
- ASSERT_FALSE(ec) << "Failed to delete " << kActiveApexPackagesDataDir;
+ // Make sure that /data/apex/active is empty
+ DeleteDirContent(kActiveApexPackagesDataDir);
ApexInfoList list;
ApexSessionParams params;
@@ -2167,12 +2236,24 @@ TEST_F(ApexServiceTest, UnstagePackagesFail) {
UnorderedElementsAre(installer1.test_installed_file));
}
+TEST_F(ApexServiceTest, UnstagePackagesFailPreInstalledApex) {
+ auto status = service_->unstagePackages(
+ {"/system/apex/com.android.apex.cts.shim.apex"});
+ ASSERT_FALSE(IsOk(status));
+ const std::string& error_message =
+ std::string(status.exceptionMessage().c_str());
+ ASSERT_THAT(error_message,
+ HasSubstr("Can't uninstall pre-installed apex "
+ "/system/apex/com.android.apex.cts.shim.apex"));
+ ASSERT_TRUE(RegularFileExists("/system/apex/com.android.apex.cts.shim.apex"));
+}
+
class ApexServiceRevertTest : public ApexServiceTest {
protected:
void SetUp() override { ApexServiceTest::SetUp(); }
void PrepareBackup(const std::vector<std::string>& pkgs) {
- ASSERT_TRUE(IsOk(createDirIfNeeded(std::string(kApexBackupDir), 0700)));
+ ASSERT_TRUE(IsOk(CreateDirIfNeeded(std::string(kApexBackupDir), 0700)));
for (const auto& pkg : pkgs) {
PrepareTestApexForInstall installer(pkg);
ASSERT_TRUE(installer.Prepare()) << " failed to prepare " << pkg;
@@ -2383,7 +2464,10 @@ TEST_F(ApexServiceRevertTest, RevertStoresCrashingNativeProcess) {
// Make sure /data/apex/active is non-empty.
ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file})));
std::string native_process = "test_process";
- Result<void> res = ::android::apex::revertActiveSessions(native_process);
+ // TODO(ioffe): this is calling into internals of apexd which makes test quite
+ // britle. With some refactoring we should be able to call binder api, or
+ // make this a unit test of apexd.cpp.
+ Result<void> res = ::android::apex::RevertActiveSessions(native_process, "");
session = ApexSession::GetSession(1543);
ASSERT_EQ(session->GetCrashingNativeProcess(), native_process);
}
@@ -2571,21 +2655,6 @@ TEST_F(ApexShimUpdateTest, UpdateToV2Success) {
ASSERT_TRUE(IsOk(service_->stagePackages({installer.test_file})));
}
-TEST_F(ApexShimUpdateTest, UpdateToV2FailureWrongSHA512) {
- PrepareTestApexForInstall installer(
- GetTestFile("com.android.apex.cts.shim.v2_wrong_sha.apex"));
-
- if (!installer.Prepare()) {
- FAIL() << GetDebugStr(&installer);
- }
-
- const auto& status = service_->stagePackages({installer.test_file});
- ASSERT_FALSE(IsOk(status));
- const std::string& error_message =
- std::string(status.exceptionMessage().c_str());
- ASSERT_THAT(error_message, HasSubstr("has unexpected SHA512 hash"));
-}
-
TEST_F(ApexShimUpdateTest, SubmitStagedSessionFailureHasPreInstallHook) {
PrepareTestApexForInstall installer(
GetTestFile("com.android.apex.cts.shim.v2_with_pre_install_hook.apex"),
@@ -2684,7 +2753,7 @@ TEST_F(ApexServiceTest, SubmitStagedSessionCorruptApexFails) {
ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list)));
}
-TEST_F(ApexServiceTest, SubmitStagedSessionCorruptApexFails_b146895998) {
+TEST_F(ApexServiceTest, SubmitStagedSessionCorruptApexFailsB146895998) {
PrepareTestApexForInstall installer(GetTestFile("corrupted_b146895998.apex"),
"/data/app-staging/session_71",
"staging_data_file");
@@ -2699,7 +2768,7 @@ TEST_F(ApexServiceTest, SubmitStagedSessionCorruptApexFails_b146895998) {
ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list)));
}
-TEST_F(ApexServiceTest, StageCorruptApexFails_b146895998) {
+TEST_F(ApexServiceTest, StageCorruptApexFailsB146895998) {
PrepareTestApexForInstall installer(GetTestFile("corrupted_b146895998.apex"));
if (!installer.Prepare()) {
@@ -2756,8 +2825,10 @@ TEST_F(ApexServiceTest, RemountPackagesPackageOnSystemChanged) {
auto active_apex = GetActivePackage("com.android.apex.test_package");
ASSERT_RESULT_OK(active_apex);
ASSERT_EQ(2u, active_apex->versionCode);
- // Sanity check that module path didn't change.
- ASSERT_EQ(kSystemPath, active_apex->modulePath);
+ // Check that module path didn't change, modulo symlink.
+ std::string realSystemPath;
+ ASSERT_TRUE(android::base::Realpath(kSystemPath, &realSystemPath));
+ ASSERT_EQ(realSystemPath, active_apex->modulePath);
}
TEST_F(ApexServiceActivationSuccessTest, RemountPackagesPackageOnDataChanged) {
@@ -2783,6 +2854,25 @@ TEST_F(ApexServiceActivationSuccessTest, RemountPackagesPackageOnDataChanged) {
ASSERT_EQ(installer_->test_installed_file, active_apex->modulePath);
}
+TEST_F(ApexServiceTest,
+ SubmitStagedSessionFailsManifestMismatchCleansUpHashtree) {
+ PrepareTestApexForInstall installer(
+ GetTestFile("apex.apexd_test_no_hashtree_manifest_mismatch.apex"),
+ "/data/app-staging/session_83", "staging_data_file");
+ if (!installer.Prepare()) {
+ return;
+ }
+
+ ApexInfoList list;
+ ApexSessionParams params;
+ params.sessionId = 83;
+ ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list)));
+ std::string hashtree_file = std::string(kApexHashTreeDir) + "/" +
+ installer.package + "@" +
+ std::to_string(installer.version) + ".new";
+ ASSERT_FALSE(RegularFileExists(hashtree_file));
+}
+
class LogTestToLogcat : public ::testing::EmptyTestEventListener {
void OnTestStart(const ::testing::TestInfo& test_info) override {
#ifdef __ANDROID__
@@ -2791,7 +2881,7 @@ class LogTestToLogcat : public ::testing::EmptyTestEventListener {
using base::StringPrintf;
base::LogdLogger l;
std::string msg =
- StringPrintf("=== %s::%s (%s:%d)", test_info.test_case_name(),
+ StringPrintf("=== %s::%s (%s:%d)", test_info.test_suite_name(),
test_info.name(), test_info.file(), test_info.line());
l(LogId::MAIN, LogSeverity::INFO, "ApexTestCases", __FILE__, __LINE__,
msg.c_str());
@@ -2838,11 +2928,164 @@ TEST_F(ApexServiceActivationNoCode, NoCodeApexIsNotExecutable) {
EXPECT_TRUE(found_apex_mountpoint);
}
+struct BannedNameProvider {
+ static std::string GetTestName() { return "sharedlibs.apex"; }
+ static std::string GetPackageName() { return "sharedlibs"; }
+};
+
+class ApexServiceActivationBannedName
+ : public ApexServiceActivationTest<BannedNameProvider> {
+ public:
+ ApexServiceActivationBannedName() : ApexServiceActivationTest(false) {}
+};
+
+TEST_F(ApexServiceActivationBannedName, ApexWithBannedNameCannotBeActivated) {
+ ASSERT_FALSE(
+ IsOk(service_->activatePackage(installer_->test_installed_file)));
+}
+
+namespace {
+void PrepareCompressedTestApex(const std::string& input_apex,
+ const std::string& builtin_dir,
+ const std::string& decompressed_dir,
+ const std::string& active_apex_dir) {
+ const Result<ApexFile>& apex_file = ApexFile::Open(input_apex);
+ ASSERT_TRUE(apex_file.ok());
+ ASSERT_TRUE(apex_file->IsCompressed()) << "Not a compressed APEX";
+
+ auto prebuilt_file_path =
+ builtin_dir + "/" + android::base::Basename(input_apex);
+ fs::copy(input_apex, prebuilt_file_path);
+
+ const ApexManifest& manifest = apex_file->GetManifest();
+ const std::string& package = manifest.name();
+ const int64_t& version = manifest.version();
+
+ auto decompressed_file_path = decompressed_dir + "/" + package + "@" +
+ std::to_string(version) + ".apex";
+ auto result = apex_file->Decompress(decompressed_file_path);
+ ASSERT_TRUE(result.ok()) << "Failed to decompress " << result.error();
+ auto active_apex_file_path =
+ active_apex_dir + "/" + package + "@" + std::to_string(version) + ".apex";
+ auto error =
+ link(decompressed_file_path.c_str(), active_apex_file_path.c_str());
+ ASSERT_EQ(error, 0) << "Failed to hardlink decompressed APEX";
+}
+
+CompressedApexInfo CreateCompressedApex(const std::string& name,
+ const int version, const int size) {
+ CompressedApexInfo result;
+ result.moduleName = name;
+ result.versionCode = version;
+ result.decompressedSize = size;
+ return result;
+}
+} // namespace
+
+class ApexServiceTestForCompressedApex : public ApexServiceTest {
+ public:
+ static constexpr const char* kTempPrebuiltDir = "/data/apex/temp_prebuilt";
+
+ void SetUp() override {
+ ApexServiceTest::SetUp();
+ ASSERT_NE(nullptr, service_.get());
+
+ TemporaryDir decompression_dir, active_apex_dir;
+ if (0 != mkdir(kTempPrebuiltDir, 0777)) {
+ int saved_errno = errno;
+ ASSERT_EQ(saved_errno, EEXIST)
+ << kTempPrebuiltDir << ":" << strerror(saved_errno);
+ }
+ PrepareCompressedTestApex(
+ GetTestFile("com.android.apex.compressed.v1.capex"), kTempPrebuiltDir,
+ kApexDecompressedDir, kActiveApexPackagesDataDir);
+ service_->recollectPreinstalledData({kTempPrebuiltDir});
+ service_->recollectDataApex(kActiveApexPackagesDataDir,
+ kApexDecompressedDir);
+ }
+
+ void TearDown() override {
+ ApexServiceTest::TearDown();
+ DeleteDirContent(kTempPrebuiltDir);
+ rmdir(kTempPrebuiltDir);
+ DeleteDirContent(kApexDecompressedDir);
+ DeleteDirContent(kActiveApexPackagesDataDir);
+ }
+};
+
+TEST_F(ApexServiceTestForCompressedApex, CalculateSizeForCompressedApex) {
+ int64_t result;
+ // Empty list of compressed apex info
+ {
+ CompressedApexInfoList empty_list;
+ ASSERT_TRUE(
+ IsOk(service_->calculateSizeForCompressedApex(empty_list, &result)));
+ ASSERT_EQ(result, 0ll);
+ }
+
+ // Multiple compressed APEX should get summed
+ {
+ CompressedApexInfoList non_empty_list;
+ CompressedApexInfo new_apex = CreateCompressedApex("new_apex", 1, 1);
+ CompressedApexInfo new_apex_2 = CreateCompressedApex("new_apex_2", 1, 2);
+ CompressedApexInfo compressed_apex_same_version =
+ CreateCompressedApex("com.android.apex.compressed", 1, 4);
+ CompressedApexInfo compressed_apex_higher_version =
+ CreateCompressedApex("com.android.apex.compressed", 2, 8);
+ non_empty_list.apexInfos.push_back(new_apex);
+ non_empty_list.apexInfos.push_back(new_apex_2);
+ non_empty_list.apexInfos.push_back(compressed_apex_same_version);
+ non_empty_list.apexInfos.push_back(compressed_apex_higher_version);
+ ASSERT_TRUE(IsOk(
+ service_->calculateSizeForCompressedApex(non_empty_list, &result)));
+ ASSERT_EQ(result, 11ll); // 1+2+8. compressed_apex_same_version is ignored
+ }
+}
+
+TEST_F(ApexServiceTestForCompressedApex, ReserveSpaceForCompressedApex) {
+ // Multiple compressed APEX should reserve equal to
+ // CalculateSizeForCompressedApex
+ {
+ CompressedApexInfoList non_empty_list;
+ CompressedApexInfo new_apex = CreateCompressedApex("new_apex", 1, 1);
+ CompressedApexInfo new_apex_2 = CreateCompressedApex("new_apex_2", 1, 2);
+ CompressedApexInfo compressed_apex_same_version =
+ CreateCompressedApex("com.android.apex.compressed", 1, 4);
+ CompressedApexInfo compressed_apex_higher_version =
+ CreateCompressedApex("com.android.apex.compressed", 2, 8);
+ non_empty_list.apexInfos.push_back(new_apex);
+ non_empty_list.apexInfos.push_back(new_apex_2);
+ non_empty_list.apexInfos.push_back(compressed_apex_same_version);
+ non_empty_list.apexInfos.push_back(compressed_apex_higher_version);
+ int64_t required_size;
+ ASSERT_TRUE(IsOk(service_->calculateSizeForCompressedApex(non_empty_list,
+ &required_size)));
+ ASSERT_EQ(required_size,
+ 11ll); // 1+2+8. compressed_apex_same_version is ignored
+
+ ASSERT_TRUE(IsOk(service_->reserveSpaceForCompressedApex(non_empty_list)));
+ auto files = ReadDir(kOtaReservedDir, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 1u);
+ EXPECT_EQ((int64_t)fs::file_size((*files)[0]), required_size);
+ }
+
+ // Sending empty list should delete reserved file
+ {
+ CompressedApexInfoList empty_list;
+ ASSERT_TRUE(IsOk(service_->reserveSpaceForCompressedApex(empty_list)));
+ auto files = ReadDir(kOtaReservedDir, [](auto _) { return true; });
+ ASSERT_TRUE(IsOk(files));
+ ASSERT_EQ(files->size(), 0u);
+ }
+}
+
} // namespace apex
} // namespace android
int main(int argc, char** argv) {
android::base::InitLogging(argv, &android::base::StderrLogger);
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
::testing::InitGoogleTest(&argc, argv);
::testing::UnitTest::GetInstance()->listeners().Append(
new android::apex::LogTestToLogcat());
diff --git a/apexd/sysprop/Android.bp b/apexd/sysprop/Android.bp
index 7e3ae611..0b81efd9 100644
--- a/apexd/sysprop/Android.bp
+++ b/apexd/sysprop/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
sysprop_library {
name: "com.android.sysprop.apex",
srcs: ["ApexProperties.sysprop"],
diff --git a/apexd/sysprop/ApexProperties.sysprop b/apexd/sysprop/ApexProperties.sysprop
index bd768303..8baf2730 100644
--- a/apexd/sysprop/ApexProperties.sysprop
+++ b/apexd/sysprop/ApexProperties.sysprop
@@ -22,3 +22,19 @@ prop {
access: Readonly
prop_name: "ro.apex.updatable"
}
+
+prop {
+ api_name: "dm_delete_timeout"
+ type: UInt
+ scope: Internal
+ access: Readonly
+ prop_name: "apexd.config.dm_delete.timeout"
+}
+
+prop {
+ api_name: "dm_create_timeout"
+ type: UInt
+ scope: Internal
+ access: Readonly
+ prop_name: "apexd.config.dm_create.timeout"
+}
diff --git a/apexd/sysprop/api/com.android.sysprop.apex-current.txt b/apexd/sysprop/api/com.android.sysprop.apex-current.txt
index 17983127..e69de29b 100644
--- a/apexd/sysprop/api/com.android.sysprop.apex-current.txt
+++ b/apexd/sysprop/api/com.android.sysprop.apex-current.txt
@@ -1,8 +0,0 @@
-props {
- module: "android.sysprop.ApexProperties"
- prop {
- api_name: "updatable"
- scope: Internal
- prop_name: "ro.apex.updatable"
- }
-}
diff --git a/apexer/Android.bp b/apexer/Android.bp
index a33aade4..29be7d1c 100644
--- a/apexer/Android.bp
+++ b/apexer/Android.bp
@@ -12,22 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
apexer_tools = [
"apexer",
"aapt2",
"avbtool",
"e2fsdroid",
- "merge_zips",
"mke2fs",
"resize2fs",
"sefcontext_compile",
- "soong_zip",
"zipalign",
+ "make_f2fs",
+ "sload_f2fs",
// TODO(b/124476339) apex doesn't follow 'required' dependencies so we need to include this
// manually for 'avbtool'.
"fec",
]
+// TODO(b/157110982): cannot specify "required" dependency on go binary
+apexer_go_tools = [
+ "merge_zips",
+ "soong_zip",
+]
+
python_library_host {
name: "apex_manifest",
srcs: [
@@ -51,6 +61,10 @@ python_binary_host {
srcs: [
"apexer.py",
],
+ // TODO(b/157625953) mke2fs.conf can't embedded directly.
+ data: [
+ ":mke2fs_conf",
+ ],
version: {
py2: {
enabled: true,
@@ -103,7 +117,8 @@ apex_test {
manifest: "etc/manifest.json",
ignore_system_library_special_case: true,
key: "com.android.support.apexer.key",
- binaries: apexer_tools,
+ binaries: apexer_tools + apexer_go_tools,
+ updatable: false,
}
// TODO(b/148659029): this test can't run in TEST_MAPPING.
@@ -118,6 +133,7 @@ python_test_host {
":com.android.example-legacy.apex",
":com.android.example-logging_parent.apex",
":com.android.example-overridden_package_name.apex",
+ ":apexer_test_host_tools",
"testdata/com.android.example.apex.avbpubkey",
"testdata/com.android.example.apex.pem",
"testdata/com.android.example.apex.pk8",
@@ -135,8 +151,59 @@ python_test_host {
libs: [
"apex_manifest",
],
- required: [
- "apexer",
- "deapexer"
+}
+
+apexer_deps_minus_go_tools = apexer_tools + [
+ "deapexer",
+ "debugfs_static",
+]
+
+apexer_deps_tools = apexer_deps_minus_go_tools + apexer_go_tools
+
+genrule_defaults {
+ name: "apexer_test_host_tools_list",
+ tools: apexer_deps_tools + [
+ "signapk",
+ ],
+}
+
+genrule {
+ name: "apexer_test_host_tools",
+ srcs: [
+ ":current_android_jar",
+ ],
+ out: ["apexer_test_host_tools.zip"],
+ tools: apexer_deps_tools + [
+ // To force signapk.jar generated in out/soong/host
+ "signapk",
],
+ cmd: "HOST_OUT_BIN=$$(dirname $(location apexer)) && " +
+ "HOST_SOONG_OUT=$$(dirname $$HOST_OUT_BIN) && " +
+ "SIGNAPK_JAR=$$(find $${HOST_SOONG_OUT}/framework -name \"signapk*\") && " +
+ "LIBCPLUSPLUS=$$(find $${HOST_SOONG_OUT}/lib64 -name \"libc++.*\") && " +
+ "LIBCONSCRYPT_OPENJDK_JNI=$$(find $${HOST_SOONG_OUT}/lib64 -name \"libconscrypt_openjdk_jni.*\") && " +
+ "BASE=$(genDir)/binary_files && " +
+ "BIN=$$BASE/bin && " +
+ "LIB=$$BASE/lib64 && " +
+ "mkdir -p $$BIN && " +
+ "mkdir -p $$LIB && " +
+ "cp $(in) $$BIN && " +
+ "cp $(location apexer) $$BIN && " +
+ "cp $(location deapexer) $$BIN && " +
+ "cp $(location avbtool) $$BIN && " +
+ "cp $(location aapt2) $$BIN && " +
+ "cp $(location e2fsdroid) $$BIN && " +
+ "cp $(location merge_zips) $$BIN && " +
+ "cp $(location mke2fs) $$BIN && " +
+ "cp $(location resize2fs) $$BIN && " +
+ "cp $(location sefcontext_compile) $$BIN && " +
+ "cp $(location soong_zip) $$BIN && " +
+ "cp $(location fec) $$BIN && " +
+ "cp $(location zipalign) $$BIN && " +
+ "cp $(location debugfs_static) $$BIN && " +
+ "cp $$SIGNAPK_JAR $$BIN && " +
+ "cp $$LIBCPLUSPLUS $$LIB && " +
+ "cp $$LIBCONSCRYPT_OPENJDK_JNI $$LIB && " +
+ "$(location soong_zip) -C $$BASE -D $$BASE -o $(out) && " +
+ "rm -rf $$BASE",
}
diff --git a/apexer/TEST_MAPPING b/apexer/TEST_MAPPING
index cbb6a858..1ad30781 100644
--- a/apexer/TEST_MAPPING
+++ b/apexer/TEST_MAPPING
@@ -1,4 +1,9 @@
{
+ "presubmit": [
+ {
+ "name": "apexer_test"
+ }
+ ],
"imports": [
{
"path": "system/apex/tests"
diff --git a/apexer/apexer.py b/apexer/apexer.py
index f698c8c8..f0c17f43 100644
--- a/apexer/apexer.py
+++ b/apexer/apexer.py
@@ -23,6 +23,7 @@ import apex_build_info_pb2
import argparse
import hashlib
import os
+import pkgutil
import re
import shlex
import shutil
@@ -101,6 +102,13 @@ def ParseArgs(argv):
choices=['zip', 'image'],
help='type of APEX payload being built "zip" or "image"')
parser.add_argument(
+ '--payload_fs_type',
+ metavar='FS_TYPE',
+ required=False,
+ default='ext4',
+ choices=['ext4', 'f2fs'],
+ help='type of filesystem being used for payload image "ext4" or "f2fs"')
+ parser.add_argument(
'--override_apk_package_name',
required=False,
help='package name of the APK container. Default is the apex name in --manifest.'
@@ -162,7 +170,7 @@ def ParseArgs(argv):
parser.add_argument(
'--unsigned_payload_only',
action='store_true',
- help="""Outputs the unsigned payload image/zip only. Also, setting this flag implies
+ help="""Outputs the unsigned payload image/zip only. Also, setting this flag implies
--payload_only is set too."""
)
parser.add_argument(
@@ -182,7 +190,7 @@ def FindBinaryPath(binary):
':'.join(tool_path_list))
-def RunCommand(cmd, verbose=False, env=None):
+def RunCommand(cmd, verbose=False, env=None, expected_return_values={0}):
env = env or {}
env.update(os.environ.copy())
@@ -194,10 +202,10 @@ def RunCommand(cmd, verbose=False, env=None):
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
output, _ = p.communicate()
- if verbose or p.returncode is not 0:
+ if verbose or p.returncode not in expected_return_values:
print(output.rstrip())
- assert p.returncode is 0, 'Failed to execute: ' + ' '.join(cmd)
+ assert p.returncode in expected_return_values, 'Failed to execute: ' + ' '.join(cmd)
return (output, p.returncode)
@@ -378,6 +386,9 @@ def GenerateBuildInfo(args):
if args.logging_parent:
build_info.logging_parent = args.logging_parent
+ if args.payload_type == 'image':
+ build_info.payload_fs_type = args.payload_fs_type
+
return build_info
def AddLoggingParent(android_manifest, logging_parent_value):
@@ -450,9 +461,8 @@ def CreateApex(args, work_dir):
print("Cannot read manifest file: '" + args.manifest + "'")
return False
- # create an empty ext4 image that is sufficiently big
- # sufficiently big = size + 16MB margin
- size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024)) + 16
+ # create an empty image that is sufficiently big
+ size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024))
content_dir = os.path.join(work_dir, 'content')
os.mkdir(content_dir)
@@ -473,67 +483,112 @@ def CreateApex(args, work_dir):
else:
key_name = os.path.basename(os.path.splitext(args.key)[0])
- if manifest_apex.name != key_name:
- print("package name '" + manifest_apex.name +
- "' does not match with key name '" + key_name + "'")
- return False
img_file = os.path.join(content_dir, 'apex_payload.img')
- # margin is for files that are not under args.input_dir. this consists of
- # n inodes for apex_manifest files and 11 reserved inodes for ext4.
- # TOBO(b/122991714) eliminate these details. use build_image.py which
- # determines the optimal inode count by first building an image and then
- # count the inodes actually used.
- inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
- inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
-
- cmd = ['mke2fs']
- cmd.extend(['-O', '^has_journal']) # because image is read-only
- cmd.extend(['-b', str(BLOCK_SIZE)])
- cmd.extend(['-m', '0']) # reserved block percentage
- cmd.extend(['-t', 'ext4'])
- cmd.extend(['-I', '256']) # inode size
- cmd.extend(['-N', str(inode_num)])
- uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
- cmd.extend(['-U', uu])
- cmd.extend(['-E', 'hash_seed=' + uu])
- cmd.append(img_file)
- cmd.append(str(size_in_mb) + 'M')
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
-
- # Compile the file context into the binary form
- compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin')
- cmd = ['sefcontext_compile']
- cmd.extend(['-o', compiled_file_contexts])
- cmd.append(args.file_contexts)
- RunCommand(cmd, args.verbose)
+ if args.payload_fs_type == 'ext4':
+ # sufficiently big = size + 16MB margin
+ size_in_mb += 16
+
+ # margin is for files that are not under args.input_dir. this consists of
+ # n inodes for apex_manifest files and 11 reserved inodes for ext4.
+ # TOBO(b/122991714) eliminate these details. use build_image.py which
+ # determines the optimal inode count by first building an image and then
+ # count the inodes actually used.
+ inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
+ inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
+
+ cmd = ['mke2fs']
+ cmd.extend(['-O', '^has_journal']) # because image is read-only
+ cmd.extend(['-b', str(BLOCK_SIZE)])
+ cmd.extend(['-m', '0']) # reserved block percentage
+ cmd.extend(['-t', 'ext4'])
+ cmd.extend(['-I', '256']) # inode size
+ cmd.extend(['-N', str(inode_num)])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-E', 'hash_seed=' + uu])
+ cmd.append(img_file)
+ cmd.append(str(size_in_mb) + 'M')
+ with tempfile.NamedTemporaryFile(dir=work_dir, suffix="mke2fs.conf") as conf_file:
+ conf_data = pkgutil.get_data('apexer', 'mke2fs.conf')
+ conf_file.write(conf_data)
+ conf_file.flush()
+ RunCommand(cmd, args.verbose,
+ {"MKE2FS_CONFIG": conf_file.name, 'E2FSPROGS_FAKE_TIME': '1'})
+
+ # Compile the file context into the binary form
+ compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin')
+ cmd = ['sefcontext_compile']
+ cmd.extend(['-o', compiled_file_contexts])
+ cmd.append(args.file_contexts)
+ RunCommand(cmd, args.verbose)
+
+ # Add files to the image file
+ cmd = ['e2fsdroid']
+ cmd.append('-e') # input is not android_sparse_file
+ cmd.extend(['-f', args.input_dir])
+ cmd.extend(['-T', '0']) # time is set to epoch
+ cmd.extend(['-S', compiled_file_contexts])
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.append('-s') # share dup blocks
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ cmd = ['e2fsdroid']
+ cmd.append('-e') # input is not android_sparse_file
+ cmd.extend(['-f', manifests_dir])
+ cmd.extend(['-T', '0']) # time is set to epoch
+ cmd.extend(['-S', compiled_file_contexts])
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.append('-s') # share dup blocks
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ # Resize the image file to save space
+ cmd = ['resize2fs']
+ cmd.append('-M') # shrink as small as possible
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ elif args.payload_fs_type == 'f2fs':
+ # F2FS requires a ~100M minimum size (necessary for ART, could be reduced a bit for other)
+ # TODO(b/158453869): relax these requirements for readonly devices
+ size_in_mb += 100
+
+ # Create an empty image
+ cmd = ['/usr/bin/fallocate']
+ cmd.extend(['-l', str(size_in_mb)+'M'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose)
+
+ # Format the image to F2FS
+ cmd = ['make_f2fs']
+ cmd.extend(['-g', 'android'])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-T', '0'])
+ cmd.append('-r') # sets checkpointing seed to 0 to remove random bits
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose)
- # Add files to the image file
- cmd = ['e2fsdroid']
- cmd.append('-e') # input is not android_sparse_file
- cmd.extend(['-f', args.input_dir])
- cmd.extend(['-T', '0']) # time is set to epoch
- cmd.extend(['-S', compiled_file_contexts])
- cmd.extend(['-C', args.canned_fs_config])
- cmd.append('-s') # share dup blocks
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
-
- cmd = ['e2fsdroid']
- cmd.append('-e') # input is not android_sparse_file
- cmd.extend(['-f', manifests_dir])
- cmd.extend(['-T', '0']) # time is set to epoch
- cmd.extend(['-S', compiled_file_contexts])
- cmd.extend(['-C', args.canned_fs_config])
- cmd.append('-s') # share dup blocks
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
-
- # Resize the image file to save space
- cmd = ['resize2fs']
- cmd.append('-M') # shrink as small as possible
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+ # Add files to the image
+ cmd = ['sload_f2fs']
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-f', manifests_dir])
+ cmd.extend(['-s', args.file_contexts])
+ cmd.extend(['-T', '0'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, expected_return_values={0,1})
+
+ cmd = ['sload_f2fs']
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-f', args.input_dir])
+ cmd.extend(['-s', args.file_contexts])
+ cmd.extend(['-T', '0'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, expected_return_values={0,1})
+
+ # TODO(b/158453869): resize the image file to save space
if args.unsigned_payload_only:
shutil.copyfile(img_file, args.output)
diff --git a/apexer/apexer_test.py b/apexer/apexer_test.py
index 176b994c..7fec4ec5 100644
--- a/apexer/apexer_test.py
+++ b/apexer/apexer_test.py
@@ -21,6 +21,7 @@ import logging
import os
import re
import shutil
+import stat
import subprocess
import tempfile
import unittest
@@ -161,6 +162,7 @@ DEBUG_TEST = False
class ApexerRebuildTest(unittest.TestCase):
def setUp(self):
self._to_cleanup = []
+ self._get_host_tools(os.path.join(get_current_dir(), "apexer_test_host_tools.zip"))
def tearDown(self):
if not DEBUG_TEST:
@@ -173,6 +175,37 @@ class ApexerRebuildTest(unittest.TestCase):
else:
print(self._to_cleanup)
+ def _get_host_tools(self, host_tools_file_path):
+ dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_host_tools_")
+ self._to_cleanup.append(dir_name)
+ if os.path.isfile(host_tools_file_path):
+ with ZipFile(host_tools_file_path, 'r') as zip_obj:
+ zip_obj.extractall(path=dir_name)
+
+ files = {}
+ for i in ["apexer", "deapexer", "avbtool", "mke2fs", "sefcontext_compile", "e2fsdroid",
+ "resize2fs", "soong_zip", "aapt2", "merge_zips", "zipalign", "debugfs_static",
+ "signapk.jar", "android.jar"]:
+ file_path = os.path.join(dir_name, "bin", i)
+ if os.path.exists(file_path):
+ os.chmod(file_path, stat.S_IRUSR | stat.S_IXUSR);
+ files[i] = file_path
+ else:
+ files[i] = i
+ self.host_tools = files
+ self.host_tools_path = os.path.join(dir_name, "bin")
+
+ path = os.path.join(dir_name, "bin")
+ if "PATH" in os.environ:
+ path += ":" + os.environ["PATH"]
+ os.environ["PATH"] = path
+
+ ld_library_path = os.path.join(dir_name, "lib64")
+ if "LD_LIBRARY_PATH" in os.environ:
+ ld_library_path += ":" + os.environ["LD_LIBRARY_PATH"]
+ if "ANDROID_HOST_OUT" in os.environ:
+ ld_library_path += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64")
+ os.environ["LD_LIBRARY_PATH"] = ld_library_path
def _get_container_files(self, apex_file_path):
dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_container_files_")
@@ -216,7 +249,8 @@ class ApexerRebuildTest(unittest.TestCase):
def _extract_payload(self, apex_file_path):
dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_extracted_payload_")
self._to_cleanup.append(dir_name)
- cmd = ["deapexer", "extract", apex_file_path, dir_name]
+ cmd = ["deapexer", "--debugfs_path", self.host_tools["debugfs_static"],
+ "extract", apex_file_path, dir_name]
run_host_command(cmd)
# Remove payload files added by apexer and e2fs tools.
@@ -235,9 +269,13 @@ class ApexerRebuildTest(unittest.TestCase):
if unsigned_payload_only or "--payload_only" in args:
payload_only = True
- os.environ["APEXER_TOOL_PATH"] = (
- "out/soong/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin")
+ os.environ["APEXER_TOOL_PATH"] = (self.host_tools_path +
+ ":out/soong/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin")
cmd = ["apexer", "--force", "--include_build_info", "--do_not_check_keyname"]
+ if DEBUG_TEST:
+ cmd.append('-v')
+ cmd.extend(["--apexer_tool_path", os.environ["APEXER_TOOL_PATH"]])
+ cmd.extend(["--android_jar_path", self.host_tools["android.jar"]])
cmd.extend(["--manifest", container_files["apex_manifest.pb"]])
if "apex_manifest.json" in container_files:
cmd.extend(["--manifest_json", container_files["apex_manifest.json"]])
@@ -261,14 +299,35 @@ class ApexerRebuildTest(unittest.TestCase):
run_host_command(cmd)
return fn
+ def _get_java_toolchain(self):
+ java_toolchain = "java"
+ if os.path.isfile("prebuilts/jdk/jdk11/linux-x86/bin/java"):
+ java_toolchain = "prebuilts/jdk/jdk11/linux-x86/bin/java"
+ elif "ANDROID_JAVA_TOOLCHAIN" in os.environ:
+ java_toolchain = os.path.join(os.environ["ANDROID_JAVA_TOOLCHAIN"], "java")
+ elif "ANDROID_JAVA_HOME" in os.environ:
+ java_toolchain = os.path.join(os.environ["ANDROID_JAVA_HOME"], "bin", "java")
+ elif "JAVA_HOME" in os.environ:
+ java_toolchain = os.path.join(os.environ["JAVA_HOME"], "bin", "java")
+
+ java_dep_lib = os.environ["LD_LIBRARY_PATH"]
+ if "ANDROID_HOST_OUT" in os.environ:
+ java_dep_lib += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64")
+ if "ANDROID_BUILD_TOP" in os.environ:
+ java_dep_lib += ":" + os.path.join(os.environ["ANDROID_BUILD_TOP"],
+ "out/soong/host/linux-x86/lib64")
+
+ return [java_toolchain, java_dep_lib]
+
def _sign_apk_container(self, unsigned_apex):
fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=".apex")
os.close(fd)
self._to_cleanup.append(fn)
+ java_toolchain, java_dep_lib = self._get_java_toolchain()
cmd = [
- "prebuilts/jdk/jdk11/linux-x86/bin/java",
- "-Djava.library.path=out/soong/host/linux-x86/lib64",
- "-jar", "out/soong/host/linux-x86/framework/signapk.jar",
+ java_toolchain,
+ "-Djava.library.path=" + java_dep_lib,
+ "-jar", self.host_tools['signapk.jar'],
"-a", "4096",
os.path.join(get_current_dir(), TEST_X509_KEY),
os.path.join(get_current_dir(), TEST_PK8_KEY),
diff --git a/apexer/runtests.sh b/apexer/runtests.sh
index 7a499c75..e2eacc90 100755
--- a/apexer/runtests.sh
+++ b/apexer/runtests.sh
@@ -28,10 +28,12 @@ m -j apexer
export APEXER_TOOL_PATH="${ANDROID_BUILD_TOP}/out/soong/host/linux-x86/bin:${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
PATH+=":${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
+for fs_type in ext4 f2fs
+do
input_dir=$(mktemp -d)
output_dir=$(mktemp -d)
-function finish {
+function cleanup {
sudo umount /dev/loop10
sudo losetup --detach /dev/loop10
@@ -39,7 +41,7 @@ function finish {
rm -rf ${output_dir}
}
-trap finish EXIT
+trap cleanup ERR
#############################################
# prepare the inputs
#############################################
@@ -55,7 +57,7 @@ ln -s file1 ${input_dir}/sym1
manifest_dir=$(mktemp -d)
manifest_file=${manifest_dir}/apex_manifest.pb
echo '{"name": "com.android.example.apex", "version": 1}' > ${manifest_dir}/apex_manifest.json
-${ANDROID_HOST_OUT}/bin/conv_apex_manifest proto ${manifest_dir}/apex_manifest.json -o ${manifest_file}
+${ANDROID_BUILD_TOP}/out/soong/host/linux-x86/bin/conv_apex_manifest proto ${manifest_dir}/apex_manifest.json -o ${manifest_file}
# Create the file_contexts file
file_contexts_file=$(mktemp)
@@ -82,7 +84,9 @@ output_file=${output_dir}/test.apex
${ANDROID_HOST_OUT}/bin/apexer --verbose --manifest ${manifest_file} \
--file_contexts ${file_contexts_file} \
--canned_fs_config ${canned_fs_config_file} \
+ --payload_fs_type ${fs_type} \
--key ${ANDROID_BUILD_TOP}/system/apex/apexer/testdata/com.android.example.apex.pem \
+ --android_jar_path ${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar \
${input_dir} ${output_file}
#############################################
@@ -131,4 +135,8 @@ sudo diff ${input_dir}/sub/file3 ${output_dir}/mnt/sub/file3
# check the android manifest
aapt dump xmltree ${output_file} AndroidManifest.xml
-echo Passed
+echo "Passed for ${fs_type}"
+cleanup
+done
+
+echo "Passed for all fs types"
diff --git a/apexer/testdata/Android.bp b/apexer/testdata/Android.bp
index 5f72dc9b..951f849e 100644
--- a/apexer/testdata/Android.bp
+++ b/apexer/testdata/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_app_certificate {
name: "com.android.example.apex.certificate",
// This will use com.android.my.apex.x509.pem (the cert) and
@@ -33,6 +37,8 @@ apex {
key: "com.android.example.apex.key",
certificate: ":com.android.example.apex.certificate",
installable: false,
+ updatable:false,
+ generate_hashtree: false,
}
apex {
@@ -44,6 +50,7 @@ apex {
certificate: ":com.android.example.apex.certificate",
min_sdk_version: "29",
installable: false,
+ generate_hashtree: false,
}
apex {
@@ -55,6 +62,7 @@ apex {
certificate: ":com.android.example.apex.certificate",
installable: false,
logging_parent: "foobar",
+ updatable: false,
}
apex {
@@ -66,4 +74,5 @@ apex {
certificate: ":com.android.example.apex.certificate",
installable: false,
package_name: "com.android.overridden.example.apex",
+ updatable: false,
}
diff --git a/docs/README.md b/docs/README.md
index 5ba0ab9c..64867a2b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,15 +1,19 @@
# APEX File Format
-Android Pony EXpress (APEX) is a container format introduced in Android Q
-that is used in the install flow for lower-level system
-modules. This format facilitates the updates of system components that don't fit
-into the standard Android application model. Some example components are native
-services and libraries, hardware abstraction layers
+Android Pony EXpress (APEX) is a container format introduced in Android Q that
+is used in the install flow for lower-level system modules. This format
+facilitates the updates of system components that don't fit into the standard
+Android application model. Some example components are native services and
+libraries, hardware abstraction layers
([HALs](/https://source.android.com/devices/architecture/hal-types)), runtime
([ART](/https://source.android.com/devices/tech/dalvik)), and class libraries.
The term "APEX" can also refer to an APEX file.
+This document describes technical details of the APEX file format. If you are
+looking at how to build an APEX package, kindly refer to [this how-to](howto.md)
+document.
+
## Background
Although Android supports updates of modules that fit within the standard app
@@ -29,7 +33,7 @@ has the following drawbacks:
This section describes the high-level design of the APEX file format and the
APEX manager, which is a service that manages APEX files.
-### APEX format
+### APEX format {#apex-format}
This is the format of an APEX file.
@@ -55,8 +59,8 @@ infrastructure such as ADB, PackageManager, and package installer apps (such as
Play Store). For example, the APEX file can use an existing tool such as `aapt`
to inspect basic metadata from the file. The file contains package name and
version information. This information is generally also available in
-`apex_manifest.json`. `AndroidManifest.xml` might contain additional
-targeting information that can be used by the existing app publishing tools.
+`apex_manifest.json`. `AndroidManifest.xml` might contain additional targeting
+information that can be used by the existing app publishing tools.
`apex_manifest.json` is recommended over `AndroidManifest.xml` for new code and
systems that deal with APEX.
@@ -68,16 +72,16 @@ metadata block are created using libavb. The file system payload isn't parsed
inside the `apex_payload.img` file.
`apex_pubkey` is the public key used to sign the file system image. At runtime,
-this key ensures that the downloaded APEX is signed with the same entity
-that signs the same APEX in the built-in partitions.
+this key ensures that the downloaded APEX is signed with the same entity that
+signs the same APEX in the built-in partitions.
### APEX manager
-The APEX manager (or `apexd`) is a native daemon responsible for
-verifying, installing, and uninstalling APEX files. This process is launched and
-is ready early in the boot sequence. APEX files are normally pre-installed on
-the device under `/system/apex`. The APEX manager defaults to using these
-packages if no updates are available.
+The APEX manager (or `apexd`) is a native daemon responsible for verifying,
+installing, and uninstalling APEX files. This process is launched and is ready
+early in the boot sequence. APEX files are normally pre-installed on the device
+under `/system/apex`. The APEX manager defaults to using these packages if no
+updates are available.
The update sequence of an APEX uses the
[PackageManager class](https://developer.android.com/reference/android/content/pm/PackageManager)
@@ -132,17 +136,17 @@ The APEX format supports these file types:
- Data files
- Config files
-The APEX format can only update some of these file types. Whether a file
-type can be updated depends on the platform and how stable the interfaces for
-the files types are defined.
+The APEX format can only update some of these file types. Whether a file type
+can be updated depends on the platform and how stable the interfaces for the
+files types are defined.
### Signing
APEX files are signed in two ways. First, the `apex_payload.img` (specifically,
the vbmeta descriptor appended to `apex_payload.img`) file is signed with a key.
Then, the entire APEX is signed using the
-[APK signature scheme v3](/https://source.android.com/security/apksigning/v3). Two different keys are used
-in this process.
+[APK signature scheme v3](/https://source.android.com/security/apksigning/v3).
+Two different keys are used in this process.
On the device side, a public key corresponding to the private key used to sign
the vbmeta descriptor is installed. The APEX manager uses the public key to
@@ -152,9 +156,8 @@ different keys and is enforced both at build time and runtime.
### APEX in built-in partitions
APEX files can be located in built-in partitions such as `/system`. The
-partition is
-already over dm-verity, so the APEX files are mounted directly over the loop
-device.
+partition is already over dm-verity, so the APEX files are mounted directly over
+the loop device.
If an APEX is present in a built-in partition, the APEX can be updated by
providing an APEX package with the same package name and a higher version code.
@@ -165,18 +168,18 @@ the newer version of the APEX is only activated after reboot.
## Kernel requirements
To support APEX mainline modules on an Android device, the following Linux
-kernel features are required: the loop driver and dm-verity. The loop
-driver mounts the file system image in an APEX module and dm-verity verifies the
-APEX module.
+kernel features are required: the loop driver and dm-verity. The loop driver
+mounts the file system image in an APEX module and dm-verity verifies the APEX
+module.
-The performance of the loop driver and dm-verity is important in achieving
-good system performance when using APEX modules.
+The performance of the loop driver and dm-verity is important in achieving good
+system performance when using APEX modules.
### Supported kernel versions
APEX mainline modules are supported on devices using kernel versions 4.4 or
-higher. New devices launching with Android Q or higher
-must use kernel version 4.9 or higher to support APEX modules.
+higher. New devices launching with Android Q or higher must use kernel version
+4.9 or higher to support APEX modules.
### Required kernel patches
@@ -187,10 +190,9 @@ of the Android common tree.
#### Kernel version 4.4
This version is only supported for devices that are upgraded from Android 9 to
-Android Q and want to support APEX modules. To get the
-required patches, a down-merge from the `android-4.4` branch is strongly
-recommended. The following is a list of the required individual patches
-for kernel version 4.4.
+Android Q and want to support APEX modules. To get the required patches, a
+down-merge from the `android-4.4` branch is strongly recommended. The following
+is a list of the required individual patches for kernel version 4.4.
- UPSTREAM: loop: add ioctl for changing logical block size
([4.4](https://android-review.googlesource.com/c/kernel/common/+/777013){: .external})
@@ -216,9 +218,9 @@ the `android-common` branch.
### Required kernel configuration options
-The following list shows the base configuration requirements for supporting
-APEX modules that were introduced in Android Q. The
-items with an asterisk (\*) are existing requirements from Android 9 and lower.
+The following list shows the base configuration requirements for supporting APEX
+modules that were introduced in Android Q. The items with an asterisk (\*) are
+existing requirements from Android 9 and lower.
```
(*) CONFIG_AIO=Y # AIO support (for direct I/O on loop devices)
@@ -242,8 +244,8 @@ requirements.
Note: Because the implementation details for APEX are still under development,
the content in this section is subject to change.
-This section describes how to build an APEX using the Android build system.
-The following is an example of `Android.bp` for an APEX named `apex.test`.
+This section describes how to build an APEX using the Android build system. The
+following is an example of `Android.bp` for an APEX named `apex.test`.
```
apex {
@@ -280,12 +282,12 @@ apex {
#### File types and locations in APEX
-|File type |Location in APEX |
-|-------------------|--------------------------------------------------------------|
-|Shared libraries |`/lib` and `/lib64` (`/lib/arm` for translated arm in x86) |
-|Executables |`/bin` |
-|Java libraries |`/javalib` |
-|Prebuilts |`/etc` |
+File type | Location in APEX
+---------------- | ----------------------------------------------------------
+Shared libraries | `/lib` and `/lib64` (`/lib/arm` for translated arm in x86)
+Executables | `/bin`
+Java libraries | `/javalib`
+Prebuilts | `/etc`
### Transitive dependencies
@@ -376,6 +378,7 @@ $ avbtool extract_public_key --key foo.pem --output foo.avbpubkey
```
In Android.bp:
+
```
apex_key {
name: "apex.test.key",
@@ -507,21 +510,21 @@ kernel to fully support APEX. For example, the kernel might have been built
without `CONFIG_BLK_DEV_LOOP=Y`, which is crucial for mounting the file system
image inside an APEX.
-Flattened APEX is a specially built APEX that can be activated on devices with
-a legacy kernel. Files in a flattened APEX are directly installed to a directory
+Flattened APEX is a specially built APEX that can be activated on devices with a
+legacy kernel. Files in a flattened APEX are directly installed to a directory
under the built-in partition. For example, `lib/libFoo.so` in a flattend APEX
`my.apex` is installed to `/system/apex/my.apex/lib/libFoo.so`.
Activating a flattened APEX doesn't involve the loop device. The entire
directory `/system/apex/my.apex` is directly bind-mounted to `/apex/name@ver`.
-Flattened APEXs can't be updated by downloading updated versions
-of the APEXs from network because the downloaded APEXs can't be flattened.
-Flattened APEXs can be updated only via a regular OTA.
+Flattened APEXs can't be updated by downloading updated versions of the APEXs
+from network because the downloaded APEXs can't be flattened. Flattened APEXs
+can be updated only via a regular OTA.
Note that flattened APEX is the default configuration for now. This means all
-APEXes are by default flattened unless you explicitly configure your device
-to support updatable APEX (explained above).
+APEXes are by default flattened unless you explicitly configure your device to
+support updatable APEX (explained above).
Also note that, mixing flattened and non-flattened APEXes in a device is NOT
supported. It should be either all non-flattened or all flattened. This is
@@ -530,22 +533,168 @@ like Mainline. APEXes that are not pre-signed (i.e. built from the source)
should also be non-flattened and signed with proper keys in that case. The
device should inherit from `updatable_apex.mk` as explained above.
+## Compressed apexes {#compressed-apex}
+
+APEX compression is a new feature introduced in Android S. Its main purpose is
+to reduce the storage impact of updatable APEX packages: after an update to an
+APEX is installed, its pre-installed version is not used anymore, and space that
+is taken by it effectively becomes a dead weight.
+
+APEX compression minimizes the storage impact by using a highly-compressed
+set of APEX files on read-only partitions (e.g. `/system`). In Android S a
+DEFLATE zip compression is used.
+
+Note: compression doesn't provide any optimization in the following scenarios:
+
+* Bootstrap apexes that are required to be mounted very early in the boot
+ sequence. List of bootstrap apexes is configured in `kBootstrapApexes`
+ constant in `system/apex/apexd/apexd.cpp`.
+* Non-updatable apexes. Compression is only beneficial in case an updated
+ version of an apex is installed on `/data partition`.
+ Full list of updatable apexes is available at
+ https://source.android.com/devices/architecture/modular-system.
+* Dynamic shared libs apexes. Since `apexd` will always activate both versions
+ of such apexes (pre-installed and upgraded), compressing them doesn't provide
+ any value.
+
+### Compressed APEX file format
+
+This is the format of a compressed APEX file.
+
+![Compressed APEX file format](compressed-apex-format.png)
+
+**Figure 2.** Compressed APEX file format
+
+At the top level, a compressed APEX file is a zip file containing the original apex in deflated
+form with compression level of 9 and other files stored uncompressed.
+
+The four files in an APEX file are:
+
+* `original_apex`: deflated with compression level of 9
+* `apex_manifest.pb`: stored only
+* `AndroidManifest.xml`: stored only
+* `apex_pubkey`: stored only
+
+
+`original_apex` is the original uncompressed [APEX file](#apex-format).
+
+`apex_manifest.pb` `AndroidManifest.xml` `apex_pubkey` are copies of the
+corresponding files from `original_apex`.
+
+
+### Building compressed apex
+
+Compressed apex can be built using `apex_compression_tool.py` located at
+`system/apex/tools`.
+
+Note: the outer apk container of the produced compressed apex file won't be
+automatically signed. You will need to manually sign it with using the correct
+certificate. See [Signing Builds for Release](
+https://source.android.com/devices/tech/ota/sign_builds#apex-signing-key-replacement).
+
+There are a few different parameters related to APEX compression available in
+the build system.
+
+In `Android.bp` whether an apex is compressible is controlled by `compressible`
+property:
+
+```
+apex {
+ name: "apex.test",
+ manifest: "apex_manifest.json",
+ file_contexts: "file_contexts",
+ compressible: true,
+}
+```
+
+Note: this only serves as a hint to build system that this apex can be
+compressed. Such property is required due to the fact that not all apexes are
+compressible as mentioned in the [section above](#compressed-apex).
+
+TODO(b/183208430): add docs on how this works for prebuilts.
+
+A `PRODUCT_COMPRESSED_APEX` product flag is used to control whether a system
+image built from source should contain compressed apexes or not.
+
+For local experimentation you can force a build to compress apexes by setting
+`OVERRIDE_PRODUCT_COMPRESSED_APEX=true`.
+
+Compressed APEX files generated by the build system will have `.capex`
+extension. It makes it easier to distinguish between compressed and uncompressed
+versions of an APEX.
+
+### Supported compression algorithms
+
+Android S only supports deflate zip compression.
+
+### Activating compressed apex during boot
+
+Before activating a compressed APEX, `original_apex` inside it will be
+decompressed into `/data/apex/decompressed` directory. The resulting
+decompressed APEX will be hard linked to the `/data/apex/active` directory.
+
+Note: because of the hard link step above, it's important that files under
+`/data/apex/decompressed` have the same SELinux label as files under
+`/data/apex/active`.
+
+Consider following example as an illustration of the process described above.
+
+Let's assume that `/system/apex/com.android.foo.capex` is a compressed APEX
+being activated, and it's `versionCode` is `37`.
+
+1. First `original_apex` inside `/system/apex/com.android.foo.capex` is
+ decompressed into `/data/apex/decompressed/com.android.foo@37.apex`.
+2. After that `restorecon /data/apex/decompressed/com.android.foo@37.apex` is
+ performed to make sure that it has a correct SELinux label.
+3. Verification checks are performed on
+ `/data/apex/decompressed/com.android.foo@37.apex` to ensure it's validity:
+ * `apexd` checks that public key bundled in
+ `/data/apex/decompressed/com.android.foo@37.apex` is equal to the one
+ bundled in `/system/apex/com.android.foo.capex`
+4. Next `/data/apex/decompressed/com.android.foo@37.apex` is hard linked to
+ `/data/apex/active/com.android.foo@37.apex`.
+5. Finally, regular activation logic for uncompressed APEX files is performed
+ for `/data/apex/active/com.android.foo@37.apex`.
+
+For more information see implementation of `OnStart` function in
+`system/apex/apexd/apexd.cpp`.
+
+### Interaction with OTA
+
+Compressed APEX files have some implications on the OTA delivery and
+application. Since an OTA might contain a compressed APEX file with higher
+version compared to what is currently active on the device, some free space must
+be reserved before rebooting a device to apply an OTA.
+
+To help OTA system, two new binder APIs are exposed by apexd:
+
+* `calculateSizeForCompressedApex` - calculates size required for decompressing
+ APEX files in OTA package. It can be used to check if device has enough space
+ before downloading an OTA.
+* `reserveSpaceForCompressedApex` - reserves space on the disk that in the
+ future will be used by apexd for decompression of compressed APEX files inside
+ the OTA package.
+
+
+In case of A/B OTA, `apexd` will attempt decompression in the background as part
+of the postinstall OTA routine. If decompression fails, `apexd` will fallback to
+decompressing during the boot that applies the OTA.
+
## Alternatives considered when developing APEX
-Here are some options that we considered when designing the APEX file
-format, and why we included or excluded them.
+Here are some options that we considered when designing the APEX file format,
+and why we included or excluded them.
### Regular package management systems
-Linux distributions have package management systems like `dpkg` and `rpm`,
-which are powerful, mature and robust. However, they weren't
-adopted for APEX because they can't protect the packages after
-installation. Verification is done only when packages are being installed.
-Attackers can break the integrity of the installed packages unnoticed. This is
-a regression for Android where all system components were stored in read-only
-file systems whose integrity is protected by dm-verity for every I/O. Any
-tampering to system components must be prohibited, or be detectable so that
-the device can refuse to boot if compromised.
+Linux distributions have package management systems like `dpkg` and `rpm`, which
+are powerful, mature and robust. However, they weren't adopted for APEX because
+they can't protect the packages after installation. Verification is done only
+when packages are being installed. Attackers can break the integrity of the
+installed packages unnoticed. This is a regression for Android where all system
+components were stored in read-only file systems whose integrity is protected by
+dm-verity for every I/O. Any tampering to system components must be prohibited,
+or be detectable so that the device can refuse to boot if compromised.
### dm-crypt for integrity
@@ -573,15 +722,15 @@ partition, they were accessible via paths such as `/system/lib/libfoo.so`. A
client of an APEX file (other APEX files or the platform) should use the new
paths. This change in paths might require updates to the existing code.
-One way to avoid the path change is to overlay the file contents in an APEX
-file over the `/system` partition. However, we decided not to overlay files over
-the `/system` partition because we believed this would negatively affect
-performance as the number of files being overlayed (possibly even stacked one
-after another) increases.
+One way to avoid the path change is to overlay the file contents in an APEX file
+over the `/system` partition. However, we decided not to overlay files over the
+`/system` partition because we believed this would negatively affect performance
+as the number of files being overlayed (possibly even stacked one after another)
+increases.
Another option was to hijack file access functions such as `open`, `stat`, and
`readlink`, so that paths that start with `/system` are redirected to their
corresponding paths under `/apex`. We discarded this option because it's
-practically infeasible to change all functions that accept paths. For
-example, some apps statically link Bionic, which implements the functions. In
-that case, the redirection won't happen for the app.
+practically infeasible to change all functions that accept paths. For example,
+some apps statically link Bionic, which implements the functions. In that case,
+the redirection won't happen for the app.
diff --git a/docs/compressed-apex-format.png b/docs/compressed-apex-format.png
new file mode 100644
index 00000000..d67efcbc
--- /dev/null
+++ b/docs/compressed-apex-format.png
Binary files differ
diff --git a/docs/howto.md b/docs/howto.md
new file mode 100644
index 00000000..a7042493
--- /dev/null
+++ b/docs/howto.md
@@ -0,0 +1,556 @@
+# How To APEX
+
+[go/android-apex-howto](http://go/android-apex-howto) (internal link)
+
+This doc reflects the current implementation status, and thus is expected to
+change regularly.
+
+## Reference
+
+To understand the design rationale, visit this
+[public doc](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/docs/README.md#alternatives-considered-when-developing-apex)
+and [go/android-apex](http://go/android-apex) (internal).
+
+## Building an APEX
+
+A cheat sheet:
+
+```
+apex {
+ name: "com.android.my.apex",
+
+ manifest: "apex_manifest.json",
+
+ // optional. if unspecified, a default one is auto-generated
+ androidManifest: "AndroidManifest.xml",
+
+ // libc.so and libcutils.so are included in the apex
+ native_shared_libs: ["libc", "libcutils"],
+ binaries: ["vold"],
+ java_libs: ["core-all"],
+ apps: ["myapk"],
+ prebuilts: ["my_prebuilt"],
+
+ compile_multilib: "both",
+
+ key: "com.android.my.apex.key",
+ certificate: ":com.android.my.apex.certificate",
+}
+```
+
+`apex_manifest.json` should look like:
+
+```
+{
+ "name": "com.android.my.apex",
+ "version": 1
+}
+```
+
+The file contexts files should be created at
+`/system/sepolicy/apex/com.android.my.apex-file_contexts`:
+
+```
+(/.*)? u:object_r:system_file:s0
+/sub(/.*)? u:object_r:sub_file:s0
+/sub/file3 u:object_r:file3_file:s0
+```
+
+The file should describe the contents of your apex. Note that the file is
+amended by the build system so that the `apexd` can access the root directory of
+your apex and the `apex_manifest.pb` file. (Technically, they are labeled as
+`system_file`.) So if you're
+[building the apex without Soong](#building-apex-without-soong), please be sure
+that `apexd` can access the root directory and the `apex_manifest.pb` file. (In
+the example above, the first line does that.)
+
+#### A script to create a skeleton of APEX
+
+For convenience, you might want to use a
+[script](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/tools/create_apex_skeleton.sh)
+that creates a skeleton (`Android.bp`, keys, etc.) of an APEX for you. You only
+need to adjust the `APEX_NAME` variable to be your actual APEX name.
+
+#### File types and places where they are installed in apex
+
+file type | place in apex
+-------------- | ----------------------------------------------------------
+shared libs | `/lib` and `/lib64` (`/lib/arm` for translated arm in x86)
+executables | `/bin`
+java libraries | `/javalib`
+android apps | `/app` or `/priv-app`
+prebuilts | `/etc`
+
+### Transitive dependencies
+
+Transitive dependencies of a native shared lib or an executable are
+automatically included in the APEX. For example, if `libFoo` depends on
+`libBar`, then the two libs are included even when only `libFoo` is listed in
+`native_shared_libs` property.
+
+However, if a transitive dependency has a stable ABI, it is not included
+transitively. It can be included in an APEX only by directly being referenced.
+Currently (2019/08/05), the only module type that can provide stable ABI is
+`cc_library`. To do so, add `stubs.*` property as shown below:
+
+```
+cc_library {
+ name: "foo",
+ srcs: [...],
+ stubs: {
+ symbol_file: "foo.map.txt",
+ versions: ["29", "30"],
+ },
+}
+```
+
+Use this when a lib has to be accessed across the APEX boundary, e.g. between
+APEXes or between an APEX and the platform.
+
+### apex_available
+
+Any module that is “included” (not just referenced) in an APEX either via the
+direct dependency or the transitive dependency has to correctly set the
+`apex_available` property in its `Android.bp` file. The property can have one or
+more of the following values:
+
+* `<name_of_an_apex>`: Like `com.android.adbd`. By specifying the APEX names
+ explicitly, the module is guaranteed to be included in those APEXes. This is
+ useful when a module has to be kept as an implementation detail of an APEX
+ and therefore shouldn’t be used from outside.
+* `//apex_available:anyapex`: This means that the module can be included in
+ any APEX. This is useful for general-purpose utility libraries like
+ `libbase`, `libcutils`, etc.
+* `//apex_available:platform`: The module can be installed to the platform,
+ outside of APEXes. This is the default value. However, `if apex_available`
+ is set to either of `<name_of_an_apex` or `//apex_available:anyapex`, the
+ default is removed. If a module has to be included in both APEX and the
+ platform, `//apex_available:platform` and`//apex_available:anyapex` should
+ be specified together.
+
+The act of adding an APEX name to the `apex_available` property of a module has
+to be done or be reviewed by the author(s) of the module. Being included in an
+APEX means that the module will be portable, i.e., running on multiple versions
+of the current and previous platforms, whereas it usually was expected to run on
+the current (the up-to-date) platform. Therefore, the module might have to be
+prepared to not have version-specific dependencies to the platform, like the
+existence of a dev node, a system call, etc.
+
+### Handling multiple ABIs
+
+`compile_multilib`: specifies the ABI(s) that this APEX will compile native
+modules for. Can be either of `both`, `first`, `32`, `64`, `prefer32`. For most
+of the cases, this should be `both`.
+
+`native_shared_libs`: installed for **_both_** primary and secondary ABIs of the
+device. Of course, if the APEX is built for a target having single ABI (i.e.
+32-bit only or 64-bit only), only libraries with the corresponding ABI are
+installed.
+
+`binaries`: installed only for the **_primary_** ABI of the device. In other
+words,
+
+* If the device is 32-bit only, only the 32-bit variant of the binary is
+ installed.
+* If the device supports both 32/64 ABIs, but with
+ `TARGET_PREFER_32_BIT_EXECUTABLES=true`, then only the 32-bit variant of the
+ binary is installed.
+* If the device is 64-bit only, then only the 64-bit variant of the binary is
+ installed.
+* If the device supports both 32/64 ABIs, but without
+ `TARGET_PREFER_32_BIT_EXECUTABLES=true`, then only the 64-bit variant of the
+ binary is installed.
+
+In order to fine control the ABIs of the native libraries and binaries to be
+installed, use
+`multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries]`
+properties.
+
+* `first`: matches with the primary ABI of the device. This is the default for
+ `binaries`.
+* `lib32`: matches with the 32-bit ABI of the device, if supported
+* `lib64`: matches with the 64-bit ABI of the device, it supported
+* `prefer32`: matches with the 32-bit ABI of the device, if support. If 32-bit
+ ABI is not supported, it is matched with the 64-bit ABI.
+* `both`: matches with the both ABIs. This is the default for
+ `native_shared_libraries`.
+* `java libraries` and `prebuilts`: ABI-agnostic
+
+Example: (let’s assume that the device supports 32/64 and does not prefer32)
+
+```
+apex {
+ // other properties are omitted
+ compile_multilib: "both",
+ native_shared_libs: ["libFoo"], // installed for 32 and 64
+ binaries: ["exec1"], // installed for 64, but not for 32
+ multilib: {
+ first: {
+ native_shared_libs: ["libBar"], // installed for 64, but not for 32
+ binaries: ["exec2"], // same as binaries without multilib.first
+ },
+ both: {
+ native_shared_libs: ["libBaz"], // same as native_shared_libs without multilib
+ binaries: ["exec3"], // installed for 32 and 64
+ },
+ prefer32: {
+ native_shared_libs: ["libX"], // installed for 32, but not for 64
+ },
+ lib64: {
+ native_shared_libs: ["libY"], // installed for 64, but not for 32
+ },
+ },
+}
+```
+
+### APEX image signing
+
+**Note**: the APEX skeleton creation
+[script](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/tools/create_apex_skeleton.sh)
+automates this step.
+
+Each APEX must be signed with different keys. There is no concept of the
+platform key. `apexd` in the future might reject if multiple APEXes are signed
+with the same key. When a new key is needed, create a public-private key pair
+and make an `apex_key` module. Use `key` property to sign an APEX using the key.
+The public key is included in the zip container of the APEX as a file entry
+`apex_pubkey`.
+
+How to generate the key pair:
+
+```
+# create an rsa key pair
+$ openssl genrsa -out com.android.my.apex.pem 4096
+
+# extract the public key from the key pair
+$ avbtool extract_public_key --key com.android.my.apex.pem \
+--output com.android.my.apex.avbpubkey
+
+# in Android.bp
+apex_key {
+ name: "com.android.my.apex.key",
+ public_key: "com.android.my.apex.avbpubkey",
+ private_key: "com.android.my.apex.pem",
+}
+```
+
+Important: In the above example, the name of the public key (that is
+`com.android.my.apex`) becomes the ID of the key. The ID of the key used to sign
+an APEX is recorded in the APEX. At runtime, a public key with the same ID in
+the device is used to verify the APEX.
+
+### APK (APEX container) signing
+
+**Note**: the APEX skeleton creation
+[script](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/tools/create_apex_skeleton.sh)
+automates this step.
+
+An APEX should also be signed just like APKs. So, an APEX is signed twice; once
+for the mini file system (`apex_payload.img` file) and once for the entire file.
+
+Just like APK, the file-level signing is done via the `certificate` property. It
+can be set in three ways.
+
+* not set: if unset, the APEX is signed with the certificate located at
+ `PRODUCT_DEFAULT_DEV_CERTIFICATE`. If the flag is also unset, it defaults to
+ `build/target/product/security/testkey`
+* `<name>`: the APEX is signed with the certificate named `<name>` in the same
+ directory as `PRODUCT_DEFAULT_DEV_CERTIFICATE`
+* `<name>`: the APEX signed with the certificate which is defined by a
+ Soong module named `<name>`. The certificate module can be defined as
+ follows.
+
+```
+android_app_certificate {
+ name: "com.android.my.apex.certificate",
+ // This will use com.android.my.apex.x509.pem (the cert) and
+ // com.android.my.apex.pk8 (the private key)
+ certificate: "com.android.my.apex",
+}
+```
+
+How to generate the certificate/private key pair:
+
+```
+# Create certificate and private in PEM form
+$ openssl req -x509 -newkey rsa:4096 -nodes -days 999999 -keyout key.pem -out com.android.my.apex.x509.pem
+
+# Enter following info via the interactive prompts
+# Country Name: US
+# State: California
+# Locality Name: Mountain View
+# Organization Name: Android
+# Organization Unit Name: Android
+# Common Name: <your-apk-name>
+# Email address: android@android.com
+
+# Convert the private to pkcs8 format
+$ openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out com.android.my.apex.pk8 -nocrypt
+```
+
+### Signing APEXs with release keys
+
+The procedures described in the [APEX image signing](#apex-image-signing) and
+[APK (APEX container) signing](#apk-apex-container_signing) sections require the
+private keys to be present in the tree. This is not suitable for public release.
+Please refer to the
+[APEX signing key replacement](https://source.android.com/devices/tech/ota/sign_builds#apex-signing-key-replacement)
+documentation to prepare the APEX packages for release.
+
+For the Google-specific procedure for release keys, the documentation is
+available at
+[go/android-apex-howto-internal](http://go/android-apex-howto-internal)
+(internal only).
+
+### Linker namespaces for native libraries and binaries
+
+The linker needs to be set up with separate namespaces for each APEX, for
+isolation. It is done through `ld.config.txt` files, which are autogenerated by
+`linkerconfig`. Normally you only need to ensure that the APEX manifest
+correctly lists the native libraries it requires (from platform or other APEXes)
+and provides, which by default is taken from the build system.
+
+Refer to the [design doc](go/linker-config-apex) for more information about
+linkerconfig and apex.
+
+## Installing an APEX
+
+Use
+
+```
+adb install --staged <path_to_apex> && adb reboot
+```
+
+The `adb install --staged` command triggers a verification for the staged APEX
+which might fail when the APEX is signed incorrectly.
+
+Note that on Q devices when the `adb install --staged` command completes you
+still will have to wait until the verification for the staged APEX is finished
+before issuing `adb reboot`.
+
+On R devices we added the `--wait` option to `adb install` to wait until the
+verification is completed before returning. On S devices the `--wait` option is
+implicit.
+
+## Hot swapping an APEX (development only)
+
+Use
+
+```
+adb sync && adb shell cmd -w apexservice remountPackages
+```
+
+Note that for this command to remount your APEX, you must ensure that all
+processes that have reference to your APEX are killed. E.g. if you are
+developing an APEX that contributes to system\_server, you can use the
+following:
+
+```
+adb root
+adb remount
+adb shell stop
+adb sync
+adb shell cmd -w apexservice remountPackages
+adb shell start
+```
+
+## Using an APEX
+
+After the reboot, the apex will be mounted at `/apex/<apex_name>@<version>`
+directory. Multiple versions of the same APEX can be mounted at the same time. A
+mount point that always points to the latest version of an APEX is provided:
+`/apex/<apex_name>`.
+
+Clients can use the latter path to read or execute something from APEX.
+
+So, typical usage of APEX is as follows.
+
+1. an APEX is pre-loaded under `/system/apex`when the device is shipped.
+2. Files in it are accessed via the `/apex/<apex_name>/`path.
+3. When an updated version of the APEX is installed in `/data/apex/active`, the
+ path will point to the new APEX after the reboot.
+
+## Updating service with APEX
+
+Using APEX, you can update a service. To do so, you need …
+
+1) Mark the service in system partition as updatable. Add the new option
+‘updatable’ to the service definition.
+
+```
+/system/etc/init/myservice.rc:
+
+service myservice /system/bin/myservice
+ class core
+ user system
+ …
+ updatable
+```
+
+2) Create a new `.rc` file for the updated service. Use ‘`override`’ option to
+redefine the existing service.
+
+```
+/apex/my.apex/etc/init.rc:
+
+service myservice /apex/my.apex/bin/myservice
+ class core
+ user system
+ …
+ override
+```
+
+Note that you can only have service definitions in the rc file in APEX. You
+cannot have action triggers in APEXes.
+
+Also note that if a service marked as updatable is started before APEXes are
+activated, the start is delayed until the activation of APEXes is finished.
+
+## Configuring system to support APEX updates
+
+Set the following system property to true to support APEX file updates.
+
+```
+<device.mk>:
+
+PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true
+
+BoardConfig.mk:
+TARGET_FLATTEN_APEX := false
+
+or just
+<device.mk>:
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk)
+```
+
+## Flattened APEX
+
+For legacy devices, it is sometimes impossible or infeasible to update the old
+kernel to fully support APEX. For example, the kernel might have been built
+without `CONFIG_BLK_DEV_LOOP=Y`, which is crucial for mounting the file system
+image inside an APEX.
+
+Flattened APEX is a specially built APEX that can be activated on devices with a
+legacy kernel. Files in a flattened APEX are directly installed to a directory
+under the built-in partition. For example, `lib/libFoo.so` in a flattened APEX
+my.apex is installed to `/system/apex/my.apex/lib/libFoo.so`.
+
+Activating a flattened APEX doesn't involve the loop device. The entire
+directory `/system/apex/my.apex` is directly bind-mounted to `/apex/name@ver`.
+
+Flattened APEXs can‘t be updated by downloading updated versions of the APEXs
+from network because the downloaded APEXs can’t be flattened. Flattened APEXs
+can be updated only via a regular OTA.
+
+Note that flattened APEX is the default configuration for now (2019/Aug). This
+means all APEXes are by default flattened unless you explicitly configure your
+device to support updatable APEX (explained above).
+
+Also note that, mixing flattened and non-flattened APEXes in a device is NOT
+supported. It should be either all non-flattened or all flattened. This is
+especially important when shipping pre-signed APEX prebuilts for the projects
+like Mainline. APEXes that are not pre-signed (i.e. built from the source)
+should also be non-flattened and signed with proper keys in that case. The
+device should inherit from `updatable_apex.mk` as explained above.
+
+## Building APEX without Soong
+
+An APEX can be built without relying on the build commands generated by Soong.
+
+1) Prepare following files:
+
+- APEX manifest file (in JSON)
+
+- AndroidManifest file (in XML, optional)
+
+- AVB private key
+
+- APK certificate (`*.x509.pem`)
+
+- APK private key (`*.pk8`)
+
+- `file_contexts` file
+
+- files to be packaged into the APEX
+
+2) Create `canned_fs_config` file
+
+It is a file that specifies access bits and uid/gid of each file in the APEX.
+
+```
+/ 1000 1000 0755
+/apex_manifest.json 1000 1000 0644
+/apex_manifest.pb 1000 1000 0644
+/file1 1000 1000 0644
+/file2 1000 1000 0644
+/dir 0 2000 0755
+/dir/file3 1000 1000 0644
+...
+```
+
+Note that ALL files AND directories must be specified. And don’t forget to have
+a line for `/`and `/apex_manifest.pb`. (`/apex_manifest.json` line is for
+Q-targeting modules)
+
+3) Invoke `apexer`
+
+```
+$ apexer \
+ --manifest <apex_manifest_file> \
+ --file_contexts <file_contexts_file> \
+ --canned_fs_config <canned_fs_config_file> \
+ --key <avb_private_key_file> \
+ --payload_type image \
+ --android_manifest <android_manifest_file> \
+ --override_apk_package_name com.google.foo \
+ <input_directory> \
+ <output_apex_file>
+```
+
+`--android_manifest` and -`-override_apk_package` are optional arguments and
+thus can be omitted if not needed.
+
+Note: The `<apex_manifest_file>` shouldn’t be under `<input_directory>`.
+
+4) Sign it
+
+`apexer` signs the `apex_payload.img` file only. The entire apex (which is a zip
+file) has to be signed with `Signapk`.
+
+```
+$ java \
+ -Djava.library.path=$(dirname out/soong/host/linux-x86/lib64/libconscrypt_openjdk_jni.so)\
+ -jar out/soong/host/linux-x86/framework/signapk.jar \
+ -a 4096 \
+ <apk_certificate_file> \
+ <apk_private_key_file> \
+ <unsigned_input_file> \
+ <signed_output_file>
+```
+
+This will sign the input file with the cert/privkey pairs to produce the output
+file.
+
+## Re-packaging an existing APEX
+
+If an APEX has been build by passing `--include_build_info` to `apexer` (this is
+the default when building via Soong), it will then include a file named
+`apex_build_info.pb` which will store as much information as possible about how
+the apex was built (see the `ApexBuildInfo` proto
+[definition](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/proto/apex_build_info.proto)
+for more info) with the exception of the signing keys.
+
+We also provide a tool named `deapexer` to extract the payload content of an
+APEX in a local directory.
+
+By using these tools, you can then adapt the procedure described in the
+[building the apex without Soong](#building-apex-without-soong) section and pass
+the `--build_info apex_build_info.pb` file where `apex_build_info.pb` contains
+all the build parameters that you would otherwise pass via flag to `apexer`.
+
+We do this programmatically in some unit test code to generate "unusual" APEX
+files, see for example
+[here](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/apexer/apexer_test.py)
+and
+[here](https://android.googlesource.com/platform/system/apex/+/refs/heads/master/tests/testdata/sharedlibs/build/shared_libs_repack.py).
diff --git a/library_linking_strategy.cc b/library_linking_strategy.cc
new file mode 100644
index 00000000..a56d92e2
--- /dev/null
+++ b/library_linking_strategy.cc
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+int main() { return 0; }
diff --git a/libs/libapexutil/Android.bp b/libs/libapexutil/Android.bp
index edf66f2f..5ba3181d 100644
--- a/libs/libapexutil/Android.bp
+++ b/libs/libapexutil/Android.bp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "libapexutil-deps",
static_libs: [
@@ -30,6 +34,10 @@ cc_library_static {
export_include_dirs: ["."],
srcs: ["apexutil.cpp"],
host_supported: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.runtime",
+ ],
}
cc_test {
@@ -45,4 +53,4 @@ cc_test {
"general-tests",
],
host_supported: true,
-} \ No newline at end of file
+}
diff --git a/libs/libapexutil/apexutil.cpp b/libs/libapexutil/apexutil.cpp
index e0b0787b..6203a3b4 100644
--- a/libs/libapexutil/apexutil.cpp
+++ b/libs/libapexutil/apexutil.cpp
@@ -18,6 +18,7 @@
#include "apexutil.h"
#include <dirent.h>
+#include <string.h>
#include <memory>
@@ -67,6 +68,8 @@ GetActivePackages(const std::string &apex_root) {
continue;
if (strchr(entry->d_name, '@') != nullptr)
continue;
+ if (strcmp(entry->d_name, "sharedlibs") == 0)
+ continue;
std::string apex_path = apex_root + "/" + entry->d_name;
auto manifest = ParseApexManifest(apex_path + "/apex_manifest.pb");
if (manifest.ok()) {
diff --git a/proto/Android.bp b/proto/Android.bp
index be9966a4..30b24e33 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_static {
name: "lib_apex_manifest_proto",
host_supported: true,
@@ -33,6 +37,10 @@ cc_library_static {
type: "lite",
},
srcs: ["apex_manifest.proto"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.runtime",
+ ],
}
python_library_host {
@@ -90,3 +98,19 @@ cc_library_static {
},
srcs: ["session_state.proto"],
}
+
+genrule {
+ name: "apex-protos",
+ tools: ["soong_zip"],
+ dist: {
+ targets: ["apexer_tools"],
+ },
+ srcs: [
+ "apex_manifest.proto",
+ "apex_build_info.proto",
+ ],
+ out: ["apex-protos.zip"],
+ cmd: "mkdir $(genDir)/protos && " +
+ "cp $(in) $(genDir)/protos && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/protos -D $(genDir)/protos",
+}
diff --git a/proto/apex_build_info.proto b/proto/apex_build_info.proto
index e25baa20..fd8a3499 100644
--- a/proto/apex_build_info.proto
+++ b/proto/apex_build_info.proto
@@ -48,4 +48,7 @@ message ApexBuildInfo {
// Value of --logging_parent passed at build time.
string logging_parent = 9;
+
+ // Value of --payload_fs_type passed at build time.
+ string payload_fs_type = 10;
}
diff --git a/proto/apex_manifest.proto b/proto/apex_manifest.proto
index dfbf4d23..22fb7d79 100644
--- a/proto/apex_manifest.proto
+++ b/proto/apex_manifest.proto
@@ -48,4 +48,35 @@ message ApexManifest {
// List of native libs which this apex uses from other apexes or system.
repeated string requireNativeLibs = 8;
+
+ // List of JNI libs.
+ // linkerconfig/libnativeloader use this field so that java libraries can
+ // load JNI libraries in the same apex.
+ // This is supposed to be filled by the build system with libraries which are
+ // marked as "is_jni: true" from the list of "native_shared_libs".
+ repeated string jniLibs = 9;
+
+ // List of libs required that are located in a shared libraries APEX.
+ // Format of the content is 'library:hash'.
+ // Example) libc++.so:83d8f50...
+ repeated string requireSharedApexLibs = 10;
+
+ // Whether this APEX provides libraries to be shared with other APEXs. This
+ // causes libraries contained in the APEX to be made available under
+ // /apex/sharedlibs .
+ bool provideSharedApexLibs = 11;
+
+ message CompressedApexMetadata {
+
+ // Valid only for compressed APEX. This field contains the root digest of
+ // the original_apex contained inside CAPEX.
+ string originalApexDigest = 1;
+ }
+
+ // Exists only for compressed APEX
+ CompressedApexMetadata capexMetadata = 12;
+
+ // Indicates that this APEX can be updated without rebooting device.
+ bool supportsRebootlessUpdate = 13;
}
+
diff --git a/proto/session_state.proto b/proto/session_state.proto
index 35319cf7..cebf0adb 100644
--- a/proto/session_state.proto
+++ b/proto/session_state.proto
@@ -58,4 +58,7 @@ message SessionState {
// The names of the apexes within this session. Only populated for sessions
// that have been activated.
repeated string apex_names = 9;
+
+ // Populated with error details when session fails to activate
+ string error_message = 10;
}
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 00000000..88750048
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,447 @@
+# This Pylint rcfile contains a best-effort configuration to uphold the
+# best-practices and style described in the Google Python style guide:
+# https://google.github.io/styleguide/pyguide.html
+#
+# Its canonical open-source location is:
+# https://google.github.io/styleguide/pylintrc
+
+[MASTER]
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=third_party
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Pickle collected data for later comparisons.
+persistent=no
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=4
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=abstract-method,
+ apply-builtin,
+ arguments-differ,
+ attribute-defined-outside-init,
+ backtick,
+ bad-option-value,
+ basestring-builtin,
+ buffer-builtin,
+ c-extension-no-member,
+ consider-using-enumerate,
+ cmp-builtin,
+ cmp-method,
+ coerce-builtin,
+ coerce-method,
+ delslice-method,
+ div-method,
+ duplicate-code,
+ eq-without-hash,
+ execfile-builtin,
+ file-builtin,
+ filter-builtin-not-iterating,
+ fixme,
+ getslice-method,
+ global-statement,
+ hex-method,
+ idiv-method,
+ implicit-str-concat-in-sequence,
+ import-error,
+ import-self,
+ import-star-module-level,
+ inconsistent-return-statements,
+ input-builtin,
+ intern-builtin,
+ invalid-str-codec,
+ locally-disabled,
+ long-builtin,
+ long-suffix,
+ map-builtin-not-iterating,
+ misplaced-comparison-constant,
+ missing-function-docstring,
+ metaclass-assignment,
+ next-method-called,
+ next-method-defined,
+ no-absolute-import,
+ no-else-break,
+ no-else-continue,
+ no-else-raise,
+ no-else-return,
+ no-init, # added
+ no-member,
+ no-name-in-module,
+ no-self-use,
+ nonzero-method,
+ oct-method,
+ old-division,
+ old-ne-operator,
+ old-octal-literal,
+ old-raise-syntax,
+ parameter-unpacking,
+ print-statement,
+ raising-string,
+ range-builtin-not-iterating,
+ raw_input-builtin,
+ rdiv-method,
+ reduce-builtin,
+ relative-import,
+ reload-builtin,
+ round-builtin,
+ setslice-method,
+ signature-differs,
+ standarderror-builtin,
+ suppressed-message,
+ sys-max-int,
+ too-few-public-methods,
+ too-many-ancestors,
+ too-many-arguments,
+ too-many-boolean-expressions,
+ too-many-branches,
+ too-many-instance-attributes,
+ too-many-locals,
+ too-many-nested-blocks,
+ too-many-public-methods,
+ too-many-return-statements,
+ too-many-statements,
+ trailing-newlines,
+ unichr-builtin,
+ unicode-builtin,
+ unnecessary-pass,
+ unpacking-in-except,
+ useless-else-on-loop,
+ useless-object-inheritance,
+ useless-suppression,
+ using-cmp-argument,
+ wrong-import-order,
+ xrange-builtin,
+ zip-builtin-not-iterating,
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]". This option is deprecated
+# and it will be removed in Pylint 2.0.
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[BASIC]
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=main,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
+
+# Regular expression matching correct function names
+function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
+
+# Regular expression matching correct variable names
+variable-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression matching correct constant names
+const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
+
+# Regular expression matching correct attribute names
+attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
+
+# Regular expression matching correct argument names
+argument-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression matching correct class names
+class-rgx=^_?[A-Z][a-zA-Z0-9]*$
+
+# Regular expression matching correct module names
+module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
+
+# Regular expression matching correct method names
+method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=10
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
+# lines made too long by directives to pytype.
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=(?x)(
+ ^\s*(\#\ )?<?https?://\S+>?$|
+ ^\s*(from\s+\S+\s+)?import\s+.+$)
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=yes
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=
+
+# Maximum number of lines in a module
+max-module-lines=99999
+
+# String used as indentation unit. The internal Google style guide mandates 2
+# spaces. Google's externaly-published style guide says 4, consistent with
+# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
+# projects (like TensorFlow).
+indent-string=' '
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=TODO
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=yes
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging,absl.logging,tensorflow.io.logging
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,
+ TERMIOS,
+ Bastion,
+ rexec,
+ sets
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant, absl
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+ __new__,
+ setUp
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+ _fields,
+ _replace,
+ _source,
+ _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls,
+ class_
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=StandardError,
+ Exception,
+ BaseException
diff --git a/shim/Android.bp b/shim/Android.bp
index 351e754f..b9952664 100644
--- a/shim/Android.bp
+++ b/shim/Android.bp
@@ -15,6 +15,10 @@
// TODO: consider removing _prebuilt suffix from module names and make use of
// 'prefer: true'
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
prebuilt_apex {
name: "com.android.apex.cts.shim.v1_prebuilt",
overrides: ["CtsShimPrebuilt", "CtsShimPrivPrebuilt"],
@@ -176,3 +180,43 @@ prebuilt_apex {
filename: "com.android.apex.cts.shim.v3.apex",
installable: false,
}
+
+prebuilt_apex {
+ name: "com.android.apex.cts.shim.v2_different_certificate_prebuilt",
+ arch: {
+ arm: {
+ src: "prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apex",
+ },
+ arm64: {
+ src: "prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apex",
+ },
+ x86: {
+ src: "prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apex",
+ },
+ x86_64: {
+ src: "prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apex",
+ },
+ },
+ filename: "com.android.apex.cts.shim.v2_different_certificate.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.cts.shim.v2_unsigned_apk_container_prebuilt",
+ arch: {
+ arm: {
+ src: "prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_apk_container.apex",
+ },
+ arm64: {
+ src: "prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_apk_container.apex",
+ },
+ x86: {
+ src: "prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_apk_container.apex",
+ },
+ x86_64: {
+ src: "prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_apk_container.apex",
+ },
+ },
+ filename: "com.android.apex.cts.shim.v2_unsigned_apk_container.apex",
+ installable: false,
+}
diff --git a/shim/build/Android.bp b/shim/build/Android.bp
index 2667984b..830cbe16 100644
--- a/shim/build/Android.bp
+++ b/shim/build/Android.bp
@@ -14,6 +14,10 @@
// Build rules to build shim apexes.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
genrule {
name: "com.android.apex.cts.shim.pem",
out: ["com.android.apex.cts.shim.pem"],
@@ -36,6 +40,27 @@ apex_key {
}
genrule {
+ name: "com.android.apex.cts.shim.debug.pem",
+ out: ["com.android.apex.cts.shim.debug.pem"],
+ cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+ name: "com.android.apex.cts.shim.debug.pubkey",
+ srcs: [":com.android.apex.cts.shim.debug.pem"],
+ out: ["com.android.apex.cts.shim.debug.pubkey"],
+ tools: ["avbtool"],
+ cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+ name: "com.android.apex.cts.shim.debug.key",
+ private_key: ":com.android.apex.cts.shim.debug.pem",
+ public_key: ":com.android.apex.cts.shim.debug.pubkey",
+ installable: false,
+}
+
+genrule {
name: "generate_hash_of_dev_null",
out: ["hash.txt"],
cmd: "sha512sum -b /dev/null | cut -d' ' -f1 | tee $(out)",
@@ -58,6 +83,7 @@ apex {
apps: ["CtsShim", "CtsShimPriv"],
installable: false,
allowed_files: "default_shim_allowed_list.txt",
+ updatable: false,
}
apex {
@@ -70,6 +96,19 @@ apex {
apps: ["CtsShim", "CtsShimPriv"],
installable: false,
allowed_files: "default_shim_allowed_list.txt",
+ updatable: false,
+}
+
+apex {
+ name: "com.android.apex.cts.shim.v2_sign_payload_with_different_key",
+ manifest: "manifest_v2.json",
+ androidManifest: "AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.cts.shim.debug.key",
+ prebuilts: ["hash_of_dev_null"],
+ installable: false,
+ allowed_files: "default_shim_allowed_list.txt",
+ updatable: false,
}
apex {
@@ -81,6 +120,7 @@ apex {
prebuilts: ["hash_of_dev_null"],
installable: false,
allowed_files: "default_shim_allowed_list.txt",
+ updatable: false,
}
apex {
@@ -93,7 +133,8 @@ apex {
apps: ["CtsShim", "CtsShimPriv"],
installable: false,
allowed_files: "default_shim_allowed_list.txt",
- test_only_no_hashtree: true,
+ generate_hashtree: false,
+ updatable: false,
}
apex {
@@ -107,6 +148,7 @@ apex {
installable: false,
allowed_files: "default_shim_allowed_list.txt",
test_only_unsigned_payload: true,
+ updatable: false,
}
override_apex {
@@ -138,6 +180,7 @@ apex {
key: "com.android.apex.cts.shim.key",
prebuilts: ["empty_hash"],
installable: false,
+ updatable: false,
}
prebuilt_etc {
@@ -155,6 +198,7 @@ apex {
key: "com.android.apex.cts.shim.key",
prebuilts: ["hash_of_dev_null", "apex_shim_additional_file"],
installable: false,
+ updatable: false,
}
prebuilt_etc {
@@ -173,6 +217,7 @@ apex {
key: "com.android.apex.cts.shim.key",
prebuilts: ["hash_of_dev_null", "apex_shim_additional_folder"],
installable: false,
+ updatable: false,
}
apex {
@@ -183,6 +228,7 @@ apex {
key: "com.android.apex.cts.shim.key",
prebuilts: ["hash_of_dev_null"],
installable: false,
+ updatable: false,
}
apex {
@@ -193,6 +239,7 @@ apex {
key: "com.android.apex.cts.shim.key",
prebuilts: ["hash_of_dev_null"],
installable: false,
+ updatable: false,
}
genrule {
@@ -212,6 +259,7 @@ genrule {
":com.android.apex.cts.shim.v2_with_post_install_hook",
":com.android.apex.cts.shim.v2_sdk_target_p",
":com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p",
+ ":com.android.apex.cts.shim.v2_rebootless",
":com.android.apex.cts.shim.v3",
":com.android.apex.cts.shim.v3_signed_bob",
":com.android.apex.cts.shim.v3_signed_bob_rot",
@@ -236,6 +284,7 @@ apex {
prebuilts: ["hash_v1"],
apps: ["CtsShim", "CtsShimPriv"],
allowed_files: "default_shim_allowed_list.txt",
+ updatable: false,
}
// This is to install the flattened version of com.android.apex.cts.shim.
@@ -276,10 +325,12 @@ apex_key {
apex {
name: "com.android.apex.cts.shim_not_pre_installed",
manifest: "manifest_not_pre_installed.json",
+ androidManifest: "AndroidManifestNotPreInstalled.xml",
file_contexts: ":apex.test-file_contexts",
key: "com.android.apex.cts.shim_not_pre_installed.key",
prebuilts: ["hash_of_dev_null"],
installable: false,
+ updatable: false,
}
apex {
@@ -291,6 +342,7 @@ apex {
prebuilts: ["hash_of_dev_null"],
installable: false,
certificate: ":com.android.apex.cts.shim.debug.cert",
+ updatable: false,
}
android_app_certificate {
@@ -455,6 +507,7 @@ apex {
apps: ["CtsShim", "CtsShimPriv"],
installable: false,
min_sdk_version: "29",
+ updatable: false,
}
genrule {
@@ -475,6 +528,7 @@ apex {
prebuilts: ["hash_of_dev_null"],
installable: false,
apps: ["CtsShim", "CtsShimPriv"],
+ updatable: false,
}
// Apex shim with apk-in-apex that targets sdk P
@@ -487,6 +541,7 @@ apex {
prebuilts: ["hash_of_dev_null"],
apps: ["CtsShimTargetPSdk"],
installable: false,
+ updatable: false,
}
// Apex shim with unsigned apk
@@ -495,7 +550,25 @@ genrule {
srcs: [":com.android.apex.cts.shim.v2"],
out: ["com.android.apex.cts.shim.v2_unsigned_apk_container.apex"],
cmd: "cp -v $(in) $(out) && zip -d $(out) META-INF*",
- dist: {
- targets: ["apps_only"],
- }
+}
+
+// Apex shim for testing rebootless updates
+apex {
+ name: "com.android.apex.cts.shim.v2_rebootless",
+ manifest: "manifest_v2_rebootless.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.cts.shim.key",
+ prebuilts: ["hash_of_dev_null"],
+ installable: false,
+ updatable: false,
+}
+
+apex {
+ name: "com.android.apex.cts.shim.v3_rebootless",
+ manifest: "manifest_v3_rebootless.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.cts.shim.key",
+ prebuilts: ["hash_of_dev_null"],
+ installable: false,
+ updatable: false,
}
diff --git a/shim/build/AndroidManifestNotPreInstalled.xml b/shim/build/AndroidManifestNotPreInstalled.xml
new file mode 100644
index 00000000..82955f77
--- /dev/null
+++ b/shim/build/AndroidManifestNotPreInstalled.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.cts.shim_not_pre_installed">
+ <!-- APEX does not have classes.dex -->
+ <application android:hasCode="false" />
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+</manifest>
+
diff --git a/shim/build/manifest_v2_rebootless.json b/shim/build/manifest_v2_rebootless.json
new file mode 100644
index 00000000..5f6adfdb
--- /dev/null
+++ b/shim/build/manifest_v2_rebootless.json
@@ -0,0 +1,5 @@
+{
+ "name": "com.android.apex.cts.shim",
+ "version": 2,
+ "supportsRebootlessUpdate": true
+}
diff --git a/shim/build/manifest_v3_rebootless.json b/shim/build/manifest_v3_rebootless.json
new file mode 100644
index 00000000..61e63ff0
--- /dev/null
+++ b/shim/build/manifest_v3_rebootless.json
@@ -0,0 +1,5 @@
+{
+ "name": "com.android.apex.cts.shim",
+ "version": 3,
+ "supportsRebootlessUpdate": true
+}
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v1.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v1.apex
index 7b5758dc..41a0c9cd 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v1.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v1.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2.apex
index dc2c8c9d..96669d89 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_file.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_file.apex
index 3cf81757..1aa561c9 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_file.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_file.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_folder.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_folder.apex
index 53358189..789230e6 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_folder.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_additional_folder.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex
index 4f0edff6..11255a54 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apex
index af89d200..f5e7f034 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_certificate.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_package_name.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_package_name.apex
index 12b85e43..ba10726d 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_package_name.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_different_package_name.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_no_hashtree.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_no_hashtree.apex
index 00c0c197..8cc3bdcd 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_no_hashtree.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_no_hashtree.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_rebootless.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_rebootless.apex
new file mode 100644
index 00000000..7556ac7e
--- /dev/null
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_rebootless.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex
index 50e77bdd..7c75442f 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex
new file mode 100644
index 00000000..1cb49705
--- /dev/null
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob.apex
index 3bde9ba5..b05306d2 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex
index 26c3f27a..14769b57 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
index 81aaecfc..8ac1ff9d 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_payload.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_payload.apex
index d2be4a2a..adaff7bc 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_payload.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_unsigned_payload.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex
index e1f904e6..3ad60002 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
index c8aa9d98..ba1755d8 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apex
index 41c29bfd..82e4754d 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_wrong_sha.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_wrong_sha.apex
index 1447e7d2..5ef089a6 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v2_wrong_sha.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v2_wrong_sha.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v3.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v3.apex
index e538f831..4b2b49ef 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v3.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v3.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v3_rebootless.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v3_rebootless.apex
new file mode 100644
index 00000000..802cfce8
--- /dev/null
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v3_rebootless.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob.apex
index 86b418e0..c2cb4e23 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex b/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex
index 29a0877b..75acc54e 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex
Binary files differ
diff --git a/shim/prebuilts/arm/com.android.apex.cts.shim_not_pre_installed.apex b/shim/prebuilts/arm/com.android.apex.cts.shim_not_pre_installed.apex
index e8705c7f..61257d6d 100644
--- a/shim/prebuilts/arm/com.android.apex.cts.shim_not_pre_installed.apex
+++ b/shim/prebuilts/arm/com.android.apex.cts.shim_not_pre_installed.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v1.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v1.apex
index 04ec92da..f8b1dc57 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v1.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v1.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2.apex
index ff547892..2500feb2 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_file.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_file.apex
index 3cf81757..1aa561c9 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_file.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_file.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_folder.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_folder.apex
index 53358189..789230e6 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_folder.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_additional_folder.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex
index 4f0edff6..11255a54 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apex
index af89d200..f5e7f034 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_certificate.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_package_name.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_package_name.apex
index eca1c375..8a6cc222 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_package_name.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_different_package_name.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_no_hashtree.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_no_hashtree.apex
index 19da8646..1ebb069f 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_no_hashtree.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_no_hashtree.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_rebootless.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_rebootless.apex
new file mode 100644
index 00000000..7556ac7e
--- /dev/null
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_rebootless.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex
index 6b7436b4..1189d97e 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex
new file mode 100644
index 00000000..1cb49705
--- /dev/null
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob.apex
index 659f4625..31307155 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex
index c5e22108..b1f1db8e 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
index 3ff88a91..3f2059da 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_payload.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_payload.apex
index cd702268..aec79145 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_payload.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_unsigned_payload.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex
index e1f904e6..3ad60002 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
index c8aa9d98..ba1755d8 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apex
index 41c29bfd..82e4754d 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_wrong_sha.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_wrong_sha.apex
index 1447e7d2..5ef089a6 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v2_wrong_sha.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v2_wrong_sha.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v3.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v3.apex
index b3ecef76..e3782eab 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v3.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v3.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v3_rebootless.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v3_rebootless.apex
new file mode 100644
index 00000000..802cfce8
--- /dev/null
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v3_rebootless.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob.apex
index 9281ee39..bec8ad0b 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex b/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex
index 23b23789..2a1ffb89 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex
Binary files differ
diff --git a/shim/prebuilts/x86/com.android.apex.cts.shim_not_pre_installed.apex b/shim/prebuilts/x86/com.android.apex.cts.shim_not_pre_installed.apex
index e8705c7f..61257d6d 100644
--- a/shim/prebuilts/x86/com.android.apex.cts.shim_not_pre_installed.apex
+++ b/shim/prebuilts/x86/com.android.apex.cts.shim_not_pre_installed.apex
Binary files differ
diff --git a/tests/Android.bp b/tests/Android.bp
index 96fd30b4..425f2bc2 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
java_defaults {
name: "apex_e2e_test_defaults",
libs: [
@@ -24,17 +28,6 @@ java_defaults {
}
java_test_host {
- name: "adbd_e2e_tests",
- srcs: ["src/**/AdbdHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.adbd",
- ],
- test_config: "adbd-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
name: "timezone_data_e2e_tests",
srcs: ["src/**/TimezoneDataHostTest.java"],
defaults: ["apex_e2e_test_defaults"],
@@ -49,17 +42,6 @@ java_test_host {
}
java_test_host {
- name: "statsd_e2e_tests",
- srcs: ["src/**/StatsdHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.os.statsd",
- ],
- test_config: "statsd-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
name: "media_e2e_tests",
srcs: ["src/**/MediaHostTest.java"],
defaults: ["apex_e2e_test_defaults"],
@@ -82,95 +64,6 @@ java_test_host {
}
java_test_host {
- name: "mediaprovider_e2e_tests",
- srcs: ["src/**/MediaProviderHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.mediaprovider",
- ],
- test_config: "mediaprovider-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
- name: "conscrypt_e2e_tests",
- srcs: ["src/**/ConscryptHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.conscrypt",
- ],
- test_config: "conscrypt-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
- name: "neuralnetworks_e2e_tests",
- srcs: ["src/**/NeuralNetworksHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.neuralnetworks",
- ],
- test_config: "neuralnetworks-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-
-java_test_host {
- name: "cellbroadcast_e2e_tests",
- srcs: ["src/**/CellbroadcastHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.cellbroadcast",
- ],
- test_config: "cellbroadcast-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
- name: "ipsec_e2e_tests",
- srcs: ["src/**/IpSecHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.ipsec",
- ],
- test_config: "ipsec-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
- name: "permission_e2e_tests",
- srcs: ["src/**/PermissionHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.permission",
- ],
- test_config: "permission-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
- name: "wifi_e2e_tests",
- srcs: ["src/**/WifiHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.wifi",
- ],
- test_config: "wifi-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
- name: "extservices_e2e_tests",
- srcs: ["src/**/ExtServicesHostTest.java"],
- defaults: ["apex_e2e_test_defaults"],
- data: [
- ":test_com.android.extservices",
- ],
- test_config: "extservices-e2e-tests.xml",
- test_suites: ["device-tests"],
-}
-
-java_test_host {
name: "apex_targetprep_tests",
libs: ["tradefed"],
srcs: ["src/**/ApexTargetPrepTest.java"],
@@ -183,19 +76,14 @@ java_library_host {
name: "apex_e2e_base_test",
srcs: ["src/**/ApexE2EBaseHostTest.java"],
static_libs: [
- "module_test_util",
+ "frameworks-base-hostutils",
+ "cts-install-lib-host",
],
libs: [
"tradefed",
],
}
-java_library_host {
- name: "module_test_util",
- srcs: ["util/**/ModuleTestUtils.java"],
- libs: ["tradefed", "truth-prebuilt"],
-}
-
apex {
name: "apex.test",
manifest: "testdata/apex_manifest.json",
@@ -203,6 +91,7 @@ apex {
key: "apex.test.key",
certificate: ":apex.test.certificate",
installable: false,
+ updatable: false,
}
apex_key {
@@ -237,21 +126,11 @@ prebuilt_etc {
installable: false,
}
-cc_binary {
- name: "sample_prefer32_binary",
- srcs: ["sample_prefer32_binary.cc"],
- target: {
- android: {
- compile_multilib: "prefer32",
- },
- },
-}
-
java_test_host {
name: "apex_rollback_tests",
- srcs: ["src/**/ApexRollbackTests.java", "src/**/ApexTestUtils.java"],
+ srcs: ["src/**/ApexRollbackTests.java"],
libs: ["tradefed", "truth-prebuilt"],
- static_libs: ["module_test_util"],
+ static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
test_config: "apex-rollback-tests.xml",
test_suites: ["general-tests"],
@@ -265,23 +144,17 @@ java_test_host {
}
java_test_host {
- name: "module_test_utils_tests",
- srcs: ["src/**/ModuleTestUtilsTest.java"],
- libs: ["tradefed", "truth-prebuilt"],
- static_libs: ["module_test_util"],
- test_config: "module-test-utils-tests.xml",
- test_suites: ["general-tests"],
- data: [":com.android.apex.cts.shim.v2_prebuilt"],
-}
-
-java_test_host {
name: "apexd_host_tests",
- srcs: ["src/**/ApexdHostTest.java"],
+ srcs: [
+ "src/**/ApexdHostTest.java",
+ ":apex-info-list",
+ ],
libs: ["tradefed"],
static_libs: [
- "module_test_util",
"truth-prebuilt",
"apex_manifest_proto_java",
+ "frameworks-base-hostutils",
+ "cts-install-lib-host"
],
test_config: "apexd-host-tests.xml",
test_suites: ["general-tests"],
@@ -292,5 +165,67 @@ java_test_host {
":apex.apexd_test_v3",
":com.android.apex.cts.shim.v2_prebuilt",
":com.android.apex.cts.shim.v2_no_pb",
+ ":com.android.apex.cts.shim.v2_additional_file_prebuilt",
+ ],
+}
+
+java_test_host {
+ name: "sharedlibs_host_tests",
+ srcs: [
+ "src/**/SharedLibsApexTest.java"
+ ],
+ libs: ["tradefed"],
+ java_resources: [
+ ":com.android.apex.test.bar_stripped.v1.libvX_prebuilt",
+ ":com.android.apex.test.bar_stripped.v2.libvY_prebuilt",
+ ":com.android.apex.test.bar.v1.libvX_prebuilt",
+ ":com.android.apex.test.bar.v2.libvY_prebuilt",
+ ":com.android.apex.test.baz_stripped.v1.libvX_prebuilt",
+ ":com.android.apex.test.foo_stripped.v1.libvX_prebuilt",
+ ":com.android.apex.test.foo_stripped.v2.libvY_prebuilt",
+ ":com.android.apex.test.foo.v1.libvX_prebuilt",
+ ":com.android.apex.test.foo.v2.libvY_prebuilt",
+ ":com.android.apex.test.pony_stripped.v1.libvZ_prebuilt",
+ ":com.android.apex.test.pony.v1.libvZ_prebuilt",
+ ":com.android.apex.test.sharedlibs_generated.v1.libvX_prebuilt",
+ ":com.android.apex.test.sharedlibs_generated.v2.libvY_prebuilt",
+ ":com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ_prebuilt",
],
+ static_libs: [
+ "compatibility-host-util",
+ "cts-install-lib-host",
+ "frameworks-base-hostutils",
+ "truth-prebuilt"
+ ],
+ test_config: "shared-libs-apex-tests.xml",
+ test_suites: ["general-tests"],
+}
+
+java_test_host {
+ name: "apex_compression_platform_tests",
+ srcs: ["src/**/ApexCompressionTests.java"],
+ libs: ["tradefed", "truth-prebuilt"],
+ static_libs: ["cts-install-lib-host", "testng"],
+ test_config: "apex_compression_platform_tests.xml",
+ test_suites: ["general-tests"],
+ data: [
+ ":com.android.apex.compressed.v1",
+ ":com.android.apex.compressed.v1_original",
+ ":com.android.apex.compressed.v1_different_digest",
+ ":com.android.apex.compressed.v2",
+ ":gen_manifest_mismatch_compressed_apex_v2",
+ ":apex_compression_tests_app",
+ ],
+}
+
+android_test_helper_app {
+ name: "apex_compression_tests_app",
+ manifest: "app/AndroidManifest.xml",
+ srcs: ["app/src/**/*.java"],
+ static_libs: ["androidx.test.rules", "cts-install-lib", "cts-rollback-lib", "testng"],
+ test_suites: ["general-tests"],
+ java_resources: [
+ ":com.android.apex.compressed.v1_original",
+ ":com.android.apex.compressed.v2_original",
+ ]
}
diff --git a/tests/TEST_MAPPING b/tests/TEST_MAPPING
index cd488fb2..d3177a80 100644
--- a/tests/TEST_MAPPING
+++ b/tests/TEST_MAPPING
@@ -1,49 +1,25 @@
{
"presubmit": [
{
- "name": "apex_rollback_tests"
- },
- {
"name": "apex_targetprep_tests"
},
{
- "name": "adbd_e2e_tests"
- },
- {
- "name": "apexd_host_tests"
- },
- {
- "name": "conscrypt_e2e_tests"
- },
- {
- "name": "extservices_e2e_tests"
- },
- {
- "name": "neuralnetworks_e2e_tests"
- },
- {
- "name": "permission_e2e_tests"
- },
- {
- "name": "sdkextensions_e2e_tests"
- },
- {
- "name": "statsd_e2e_tests"
- },
- {
"name": "timezone_data_e2e_tests"
},
{
- "name": "ipsec_e2e_tests"
- },
- {
- "name": "wifi_e2e_tests"
- },
+ "name": "CtsApexSharedLibrariesTestCases"
+ }
+ ],
+ "presubmit-large": [
+ // TODO(b/190710217): uncomment this.
+ //{
+ // "name": "apex_rollback_tests"
+ //},
{
- "name": "mediaprovider_e2e_tests"
+ "name": "sdkextensions_e2e_tests"
},
{
- "name": "cellbroadcast_e2e_tests"
+ "name": "sharedlibs_host_tests"
}
],
"postsubmit": [
@@ -58,57 +34,16 @@
"name": "media_swcodec_e2e_tests",
"keywords": ["primary-device"]
},
- // The following changes are in post-submit to restrict to physical
- // devices (currently userspace reboot fails on cuttlefish).
- // TODO(b/147726967): Remove when Userspace reboot works on cuttlefish
{
- "name": "adbd_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "conscrypt_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "extservices_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "neuralnetworks_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "permission_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "sdkextensions_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "timezone_data_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "ipsec_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "wifi_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "mediaprovider_e2e_tests",
- "keywords": ["primary-device"]
- },
- {
- "name": "cellbroadcast_e2e_tests",
- "keywords": ["primary-device"]
+ "name": "apex_compression_platform_tests"
}
],
"imports": [
{
"path": "cts/hostsidetests/stagedinstall"
+ },
+ {
+ "path": "frameworks/base/tests/StagedInstallTest"
}
]
}
diff --git a/tests/adbd-e2e-tests.xml b/tests/adbd-e2e-tests.xml
deleted file mode 100644
index a144d834..00000000
--- a/tests/adbd-e2e-tests.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
-<configuration description="Configuration for adbd module e2e tests">
- <option name="test-suite-tag" value="adbd_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="adbd_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.adbd.apex" />
- </test>
-</configuration>
diff --git a/tests/apex_compression_platform_tests.xml b/tests/apex_compression_platform_tests.xml
new file mode 100644
index 00000000..a837fbad
--- /dev/null
+++ b/tests/apex_compression_platform_tests.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Runs the apex compression test cases">
+ <option name="test-suite-tag" value="apex_compression_platform_tests" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="apex_compression_tests_app.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop persist.rollback.is_test 1" />
+ <option name="teardown-command" value="setprop persist.rollback.is_test 0" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class"
+ value="com.android.tests.apex.host.ApexCompressionTests" />
+ </test>
+</configuration>
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
new file mode 100644
index 00000000..0644ba7e
--- /dev/null
+++ b/tests/app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.apex.app" >
+
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <application>
+ <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+ android:exported="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tests.apex.app"
+ android:label="ApexCompression Test"/>
+
+</manifest>
diff --git a/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java b/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
new file mode 100644
index 00000000..a97293ce
--- /dev/null
+++ b/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 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 com.android.tests.apex.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.rollback.RollbackInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApexCompressionTests {
+ private static final String COMPRESSED_APEX_PACKAGE_NAME = "com.android.apex.compressed";
+ private final Context mContext = InstrumentationRegistry.getContext();
+ private final PackageManager mPm = mContext.getPackageManager();
+
+ private static final TestApp UNCOMPRESSED_APEX_V1 = new TestApp(
+ "TestAppUncompressedApexV1", COMPRESSED_APEX_PACKAGE_NAME, 2, /*isApex*/ true,
+ "com.android.apex.compressed.v1_original.apex");
+ private static final TestApp UNCOMPRESSED_APEX_V2 = new TestApp(
+ "TestAppUncompressedApexV2", COMPRESSED_APEX_PACKAGE_NAME, 2, /*isApex*/ true,
+ "com.android.apex.compressed.v2_original.apex");
+
+ @Before
+ public void adoptShellPermissions() {
+ androidx.test.platform.app.InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS);
+ }
+
+ @After
+ public void dropShellPermissions() {
+ androidx.test.platform.app.InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testDecompressedApexIsConsideredFactory() throws Exception {
+ // Only retrieve active apex package
+ PackageInfo pi = mPm.getPackageInfo(
+ COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(pi).isNotNull();
+ assertThat(pi.isApex).isTrue();
+ assertThat(pi.packageName).isEqualTo(COMPRESSED_APEX_PACKAGE_NAME);
+ assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isTrue();
+ }
+
+ @Test
+ public void testUnusedDecompressedApexIsCleanedUp_HigherVersion() throws Exception {
+ Install.single(UNCOMPRESSED_APEX_V2).setStaged().commit();
+ }
+
+ @Test
+ public void testUnusedDecompressedApexIsCleanedUp_SameVersion() throws Exception {
+ Install.single(UNCOMPRESSED_APEX_V1).setStaged().commit();
+ }
+
+
+ @Test
+ public void testCapexToApexSwitch() throws Exception {
+ // Only retrieve active apex package
+ PackageInfo pi = mPm.getPackageInfo(
+ COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(pi).isNotNull();
+ assertThat(pi.isApex).isTrue();
+ assertThat(pi.packageName).isEqualTo(COMPRESSED_APEX_PACKAGE_NAME);
+ assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isTrue();
+ assertThat(pi.applicationInfo.sourceDir).startsWith("/system/apex");
+ }
+
+ @Test
+ public void testDecompressedApexVersionAlwaysHasSameVersionAsCapex() throws Exception {
+ // Only retrieve active apex package
+ PackageInfo pi = mPm.getPackageInfo(
+ COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(pi).isNotNull();
+ assertThat(pi.isApex).isTrue();
+ assertThat(pi.packageName).isEqualTo(COMPRESSED_APEX_PACKAGE_NAME);
+ assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isTrue();
+ }
+
+ @Test
+ public void testCompressedApexCanBeRolledBack_Commit() throws Exception {
+ // Verify before updating
+ PackageInfo pi = mPm.getPackageInfo(
+ COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isTrue();
+ assertThat(pi.applicationInfo.sourceDir).startsWith("/data/apex/decompressed/");
+
+ Install.single(UNCOMPRESSED_APEX_V2).setStaged().setEnableRollback().commit();
+ }
+
+ @Test
+ public void testCompressedApexCanBeRolledBack_Rollback() throws Exception {
+ // Verify before rollback
+ PackageInfo pi = mPm.getPackageInfo(
+ COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(pi.getLongVersionCode()).isEqualTo(2);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isFalse();
+ assertThat(pi.applicationInfo.sourceDir).startsWith("/data/apex/active/");
+
+ // Trigger rollback
+ RollbackInfo available = RollbackUtils.getAvailableRollback(COMPRESSED_APEX_PACKAGE_NAME);
+ RollbackUtils.rollback(available.getRollbackId(), UNCOMPRESSED_APEX_V2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ }
+
+ @Test
+ public void testCompressedApexCanBeRolledBack_Verify() throws Exception {
+ // Verify rollback worked
+ PackageInfo pi = mPm.getPackageInfo(
+ COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+ assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isFalse();
+ assertThat(pi.applicationInfo.sourceDir).startsWith("/data/apex/active/");
+ }
+}
diff --git a/tests/cellbroadcast-e2e-tests.xml b/tests/cellbroadcast-e2e-tests.xml
deleted file mode 100644
index 368a88fa..00000000
--- a/tests/cellbroadcast-e2e-tests.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Config for Cellbroadcast module e2e test cases">
- <option name="test-suite-tag" value="cellbroadcast_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="cellbroadcast_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.cellbroadcast.apex" />
- </test>
-</configuration> \ No newline at end of file
diff --git a/tests/conscrypt-e2e-tests.xml b/tests/conscrypt-e2e-tests.xml
deleted file mode 100644
index 77f781a2..00000000
--- a/tests/conscrypt-e2e-tests.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Config for Conscrypt module e2e test cases">
- <option name="test-suite-tag" value="conscrypt_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="conscrypt_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.conscrypt.apex" />
- </test>
-</configuration>
-
diff --git a/tests/extservices-e2e-tests.xml b/tests/extservices-e2e-tests.xml
deleted file mode 100644
index c45392e6..00000000
--- a/tests/extservices-e2e-tests.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.
--->
-<configuration description="Config for extservices apex e2e testing">
- <option name="test-suite-tag" value="extservices_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="extservices_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.extservices.apex" />
- </test>
-</configuration>
diff --git a/tests/ipsec-e2e-tests.xml b/tests/ipsec-e2e-tests.xml
deleted file mode 100644
index 0030bd34..00000000
--- a/tests/ipsec-e2e-tests.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Config for ipsec module e2e testing">
- <option name="test-suite-tag" value="ipsec_e2e_test" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="ipsec_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.ipsec.apex" />
- </test>
-</configuration>
-
diff --git a/tests/mediaprovider-e2e-tests.xml b/tests/mediaprovider-e2e-tests.xml
deleted file mode 100644
index e1212622..00000000
--- a/tests/mediaprovider-e2e-tests.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Config for MediaProvider module e2e test cases">
- <option name="test-suite-tag" value="mediaprovider_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="mediaprovider_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.mediaprovider.apex" />
- </test>
-</configuration>
diff --git a/tests/module-test-utils-tests.xml b/tests/module-test-utils-tests.xml
deleted file mode 100644
index 6337ab92..00000000
--- a/tests/module-test-utils-tests.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-<configuration description="Unit tests for ModuleTestUtils">
- <option name="test-suite-tag" value="module_test_utils_tests" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="module_test_utils_tests.jar" />
- </test>
-</configuration>
diff --git a/tests/native/.clang-format b/tests/native/.clang-format
new file mode 100644
index 00000000..f6cb8ad9
--- /dev/null
+++ b/tests/native/.clang-format
@@ -0,0 +1 @@
+BasedOnStyle: Google
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
new file mode 100644
index 00000000..79152223
--- /dev/null
+++ b/tests/native/Android.bp
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "CtsApexSharedLibrariesTestCases",
+ test_suites: [
+ "cts",
+ "device-tests",
+ "mts",
+ ],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libc++fs",
+ "libfs_mgr",
+ ],
+
+ srcs: [
+ "apex_shared_libraries_test.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+}
diff --git a/tests/native/AndroidTest.xml b/tests/native/AndroidTest.xml
new file mode 100644
index 00000000..fa189d5e
--- /dev/null
+++ b/tests/native/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Config for CTS apex_shared_libraries test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="systems" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsApexSharedLibrariesTestCases->/data/local/tmp/CtsApexSharedLibrariesTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsApexSharedLibrariesTestCases" />
+ <option name="runtime-hint" value="65s" />
+ </test>
+</configuration>
diff --git a/tests/native/apex_shared_libraries_test.cpp b/tests/native/apex_shared_libraries_test.cpp
new file mode 100644
index 00000000..9f536dc6
--- /dev/null
+++ b/tests/native/apex_shared_libraries_test.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+#define LOG_TAG "apex_shared_libraries_test"
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
+#include <dlfcn.h>
+#include <fstab/fstab.h>
+#include <gtest/gtest.h>
+#include <link.h>
+
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+using android::base::GetBoolProperty;
+using android::base::Split;
+using android::base::StartsWith;
+using android::fs_mgr::Fstab;
+using android::fs_mgr::ReadFstabFromFile;
+
+namespace fs = std::filesystem;
+
+static constexpr const char* kApexRoot = "/apex";
+static constexpr const char* kApexSharedLibsRoot = "/apex/sharedlibs";
+
+TEST(apex_shared_libraries, symlink_libraries_loadable) {
+ if (!GetBoolProperty("ro.apex.updatable", false)) {
+ GTEST_SKIP() << "Skipping test because device doesn't support APEX";
+ }
+
+ Fstab fstab;
+ ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &fstab));
+
+ // Traverse mount points to identify apexs.
+ for (auto& entry : fstab) {
+ if (fs::path(entry.mount_point).parent_path() != kApexRoot) {
+ continue;
+ }
+
+ // Focus on "active" apexs.
+ if (entry.mount_point.find('@') != std::string::npos) {
+ continue;
+ }
+ std::string dev_file = fs::path(entry.blk_device).filename();
+
+ // Filter out any mount irrelevant (e.g. tmpfs)
+ if (!StartsWith(dev_file, "loop") && !StartsWith(dev_file, "dm-")) {
+ continue;
+ }
+
+#if !defined(__LP64__)
+ auto lib = fs::path(entry.mount_point) / "lib";
+#else // !__LP64__
+ auto lib = fs::path(entry.mount_point) / "lib64";
+#endif // !__LP64__
+ if (!fs::is_directory(lib)) {
+ continue;
+ }
+ for (auto& p : fs::directory_iterator(lib)) {
+ std::error_code ec;
+ if (!fs::is_symlink(p, ec)) {
+ continue;
+ }
+
+ // We are only checking libraries pointing at a location inside
+ // /apex/sharedlibs.
+ auto target = fs::read_symlink(p.path(), ec);
+ if (ec || !StartsWith(target.string(), kApexSharedLibsRoot)) {
+ continue;
+ }
+
+ // Symlink validity check.
+ auto dest = fs::canonical(p.path(), ec);
+ EXPECT_FALSE(ec) << "Failed to resolve " << p.path() << " (symlink to "
+ << target << "): " << ec;
+ if (ec) {
+ continue;
+ }
+
+ // Library loading validity check.
+ dlerror(); // Clear any pending errors.
+ void* handle = dlopen(p.path().c_str(), RTLD_NOW);
+ EXPECT_TRUE(handle != nullptr) << dlerror();
+ if (handle == nullptr) {
+ continue;
+ }
+ auto guard = android::base::make_scope_guard([&]() { dlclose(handle); });
+
+ // Check that library is loaded and pointing to the realpath of the
+ // library.
+ auto dl_callback = [](dl_phdr_info* info, size_t /* size */, void* data) {
+ auto dest = *static_cast<fs::path*>(data);
+ if (info->dlpi_name == nullptr) {
+ // This is linker imposing as libdl.so - skip it
+ return 0;
+ }
+ int j;
+ for (j = 0; j < info->dlpi_phnum; j++) {
+ void* addr = (void*)(info->dlpi_addr + info->dlpi_phdr[j].p_vaddr);
+ Dl_info dl_info;
+ int rc = dladdr(addr, &dl_info);
+ if (rc == 0) {
+ continue;
+ }
+ if (dl_info.dli_fname) {
+ auto libpath = fs::path(dl_info.dli_fname);
+ if (libpath == dest) {
+ // Library found!
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+ };
+ bool found = (dl_iterate_phdr(dl_callback, &dest) == 1);
+ EXPECT_TRUE(found) << "Error verifying library symlink " << p.path()
+ << " which points to " << target
+ << " which resolves to file " << dest;
+ if (found) {
+ LOG(INFO) << "Verified that " << p.path()
+ << " correctly loads as library " << dest;
+ }
+ }
+ }
+}
diff --git a/tests/neuralnetworks-e2e-tests.xml b/tests/neuralnetworks-e2e-tests.xml
deleted file mode 100644
index 63c38a68..00000000
--- a/tests/neuralnetworks-e2e-tests.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Config for neuralnetworks module e2e test cases">
- <option name="test-suite-tag" value="neuralnetworks_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="neuralnetworks_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.neuralnetworks.apex" />
- </test>
-</configuration>
-
diff --git a/tests/permission-e2e-tests.xml b/tests/permission-e2e-tests.xml
deleted file mode 100644
index e1605abe..00000000
--- a/tests/permission-e2e-tests.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2018 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.
--->
-
-<configuration description="Configuration for permission module e2e test cases">
- <option name="test-suite-tag" value="permission_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="permission_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.permission.apex" />
- </test>
-</configuration>
diff --git a/tests/sample_prefer32_binary.cc b/tests/sample_prefer32_binary.cc
deleted file mode 100644
index 76e81970..00000000
--- a/tests/sample_prefer32_binary.cc
+++ /dev/null
@@ -1 +0,0 @@
-int main() { return 0; }
diff --git a/tests/shared-libs-apex-tests.xml b/tests/shared-libs-apex-tests.xml
new file mode 100644
index 00000000..d41b8916
--- /dev/null
+++ b/tests/shared-libs-apex-tests.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Runs the shared libs apex host side test cases">
+ <option name="test-suite-tag" value="sharedlibs_host_tests" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="sharedlibs_host_tests.jar" />
+ </test>
+</configuration>
diff --git a/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java b/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
index 0812c6ac..e0871df5 100644
--- a/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
+++ b/tests/src/com/android/tests/apex/ApexE2EBaseHostTest.java
@@ -21,9 +21,10 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
+import android.cts.install.lib.host.InstallUtilsHost;
import android.platform.test.annotations.RequiresDevice;
-import com.android.tests.util.ModuleTestUtils;
+import com.android.tests.rollback.host.AbandonSessionsRule;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.device.ITestDevice.ApexInfo;
@@ -33,6 +34,7 @@ import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.io.File;
@@ -52,8 +54,11 @@ public abstract class ApexE2EBaseHostTest extends BaseHostJUnit4Test {
private static final String USERSPACE_REBOOT_SUPPORTED_PROP =
"init.userspace_reboot.is_supported";
- /* protected so that derived tests can have access to test utils automatically */
- protected final ModuleTestUtils mUtils = new ModuleTestUtils(this);
+ // Protected so that derived tests can have access to test utils automatically
+ protected final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+
+ @Rule
+ public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
@Option(name = OPTION_APEX_FILE_NAME,
description = "The file name of the apex module.",
@@ -64,15 +69,13 @@ public abstract class ApexE2EBaseHostTest extends BaseHostJUnit4Test {
@Before
public void setUp() throws Exception {
- assumeTrue("Updating APEX is not supported", mUtils.isApexUpdateSupported());
- mUtils.abandonActiveStagedSession();
+ assumeTrue("Updating APEX is not supported", mHostUtils.isApexUpdateSupported());
uninstallAllApexes();
}
@After
public void tearDown() throws Exception {
- assumeTrue("Updating APEX is not supported", mUtils.isApexUpdateSupported());
- mUtils.abandonActiveStagedSession();
+ assumeTrue("Updating APEX is not supported", mHostUtils.isApexUpdateSupported());
uninstallAllApexes();
}
@@ -105,35 +108,23 @@ public abstract class ApexE2EBaseHostTest extends BaseHostJUnit4Test {
private void uninstallAllApexes() throws Exception {
for (String filename : getAllApexFilenames()) {
- ApexInfo apex = mUtils.getApexInfo(mUtils.getTestFile(filename));
+ ApexInfo apex = mHostUtils.getApexInfo(mHostUtils.getTestFile(filename));
uninstallApex(apex.name);
}
}
protected final ApexInfo installApex(String filename) throws Exception {
- File testAppFile = mUtils.getTestFile(filename);
+ File testAppFile = mHostUtils.getTestFile(filename);
- String installResult = getDevice().installPackage(testAppFile, false, "--wait");
+ String installResult = mHostUtils.installStagedPackage(testAppFile);
assertWithMessage("failed to install test app %s. Reason: %s", filename, installResult)
.that(installResult).isNull();
- ApexInfo testApexInfo = mUtils.getApexInfo(testAppFile);
+ ApexInfo testApexInfo = mHostUtils.getApexInfo(testAppFile);
Assert.assertNotNull(testApexInfo);
return testApexInfo;
}
- protected final void installApexes(String... filenames) throws Exception {
- // We don't use the installApex method from the super class here, because that won't install
- // the two apexes into the same session.
- String[] args = new String[filenames.length + 1];
- args[0] = "install-multi-package";
- for (int i = 0; i < filenames.length; i++) {
- args[i + 1] = mUtils.getTestFile(filenames[i]).getAbsolutePath();
- }
- String stdout = getDevice().executeAdbCommand(args);
- assertThat(stdout).isNotNull();
- }
-
protected final void reboot(boolean userspaceReboot) throws Exception {
if (userspaceReboot) {
assertThat(getDevice().setProperty("test.userspace_reboot.requested", "1")).isTrue();
diff --git a/tests/src/com/android/tests/apex/ApexRollbackTests.java b/tests/src/com/android/tests/apex/ApexRollbackTests.java
index 6cf54e1f..68f82ca6 100644
--- a/tests/src/com/android/tests/apex/ApexRollbackTests.java
+++ b/tests/src/com/android/tests/apex/ApexRollbackTests.java
@@ -22,16 +22,18 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
-import com.android.tests.util.ModuleTestUtils;
+import android.cts.install.lib.host.InstallUtilsHost;
+
+import com.android.tests.rollback.host.AbandonSessionsRule;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.ApexInfo;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,13 +46,20 @@ import java.util.Set;
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexRollbackTests extends BaseHostJUnit4Test {
- private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
+ private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+ @Rule
+ public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
+
+ private boolean mWasAdbRoot = false;
@Before
public void setUp() throws Exception {
- mUtils.abandonActiveStagedSession();
- mUtils.uninstallShimApexIfNecessary();
+ mHostUtils.uninstallShimApexIfNecessary();
resetProperties();
+ mWasAdbRoot = getDevice().isAdbRoot();
+ if (!mWasAdbRoot) {
+ assumeTrue("Requires root", getDevice().enableAdbRoot());
+ }
}
/**
@@ -59,9 +68,11 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@After
public void tearDown() throws Exception {
- mUtils.abandonActiveStagedSession();
- mUtils.uninstallShimApexIfNecessary();
+ mHostUtils.uninstallShimApexIfNecessary();
resetProperties();
+ if (!mWasAdbRoot) {
+ getDevice().disableAdbRoot();
+ }
}
private void resetProperties() throws Exception {
@@ -81,9 +92,15 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testAutomaticBootLoopRecovery() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
-
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ ITestDevice device = getDevice();
+ // Skip this test if there is already crashing process on device
+ boolean hasCrashingProcess =
+ device.getBooleanProperty("sys.init.updatable_crashing", false);
+ String crashingProcess = device.getProperty("sys.init.updatable_crashing_process_name");
+ assumeFalse(
+ "Device already has a crashing process: " + crashingProcess, hasCrashingProcess);
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// To simulate an apex update that causes a boot loop, we install a
// trigger_watchdog.rc file that arranges for a trigger_watchdog.sh
@@ -92,10 +109,9 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
// persist.debug.trigger_watchdog.apex is installed. If so,
// trigger_watchdog.sh repeatedly kills the system server causing a
// boot loop.
- ITestDevice device = getDevice();
assertThat(device.setProperty("persist.debug.trigger_watchdog.apex",
"com.android.apex.cts.shim@2")).isTrue();
- String error = device.installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertThat(error).isNull();
String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions --only-ready "
@@ -129,10 +145,10 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testSessionNotRevertedWithCheckpointingDisabled() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
- assumeFalse("Fs checkpointing is enabled", supportsFsCheckpointing());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeFalse("Fs checkpointing is enabled", mHostUtils.isCheckpointSupported());
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
ITestDevice device = getDevice();
assertThat(device.setProperty("persist.debug.trigger_reboot_after_activation",
@@ -140,7 +156,7 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
assertThat(device.setProperty("debug.trigger_reboot_once_after_activation",
"1")).isTrue();
- String error = device.installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertThat(error).isNull();
String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions --only-ready "
@@ -153,12 +169,8 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
ApexInfo ctsShimV1 = new ApexInfo("com.android.apex.cts.shim", 1L);
ApexInfo ctsShimV2 = new ApexInfo("com.android.apex.cts.shim", 2L);
- String stagedSessionsInfo = device.executeShellCommand("pm get-stagedsessions");
- for (String line: stagedSessionsInfo.split("[\\r\\n]+")) {
- if (line.contains(sessionIdToCheck)) {
- assertThat(line).contains("isApplied = true");
- }
- }
+ String stagedSessionInfo = getStagedSession(sessionIdToCheck);
+ assertThat(stagedSessionInfo).contains("isApplied = true");
Set<ApexInfo> activatedApexes = device.getActiveApexes();
assertThat(activatedApexes).contains(ctsShimV2);
@@ -171,17 +183,17 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testCheckpointingRevertsSession() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
- assumeTrue("Device doesn't support fs checkpointing", supportsFsCheckpointing());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
ITestDevice device = getDevice();
assertThat(device.setProperty("persist.debug.trigger_reboot_after_activation",
"com.android.apex.cts.shim@2.apex")).isTrue();
assertThat(device.setProperty("persist.debug.trigger_reboot_twice_after_activation",
"1")).isTrue();
- String error = device.installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertThat(error).isNull();
String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions --only-ready "
@@ -195,12 +207,8 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
ApexInfo ctsShimV1 = new ApexInfo("com.android.apex.cts.shim", 1L);
ApexInfo ctsShimV2 = new ApexInfo("com.android.apex.cts.shim", 2L);
- String stagedSessionsInfo = device.executeShellCommand("pm get-stagedsessions");
- for (String line: stagedSessionsInfo.split("[\\r\\n]+")) {
- if (line.contains(sessionIdToCheck)) {
- assertThat(line).contains("isFailed = true");
- }
- }
+ String stagedSessionInfo = getStagedSession(sessionIdToCheck);
+ assertThat(stagedSessionInfo).contains("isFailed = true");
Set<ApexInfo> activatedApexes = device.getActiveApexes();
assertThat(activatedApexes).contains(ctsShimV1);
@@ -213,17 +221,17 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testRebootingOnceDoesNotRevertSession() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
- assumeTrue("Device doesn't support fs checkpointing", supportsFsCheckpointing());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
ITestDevice device = getDevice();
assertThat(device.setProperty("persist.debug.trigger_reboot_after_activation",
"com.android.apex.cts.shim@2.apex")).isTrue();
assertThat(device.setProperty("debug.trigger_reboot_once_after_activation",
"1")).isTrue();
- String error = device.installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertThat(error).isNull();
String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions --only-ready "
@@ -237,12 +245,8 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
ApexInfo ctsShimV1 = new ApexInfo("com.android.apex.cts.shim", 1L);
ApexInfo ctsShimV2 = new ApexInfo("com.android.apex.cts.shim", 2L);
- String stagedSessionsInfo = device.executeShellCommand("pm get-stagedsessions");
- for (String line: stagedSessionsInfo.split("[\\r\\n]+")) {
- if (line.contains(sessionIdToCheck)) {
- assertThat(line).contains("isApplied = true");
- }
- }
+ String stagedSessionInfo = getStagedSession(sessionIdToCheck);
+ assertThat(stagedSessionInfo).contains("isApplied = true");
Set<ApexInfo> activatedApexes = device.getActiveApexes();
assertThat(activatedApexes).contains(ctsShimV2);
@@ -257,7 +261,7 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testApexdDoesNotBootLoopDeviceIfThereIsNothingToRevert() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
// On next boot trigger setprop sys.init.updatable_crashing 1, which will trigger a
// revert mechanism in apexd. Since there is nothing to revert, this should be a no-op
// and device will boot successfully.
@@ -276,16 +280,16 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testFailingUserspaceReboot_doesNotRevertUpdate() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device doesn't support userspace reboot",
getDevice().getBooleanProperty("init.userspace_reboot.is_supported", false));
- assumeTrue("Device doesn't support fs checkpointing", supportsFsCheckpointing());
+ assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// Simulate failure in userspace reboot by triggering a full reboot in the middle of the
// boot sequence.
assertThat(getDevice().setProperty("test.apex_revert_test_force_reboot", "1")).isTrue();
- String error = getDevice().installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertWithMessage("Failed to stage com.android.apex.cts.shim.v2.apex : %s", error).that(
error).isNull();
// After we reboot the device, apexd will apply the update
@@ -305,17 +309,17 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testUserspaceRebootFailedShutdownSequence_doesNotRevertUpdate() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device doesn't support userspace reboot",
getDevice().getBooleanProperty("init.userspace_reboot.is_supported", false));
- assumeTrue("Device doesn't support fs checkpointing", supportsFsCheckpointing());
+ assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// Simulate failure in userspace reboot by triggering a full reboot in the middle of the
// boot sequence.
assertThat(getDevice().setProperty("test.apex_userspace_reboot_simulate_shutdown_failed",
"1")).isTrue();
- String error = getDevice().installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertWithMessage("Failed to stage com.android.apex.cts.shim.v2.apex : %s", error).that(
error).isNull();
// After the userspace reboot started, we simulate it's failure by rebooting device during
@@ -338,17 +342,17 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testUserspaceRebootFailedRemount_revertsUpdate() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device doesn't support userspace reboot",
getDevice().getBooleanProperty("init.userspace_reboot.is_supported", false));
- assumeTrue("Device doesn't support fs checkpointing", supportsFsCheckpointing());
+ assumeTrue("Device doesn't support fs checkpointing", mHostUtils.isCheckpointSupported());
- File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// Simulate failure in userspace reboot by triggering a full reboot in the middle of the
// boot sequence.
assertThat(getDevice().setProperty("test.apex_userspace_reboot_simulate_remount_failed",
"1")).isTrue();
- String error = getDevice().installPackage(apexFile, false, "--wait");
+ String error = mHostUtils.installStagedPackage(apexFile);
assertWithMessage("Failed to stage com.android.apex.cts.shim.v2.apex : %s", error).that(
error).isNull();
// After we reboot the device, apexd will apply the update
@@ -367,14 +371,14 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
*/
@Test
public void testBootCompletedCleanupHappensEvenWhenThereIsCrashingProcess() throws Exception {
- assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
try {
// On next boot trigger setprop sys.init.updatable_crashing 1, which will trigger a
// revert mechanism in apexd. Since there is nothing to revert, this should be a no-op
// and device will boot successfully.
getDevice().setProperty("persist.debug.trigger_updatable_crashing_for_testing", "1");
- assertThat(getDevice().pushFile(mUtils.getTestFile("apex.apexd_test_v2.apex"),
+ assertThat(getDevice().pushFile(mHostUtils.getTestFile("apex.apexd_test_v2.apex"),
"/data/apex/active/apexd_test_v2.apex")).isTrue();
getDevice().reboot();
assertWithMessage("Timed out waiting for device to boot").that(
@@ -386,18 +390,84 @@ public class ApexRollbackTests extends BaseHostJUnit4Test {
ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
"com.android.apex.cts.shim", 2L);
assertThat(activeApexes).doesNotContain(testApex);
- mUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex",
+ mHostUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex",
Duration.ofMinutes(3));
} finally {
getDevice().executeShellV2Command("rm /data/apex/active/apexd_test_v2.apex");
}
}
+ /**
+ * Test reason for revert is properly logged during boot loops
+ */
+ @Test
+ public void testReasonForRevertIsLoggedDuringBootloop() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Fs checkpointing is enabled", mHostUtils.isCheckpointSupported());
+
+ ITestDevice device = getDevice();
+ // Skip this test if there is already crashing process on device
+ final boolean hasCrashingProcess =
+ device.getBooleanProperty("sys.init.updatable_crashing", false);
+ final String crashingProcess =
+ device.getProperty("sys.init.updatable_crashing_process_name");
+ assumeFalse(
+ "Device already has a crashing process: " + crashingProcess, hasCrashingProcess);
+ final File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+
+ // To simulate an apex update that causes a boot loop, we install a
+ // trigger_watchdog.rc file that arranges for a trigger_watchdog.sh
+ // script to be run at boot. The trigger_watchdog.sh script checks if
+ // the apex version specified in the property
+ // persist.debug.trigger_watchdog.apex is installed. If so,
+ // trigger_watchdog.sh repeatedly kills the system server causing a
+ // boot loop.
+ assertThat(device.setProperty("persist.debug.trigger_watchdog.apex",
+ "com.android.apex.cts.shim@2")).isTrue();
+ final String error = mHostUtils.installStagedPackage(apexFile);
+ assertThat(error).isNull();
+
+ final String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions "
+ + "--only-ready --only-parent --only-sessionid").trim();
+ assertThat(sessionIdToCheck).isNotEmpty();
+
+ // After we reboot the device, we expect the device to go into boot
+ // loop from trigger_watchdog.sh. Native watchdog should detect and
+ // report the boot loop, causing apexd to roll back to the previous
+ // version of the apex and force reboot. When the device comes up
+ // after the forced reboot, trigger_watchdog.sh will see the different
+ // version of the apex and refrain from forcing a boot loop, so the
+ // device will be recovered.
+ device.reboot();
+
+ final ApexInfo ctsShimV1 = new ApexInfo("com.android.apex.cts.shim", 1L);
+ final ApexInfo ctsShimV2 = new ApexInfo("com.android.apex.cts.shim", 2L);
+ final Set<ApexInfo> activatedApexes = device.getActiveApexes();
+ assertThat(activatedApexes).contains(ctsShimV1);
+ assertThat(activatedApexes).doesNotContain(ctsShimV2);
+
+ // Assert that a session has failed with the expected reason
+ final String stagedSessionString = getStagedSession(sessionIdToCheck);
+ assertThat(stagedSessionString).contains("Session reverted due to crashing native process");
+ }
- private boolean supportsFsCheckpointing() throws Exception {
- CommandResult result = getDevice().executeShellV2Command("sm supports-checkpoint");
- assertWithMessage("Failed to check if fs checkpointing is supported : %s",
- result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
- return "true".equals(result.getStdout().trim());
+ String getStagedSession(String sessionId) throws DeviceNotAvailableException {
+ final String[] lines = getDevice().executeShellCommand(
+ "pm get-stagedsessions").split("\n");
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].startsWith("sessionId = " + sessionId + ";")) {
+ // Join all lines realted to this session
+ final StringBuilder result = new StringBuilder(lines[i]);
+ for (int j = i + 1; j < lines.length; j++) {
+ if (lines[j].startsWith("sessionId = ")) {
+ // A new session block has started
+ break;
+ }
+ result.append(lines[j]);
+ }
+ return result.toString();
+ }
+ }
+ return "";
}
}
diff --git a/tests/src/com/android/tests/apex/ApexdHostTest.java b/tests/src/com/android/tests/apex/ApexdHostTest.java
index fc1fa4be..2d72e9fd 100644
--- a/tests/src/com/android/tests/apex/ApexdHostTest.java
+++ b/tests/src/com/android/tests/apex/ApexdHostTest.java
@@ -21,17 +21,27 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
-import com.android.tests.util.ModuleTestUtils;
+import android.cts.install.lib.host.InstallUtilsHost;
+
+import com.android.apex.ApexInfo;
+import com.android.apex.XmlParser;
+import com.android.tests.rollback.host.AbandonSessionsRule;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileInputStream;
import java.time.Duration;
+import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Host side integration tests for apexd.
@@ -41,14 +51,36 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
- private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
+ private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+
+ @Rule
+ public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
+
+ private boolean mWasAdbRoot = false;
+
+ @Before
+ public void setUp() throws Exception {
+ mHostUtils.uninstallShimApexIfNecessary();
+ mWasAdbRoot = getDevice().isAdbRoot();
+ if (!mWasAdbRoot) {
+ assumeTrue("Device requires root", getDevice().enableAdbRoot());
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHostUtils.uninstallShimApexIfNecessary();
+ if (!mWasAdbRoot) {
+ getDevice().disableAdbRoot();
+ }
+ }
@Test
public void testOrphanedApexIsNotActivated() throws Exception {
- assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
try {
- assertThat(getDevice().pushFile(mTestUtils.getTestFile("apex.apexd_test_v2.apex"),
+ assertThat(getDevice().pushFile(mHostUtils.getTestFile("apex.apexd_test_v2.apex"),
"/data/apex/active/apexd_test_v2.apex")).isTrue();
getDevice().reboot();
assertWithMessage("Timed out waiting for device to boot").that(
@@ -57,7 +89,7 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
"com.android.apex.test_package", 2L);
assertThat(activeApexes).doesNotContain(testApex);
- mTestUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex",
+ mHostUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex",
Duration.ofMinutes(3));
} finally {
getDevice().executeShellV2Command("rm /data/apex/active/apexd_test_v2.apex");
@@ -65,11 +97,11 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
}
@Test
public void testApexWithoutPbIsNotActivated() throws Exception {
- assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex";
try {
- assertThat(getDevice().pushFile(mTestUtils.getTestFile(testApexFile),
+ assertThat(getDevice().pushFile(mHostUtils.getTestFile(testApexFile),
"/data/apex/active/" + testApexFile)).isTrue();
getDevice().reboot();
assertWithMessage("Timed out waiting for device to boot").that(
@@ -78,7 +110,7 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
"com.android.apex.cts.shim", 2L);
assertThat(activeApexes).doesNotContain(testApex);
- mTestUtils.waitForFileDeleted("/data/apex/active/" + testApexFile,
+ mHostUtils.waitForFileDeleted("/data/apex/active/" + testApexFile,
Duration.ofMinutes(3));
} finally {
getDevice().executeShellV2Command("rm /data/apex/active/" + testApexFile);
@@ -87,14 +119,14 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
@Test
public void testRemountApex() throws Exception {
- assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
final File oldFile = getDevice().pullFile(SHIM_APEX_PATH);
try {
getDevice().remountSystemWritable();
// In case remount requires a reboot, wait for boot to complete.
getDevice().waitForBootComplete(Duration.ofMinutes(3).toMillis());
- final File newFile = mTestUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ final File newFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// Stop framework
getDevice().executeShellV2Command("stop");
// Push new shim APEX. This simulates adb sync.
@@ -118,7 +150,7 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
@Test
public void testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion()
throws Exception {
- assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
try {
@@ -127,10 +159,10 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
assertWithMessage("Timed out waiting for device to boot").that(
getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
- final File v1 = mTestUtils.getTestFile("apex.apexd_test.apex");
+ final File v1 = mHostUtils.getTestFile("apex.apexd_test.apex");
getDevice().pushFile(v1, "/product/apex/apex.apexd_test.apex");
- final File v2_no_pb = mTestUtils.getTestFile("apex.apexd_test_v2_no_pb.apex");
+ final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex");
getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex");
getDevice().reboot();
@@ -144,7 +176,7 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
"com.android.apex.test_package", 2L));
// v2_no_pb should be deleted
- mTestUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex",
+ mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex",
Duration.ofMinutes(3));
} finally {
getDevice().remountSystemWritable();
@@ -159,7 +191,7 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
@Test
public void testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion()
throws Exception {
- assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
try {
@@ -168,10 +200,10 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
assertWithMessage("Timed out waiting for device to boot").that(
getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
- final File v3 = mTestUtils.getTestFile("apex.apexd_test_v3.apex");
+ final File v3 = mHostUtils.getTestFile("apex.apexd_test_v3.apex");
getDevice().pushFile(v3, "/product/apex/apex.apexd_test_v3.apex");
- final File v2_no_pb = mTestUtils.getTestFile("apex.apexd_test_v2_no_pb.apex");
+ final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex");
getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex");
getDevice().reboot();
@@ -185,7 +217,7 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
"com.android.apex.test_package", 2L));
// v2_no_pb should be deleted
- mTestUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex",
+ mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex",
Duration.ofMinutes(3));
} finally {
getDevice().remountSystemWritable();
@@ -196,4 +228,115 @@ public class ApexdHostTest extends BaseHostJUnit4Test {
getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex");
}
}
+
+ @Test
+ public void testApexInfoListIsValid() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+
+ try (FileInputStream fis = new FileInputStream(
+ getDevice().pullFile("/apex/apex-info-list.xml"))) {
+ // #1 Data got from apexd via binder
+ Set<ITestDevice.ApexInfo> fromApexd = getDevice().getActiveApexes();
+ // #2 Data got from the xml file (key is the path)
+ Map<String, ApexInfo> fromXml = XmlParser.readApexInfoList(fis).getApexInfo().stream()
+ .collect(Collectors.toMap(ApexInfo::getModulePath, ai -> ai));
+
+ // Make sure that all items in #1 are also in #2 and they are identical
+ for (ITestDevice.ApexInfo ai : fromApexd) {
+ ApexInfo apexFromXml = fromXml.get(ai.sourceDir);
+ assertWithMessage("APEX (" + ai.toString() + ") is not found in the list")
+ .that(apexFromXml).isNotNull();
+ assertWithMessage("Version mismatch for APEX (" + ai.toString() + ")")
+ .that(ai.versionCode).isEqualTo(apexFromXml.getVersionCode());
+ assertWithMessage("APEX (" + ai.toString() + ") is not active")
+ .that(apexFromXml.getIsActive()).isTrue();
+ }
+ }
+ }
+
+ /**
+ * Test to verify that the state of a staged session does not change if apexd is stopped and
+ * restarted while a session is staged.
+ */
+ @Test
+ public void testApexSessionStateUnchangedBeforeReboot() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+
+ File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ String error = mHostUtils.installStagedPackage(apexFile);
+ assertThat(error).isNull();
+ String sessionId = getDevice().executeShellCommand(
+ "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
+ assertThat(sessionId).isNotEmpty();
+ String sessionStateCmd = "cmd -w apexservice getStagedSessionInfo " + sessionId;
+ String initialState = getDevice().executeShellV2Command(sessionStateCmd).getStdout();
+ assertThat(initialState).isNotEmpty();
+
+ // Kill apexd. This means apexd will perform its start logic when the second install
+ // is staged.
+ getDevice().executeShellV2Command("kill `pidof apexd`");
+
+ // Verify that the session state remains consistent after apexd has restarted.
+ String updatedState = getDevice().executeShellV2Command(sessionStateCmd).getStdout();
+ assertThat(updatedState).isEqualTo(initialState);
+ }
+
+ /**
+ * Verifies that content of {@code /data/apex/sessions/} is migrated to the {@code
+ * /metadata/apex/sessions}.
+ */
+ @Test
+ public void testSessionsDirMigrationToMetadata() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+
+ try {
+ getDevice().executeShellV2Command("mkdir -p /data/apex/sessions/1543");
+ File file = File.createTempFile("foo", "bar");
+ getDevice().pushFile(file, "/data/apex/sessions/1543/file");
+
+ // During boot sequence apexd will move /data/apex/sessions/1543/file to
+ // /metadata/apex/sessions/1543/file.
+ getDevice().reboot();
+ assertWithMessage("Timed out waiting for device to boot").that(
+ getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
+
+ assertThat(getDevice().doesFileExist("/metadata/apex/sessions/1543/file")).isTrue();
+ assertThat(getDevice().doesFileExist("/data/apex/sessions/1543/file")).isFalse();
+ } finally {
+ getDevice().executeShellV2Command("rm -R /data/apex/sessions/1543");
+ getDevice().executeShellV2Command("rm -R /metadata/apex/sessions/1543");
+ }
+ }
+
+ @Test
+ public void testFailsToActivateApexOnDataFallbacksToPreInstalled() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+
+ try {
+ final File file =
+ mHostUtils.getTestFile("com.android.apex.cts.shim.v2_additional_file.apex");
+ getDevice().pushFile(file, "/data/apex/active/com.android.apex.cts.shim@2.apex");
+
+ getDevice().reboot();
+ assertWithMessage("Timed out waiting for device to boot").that(
+ getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
+
+ // After reboot pre-installed version of shim apex should be activated, and corrupted
+ // version on /data should be deleted.
+ final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
+ ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
+ "com.android.apex.cts.shim", 1L);
+ assertThat(activeApexes).contains(testApex);
+ assertThat(
+ getDevice()
+ .doesFileExist("/data/apex/active/com.android.apex.cts.shim@2.apex"))
+ .isFalse();
+ } finally {
+ getDevice().deleteFile("/data/apex/active/com.android.apex.cts.shim@2.apex");
+ }
+ }
}
diff --git a/tests/src/com/android/tests/apex/MediaProviderHostTest.java b/tests/src/com/android/tests/apex/MediaProviderHostTest.java
deleted file mode 100644
index 4fc72251..00000000
--- a/tests/src/com/android/tests/apex/MediaProviderHostTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.apex;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class MediaProviderHostTest extends ApexE2EBaseHostTest {
-}
diff --git a/tests/src/com/android/tests/apex/NeuralNetworksHostTest.java b/tests/src/com/android/tests/apex/NeuralNetworksHostTest.java
deleted file mode 100644
index 1effbe2a..00000000
--- a/tests/src/com/android/tests/apex/NeuralNetworksHostTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.apex;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class NeuralNetworksHostTest extends ApexE2EBaseHostTest {
-}
diff --git a/tests/src/com/android/tests/apex/PermissionHostTest.java b/tests/src/com/android/tests/apex/PermissionHostTest.java
deleted file mode 100644
index c673ade9..00000000
--- a/tests/src/com/android/tests/apex/PermissionHostTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.apex;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@Ignore("b/152717947")
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class PermissionHostTest extends ApexE2EBaseHostTest {
-}
diff --git a/tests/src/com/android/tests/apex/SharedLibsApexTest.java b/tests/src/com/android/tests/apex/SharedLibsApexTest.java
new file mode 100644
index 00000000..9097e3a2
--- /dev/null
+++ b/tests/src/com/android/tests/apex/SharedLibsApexTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2020 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 com.android.tests.apex;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.lib.host.InstallUtilsHost;
+
+import com.android.compatibility.common.util.CpuFeatures;
+import com.android.internal.util.test.SystemPreparer;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SharedLibsApexTest extends BaseHostJUnit4Test {
+
+ private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+ private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder,
+ this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+
+ enum ApexName {
+ FOO,
+ BAR,
+ BAZ,
+ PONY,
+ SHAREDLIBS,
+ SHAREDLIBS_SECONDARY
+ }
+
+ enum ApexVersion {
+ ONE,
+ TWO
+ }
+
+ enum ApexType {
+ DEFAULT,
+ STRIPPED
+ }
+
+ enum SharedLibsVersion {
+ X,
+ Y,
+ Z
+ }
+
+ /**
+ * Utility function to generate test apex names in the form e.g.:
+ * "com.android.apex.test.bar.v1.libvX.apex"
+ */
+ private String getTestApex(ApexName apexName, ApexType apexType, ApexVersion apexVersion,
+ SharedLibsVersion sharedLibsVersion) {
+ StringBuilder ret = new StringBuilder();
+ ret.append("com.android.apex.test.");
+ switch(apexName) {
+ case FOO:
+ ret.append("foo");
+ break;
+ case BAR:
+ ret.append("bar");
+ break;
+ case BAZ:
+ ret.append("baz");
+ break;
+ case PONY:
+ ret.append("pony");
+ break;
+ case SHAREDLIBS:
+ ret.append("sharedlibs_generated");
+ break;
+ case SHAREDLIBS_SECONDARY:
+ ret.append("sharedlibs_secondary_generated");
+ break;
+ }
+
+ switch(apexType) {
+ case STRIPPED:
+ ret.append("_stripped");
+ break;
+ case DEFAULT:
+ break;
+ }
+
+ switch(apexVersion) {
+ case ONE:
+ ret.append(".v1");
+ break;
+ case TWO:
+ ret.append(".v2");
+ break;
+ }
+
+ switch(sharedLibsVersion) {
+ case X:
+ ret.append(".libvX.apex");
+ break;
+ case Y:
+ ret.append(".libvY.apex");
+ break;
+ case Z:
+ ret.append(".libvZ.apex");
+ break;
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Tests basic functionality of two apex packages being force-installed and the C++ binaries
+ * contained in them being executed correctly.
+ */
+ @Test
+ public void testInstallAndRunDefaultApexs() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+
+ for (String apex : new String[]{
+ getTestApex(ApexName.BAR, ApexType.DEFAULT, ApexVersion.ONE, SharedLibsVersion.X),
+ getTestApex(ApexName.FOO, ApexType.DEFAULT, ApexVersion.ONE, SharedLibsVersion.X),
+ getTestApex(ApexName.PONY, ApexType.DEFAULT, ApexVersion.ONE, SharedLibsVersion.Z),
+ }) {
+ mPreparer.pushResourceFile(apex,
+ "/system/apex/" + apex);
+ }
+ mPreparer.reboot();
+
+ getDevice().disableAdbRoot();
+ String runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.foo/bin/foo_test");
+ assertThat(runAsResult).isEqualTo("FOO_VERSION_1 SHARED_LIB_VERSION_X");
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test32");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_1 SHARED_LIB_VERSION_X");
+ if (CpuFeatures.isX86_64(getDevice()) || CpuFeatures.isArm64(getDevice())) {
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test64");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_1 SHARED_LIB_VERSION_X");
+ }
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.pony/bin/pony_test");
+ assertThat(runAsResult).isEqualTo("PONY_VERSION_1 SHARED_LIB_VERSION_Z");
+
+ mPreparer.stageMultiplePackages(
+ new String[]{
+ getTestApex(ApexName.BAR, ApexType.DEFAULT, ApexVersion.TWO, SharedLibsVersion.Y),
+ getTestApex(ApexName.FOO, ApexType.DEFAULT, ApexVersion.TWO, SharedLibsVersion.Y),
+ },
+ new String[] {
+ "com.android.apex.test.bar",
+ "com.android.apex.test.foo",
+ }).reboot();
+
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.foo/bin/foo_test");
+ assertThat(runAsResult).isEqualTo("FOO_VERSION_2 SHARED_LIB_VERSION_Y");
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test32");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_2 SHARED_LIB_VERSION_Y");
+ if (CpuFeatures.isX86_64(getDevice()) || CpuFeatures.isArm64(getDevice())) {
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test64");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_2 SHARED_LIB_VERSION_Y");
+ }
+ }
+
+ /**
+ * Tests functionality of shared libraries apex: installs two apexs "stripped" of libc++.so and
+ * one apex containing it and verifies that C++ binaries can run.
+ */
+ @Test
+ public void testInstallAndRunOptimizedApexs() throws Exception {
+ assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+
+ // Pre-installed on /system:
+ // package bar version 1 using library version X
+ // package foo version 1 using library version X
+ // package sharedlibs version 1 exporting library version X
+ //
+ // package pony version 1 using library version Z
+ // package sharedlibs_secondary version 1 exporting library version Z
+
+ for (String apex : new String[]{
+ getTestApex(ApexName.BAR, ApexType.STRIPPED, ApexVersion.ONE, SharedLibsVersion.X),
+ getTestApex(ApexName.FOO, ApexType.STRIPPED, ApexVersion.ONE, SharedLibsVersion.X),
+ getTestApex(ApexName.PONY, ApexType.STRIPPED, ApexVersion.ONE, SharedLibsVersion.Z),
+ getTestApex(ApexName.SHAREDLIBS, ApexType.DEFAULT, ApexVersion.ONE,
+ SharedLibsVersion.X),
+ getTestApex(ApexName.SHAREDLIBS_SECONDARY, ApexType.DEFAULT, ApexVersion.ONE,
+ SharedLibsVersion.Z),
+ }) {
+ mPreparer.pushResourceFile(apex,
+ "/system/apex/" + apex);
+ }
+ mPreparer.reboot();
+
+ getDevice().disableAdbRoot();
+ String runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.foo/bin/foo_test");
+ assertThat(runAsResult).isEqualTo("FOO_VERSION_1 SHARED_LIB_VERSION_X");
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test32");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_1 SHARED_LIB_VERSION_X");
+ if (CpuFeatures.isX86_64(getDevice()) || CpuFeatures.isArm64(getDevice())) {
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test64");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_1 SHARED_LIB_VERSION_X");
+ }
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.pony/bin/pony_test");
+ assertThat(runAsResult).isEqualTo("PONY_VERSION_1 SHARED_LIB_VERSION_Z");
+
+ // Updated packages (installed on /data/apex/active):
+ // package bar version 2 using library version Y <-- new
+ // package foo version 2 using library version Y <-- new
+ // package sharedlibs version 2 exporting library version Y <-- new
+ //
+ // Pre-installed:
+ // (inactive) package bar version 1 using library version X
+ // (inactive) package foo version 1 using library version X
+ // package sharedlibs version 1 exporting library version X
+ //
+ // package pony version 1 using library version Z
+ // package sharedlibs_secondary version 1 exporting library version Z
+
+ mPreparer.stageMultiplePackages(
+ new String[]{
+ getTestApex(ApexName.BAR, ApexType.STRIPPED, ApexVersion.TWO, SharedLibsVersion.Y),
+ getTestApex(ApexName.FOO, ApexType.STRIPPED, ApexVersion.TWO, SharedLibsVersion.Y),
+ getTestApex(ApexName.SHAREDLIBS, ApexType.DEFAULT, ApexVersion.TWO,
+ SharedLibsVersion.Y),
+ },
+ new String[]{
+ "com.android.apex.test.bar",
+ "com.android.apex.test.foo",
+ "com.android.apex.test.sharedlibs",
+ }).reboot();
+
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.foo/bin/foo_test");
+ assertThat(runAsResult).isEqualTo("FOO_VERSION_2 SHARED_LIB_VERSION_Y");
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test32");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_2 SHARED_LIB_VERSION_Y");
+ if (CpuFeatures.isX86_64(getDevice()) || CpuFeatures.isArm64(getDevice())) {
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test64");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_2 SHARED_LIB_VERSION_Y");
+ }
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.pony/bin/pony_test");
+ assertThat(runAsResult).isEqualTo("PONY_VERSION_1 SHARED_LIB_VERSION_Z");
+
+ // Assume that an OTA now adds a package baz on /system needing libraries installed on
+ // /system:
+ //
+ // Updated packages (installed on /data/apex/active):
+ // package bar version 2 using library version Y
+ // package foo version 2 using library version Y
+ // package sharedlibs version 2 exporting library version Y
+ //
+ // Pre-installed:
+ // (inactive) package bar version 1 using library version X
+ // package baz version 1 using library version X <-- new
+ // (inactive) package foo version 1 using library version X
+ // package sharedlibs version 1 exporting library version X
+ // package pony version 1 using library version Z
+ // package sharedlibs_secondary version 1 exporting library version Z
+
+ String baz_apex =
+ getTestApex(ApexName.BAZ, ApexType.STRIPPED, ApexVersion.ONE, SharedLibsVersion.X);
+ mPreparer.pushResourceFile(baz_apex, "/system/apex/" + baz_apex);
+ mPreparer.reboot();
+
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.foo/bin/foo_test");
+ assertThat(runAsResult).isEqualTo("FOO_VERSION_2 SHARED_LIB_VERSION_Y");
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test32");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_2 SHARED_LIB_VERSION_Y");
+ if (CpuFeatures.isX86_64(getDevice()) || CpuFeatures.isArm64(getDevice())) {
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.bar/bin/bar_test64");
+ assertThat(runAsResult).isEqualTo("BAR_VERSION_2 SHARED_LIB_VERSION_Y");
+ }
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.baz/bin/baz_test");
+ assertThat(runAsResult).isEqualTo("BAZ_VERSION_1 SHARED_LIB_VERSION_X");
+ runAsResult = getDevice().executeShellCommand(
+ "/apex/com.android.apex.test.pony/bin/pony_test");
+ assertThat(runAsResult).isEqualTo("PONY_VERSION_1 SHARED_LIB_VERSION_Z");
+ }
+}
diff --git a/tests/src/com/android/tests/apex/StatsdHostTest.java b/tests/src/com/android/tests/apex/StatsdHostTest.java
deleted file mode 100644
index 4ad3bb8d..00000000
--- a/tests/src/com/android/tests/apex/StatsdHostTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.apex;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@Ignore // TODO(b/150452752): unignore when test is fixed.
-public class StatsdHostTest extends ApexE2EBaseHostTest {
-}
diff --git a/tests/src/com/android/tests/apex/WifiHostTest.java b/tests/src/com/android/tests/apex/WifiHostTest.java
deleted file mode 100644
index 7fd85289..00000000
--- a/tests/src/com/android/tests/apex/WifiHostTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.apex;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.runner.RunWith;
-
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class WifiHostTest extends ApexE2EBaseHostTest {
- // TODO(b/146163587): Add more checks to verify apex installation.
-}
diff --git a/tests/src/com/android/tests/apex/host/ApexCompressionTests.java b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
new file mode 100644
index 00000000..a09ca60d
--- /dev/null
+++ b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2020 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 com.android.tests.apex.host;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.lib.host.InstallUtilsHost;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Test for platform support for Apex Compression feature
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ApexCompressionTests extends BaseHostJUnit4Test {
+ private static final String COMPRESSED_APEX_PACKAGE_NAME = "com.android.apex.compressed";
+ private static final String ORIGINAL_APEX_FILE_NAME =
+ COMPRESSED_APEX_PACKAGE_NAME + ".v1_original.apex";
+ private static final String DECOMPRESSED_DIR_PATH = "/data/apex/decompressed/";
+ private static final String APEX_ACTIVE_DIR = "/data/apex/active/";
+ private static final String OTA_RESERVED_DIR = "/data/apex/ota_reserved/";
+ private static final String DECOMPRESSED_APEX_SUFFIX = ".decompressed.apex";
+
+ private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+ private boolean mWasAdbRoot = false;
+
+ @Before
+ public void setUp() throws Exception {
+ mWasAdbRoot = getDevice().isAdbRoot();
+ if (!mWasAdbRoot) {
+ assumeTrue("Requires root", getDevice().enableAdbRoot());
+ }
+ deleteFiles("/system/apex/" + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ APEX_ACTIVE_DIR + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ OTA_RESERVED_DIR + "*");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!mWasAdbRoot) {
+ getDevice().disableAdbRoot();
+ }
+ deleteFiles("/system/apex/" + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ APEX_ACTIVE_DIR + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ OTA_RESERVED_DIR + "*");
+ }
+
+ /**
+ * Runs the given phase of a test by calling into the device.
+ * Throws an exception if the test phase fails.
+ * <p>
+ * For example, <code>runPhase("testApkOnlyEnableRollback");</code>
+ */
+ private void runPhase(String phase) throws Exception {
+ assertTrue(runDeviceTests("com.android.tests.apex.app",
+ "com.android.tests.apex.app.ApexCompressionTests",
+ phase));
+ }
+
+ /**
+ * Deletes files and reboots the device if necessary.
+ * @param files the paths of files which might contain wildcards
+ */
+ private void deleteFiles(String... files) throws Exception {
+ boolean found = false;
+ for (String file : files) {
+ CommandResult result = getDevice().executeShellV2Command("ls " + file);
+ if (result.getStatus() == CommandStatus.SUCCESS) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ getDevice().remountSystemWritable();
+ for (String file : files) {
+ getDevice().executeShellCommand("rm -rf " + file);
+ }
+ getDevice().reboot();
+ }
+ }
+
+ private void pushTestApex(final String fileName) throws Exception {
+ final File apex = mHostUtils.getTestFile(fileName);
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ getDevice().reboot();
+ }
+
+ private List<String> getFilesInDir(String baseDir) throws DeviceNotAvailableException {
+ return getDevice().getFileEntry(baseDir).getChildren(false)
+ .stream().map(entry -> entry.getName())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Returns the active apex info as optional.
+ */
+ private Optional<ITestDevice.ApexInfo> getActiveApexInfo(String packageName)
+ throws DeviceNotAvailableException {
+ return getDevice().getActiveApexes().stream().filter(
+ apex -> apex.name.equals(packageName)).findAny();
+ }
+
+ @Test
+ @LargeTest
+ public void testDecompressedApexIsConsideredFactory() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ runPhase("testDecompressedApexIsConsideredFactory");
+ }
+
+ @Test
+ @LargeTest
+ public void testCompressedApexIsDecompressedAndActivated() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+
+ // Ensure that compressed APEX was decompressed in DECOMPRESSED_DIR_PATH
+ List<String> files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+
+ // Match the decompressed apex with original byte for byte
+ final File originalApex = mHostUtils.getTestFile(ORIGINAL_APEX_FILE_NAME);
+ final byte[] originalApexFileBytes = Files.readAllBytes(Paths.get(originalApex.toURI()));
+ final File decompressedFile = getDevice().pullFile(
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "@1"
+ + DECOMPRESSED_APEX_SUFFIX);
+ final byte[] decompressedFileBytes =
+ Files.readAllBytes(Paths.get(decompressedFile.toURI()));
+ assertThat(decompressedFileBytes).isEqualTo(originalApexFileBytes);
+
+ // The decompressed APEX should note be hard linked to APEX_ACTIVE_DIR
+ files = getFilesInDir(APEX_ACTIVE_DIR);
+ assertThat(files).doesNotContain(
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ }
+
+ @Test
+ @LargeTest
+ public void testDecompressedApexSurvivesReboot() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+
+ // Ensure that compressed APEX was activated from DECOMPRESSED_DIR_PATH
+ List<String> files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ final File decompressedFile = getDevice().pullFile(
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "@1"
+ + DECOMPRESSED_APEX_SUFFIX);
+ final byte[] decompressedFileBytes =
+ Files.readAllBytes(Paths.get(decompressedFile.toURI()));
+
+ getDevice().reboot();
+
+ // Ensure it gets activated again on reboot
+ files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ final File decompressedFileAfterReboot = getDevice().pullFile(
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "@1"
+ + DECOMPRESSED_APEX_SUFFIX);
+ final byte[] decompressedFileBytesAfterReboot =
+ Files.readAllBytes(Paths.get(decompressedFileAfterReboot.toURI()));
+ assertThat(decompressedFileBytes).isEqualTo(decompressedFileBytesAfterReboot);
+ }
+
+ @Test
+ @LargeTest
+ public void testDecompressionDoesNotHappenOnEveryReboot() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+
+ final String decompressedApexFilePath = DECOMPRESSED_DIR_PATH
+ + COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX;
+ String lastModifiedTime1 =
+ getDevice().executeShellCommand("stat -c %Y " + decompressedApexFilePath);
+
+ getDevice().reboot();
+ getDevice().waitForDeviceAvailable();
+
+ String lastModifiedTime2 =
+ getDevice().executeShellCommand("stat -c %Y " + decompressedApexFilePath);
+ assertThat(lastModifiedTime1).isEqualTo(lastModifiedTime2);
+ }
+
+ @Test
+ @LargeTest
+ public void testHigherVersionOnSystemTriggerDecompression() throws Exception {
+ // Install v1 on /system partition
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ // On boot, /data partition will have decompressed v1 APEX in it
+ List<String> files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+
+ // Now replace /system APEX with v2
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand("rm -rf /system/apex/"
+ + COMPRESSED_APEX_PACKAGE_NAME + "*apex");
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v2.capex");
+
+ // Ensure that v2 was decompressed
+ files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@2" + DECOMPRESSED_APEX_SUFFIX);
+ }
+
+
+ @Test
+ @LargeTest
+ public void testDifferentRootDigestTriggersDecompression() throws Exception {
+ // Install v1 on /system partition
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ // On boot, /data partition will have decompressed v1 APEX in it
+ List<String> files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ final File decompressedFile = getDevice().pullFile(
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "@1"
+ + DECOMPRESSED_APEX_SUFFIX);
+ final byte[] decompressedFileBytes =
+ Files.readAllBytes(Paths.get(decompressedFile.toURI()));
+
+ // Now replace /system APEX with same version but different root digest
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand("rm -rf /system/apex/"
+ + COMPRESSED_APEX_PACKAGE_NAME + "*apex");
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1_different_digest.capex");
+
+ // Ensure that decompressed APEX is different than before
+ files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ final File decompressedFileAfterReboot = getDevice().pullFile(
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "@1"
+ + DECOMPRESSED_APEX_SUFFIX);
+ final byte[] decompressedFileBytesAfterReboot =
+ Files.readAllBytes(Paths.get(decompressedFileAfterReboot.toURI()));
+ assertThat(decompressedFileBytes).isNotEqualTo(decompressedFileBytesAfterReboot);
+ }
+
+ @Test
+ @LargeTest
+ public void testUnusedDecompressedApexIsCleanedUp_HigherVersion() throws Exception {
+ // Install v1 on /system partition
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ // Ensure that compressed APEX was decompressed in DECOMPRESSED_DIR_PATH
+ List<String> files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+
+ // Now install an update for that APEX so that decompressed APEX becomes redundant
+ runPhase("testUnusedDecompressedApexIsCleanedUp_HigherVersion");
+ getDevice().reboot();
+
+ // Verify that DECOMPRESSED_DIR_PATH does not contain the decompressed APEX
+ files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).doesNotContain(
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ }
+
+ @Test
+ @LargeTest
+ public void testUnusedDecompressedApexIsCleanedUp_SameVersion() throws Exception {
+ // Install v1 on /system partition
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ // Ensure that compressed APEX was decompressed in DECOMPRESSED_DIR_PATH
+ List<String> files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+
+ // Now install an update for that APEX so that decompressed APEX becomes redundant
+ runPhase("testUnusedDecompressedApexIsCleanedUp_SameVersion");
+ getDevice().reboot();
+
+ // Verify that DECOMPRESSED_DIR_PATH does not contain the decompressed APEX
+ files = getFilesInDir(DECOMPRESSED_DIR_PATH);
+ assertThat(files).doesNotContain(
+ COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ }
+
+ @Test
+ @LargeTest
+ public void testReservedSpaceIsNotCleanedOnReboot() throws Exception {
+ getDevice().executeShellCommand("touch " + OTA_RESERVED_DIR + "random");
+
+ getDevice().reboot();
+
+ List<String> files = getFilesInDir(OTA_RESERVED_DIR);
+ assertThat(files).hasSize(1);
+ assertThat(files).contains("random");
+ }
+
+ @Test
+ @LargeTest
+ public void testReservedSpaceIsCleanedUpOnDecompression() throws Exception {
+ getDevice().executeShellCommand("touch " + OTA_RESERVED_DIR + "random1");
+ getDevice().executeShellCommand("touch " + OTA_RESERVED_DIR + "random2");
+
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+
+ assertThat(getFilesInDir(OTA_RESERVED_DIR)).isEmpty();
+ }
+
+ @Test
+ @LargeTest
+ public void testFailsToActivateApexOnDataFallbacksToPreInstalled() throws Exception {
+ // Push a data apex that will fail to activate
+ final File file =
+ mHostUtils.getTestFile("com.android.apex.compressed.v2_manifest_mismatch.apex");
+ getDevice().pushFile(file, APEX_ACTIVE_DIR + COMPRESSED_APEX_PACKAGE_NAME + "@2.apex");
+ // Push a CAPEX which should act as the fallback
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v2.capex");
+ assertWithMessage("Timed out waiting for device to boot").that(
+ getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
+
+ // After reboot pre-installed version of shim apex should be activated, and corrupted
+ // version on /data should be deleted.
+ final ITestDevice.ApexInfo activeApex =
+ getActiveApexInfo(COMPRESSED_APEX_PACKAGE_NAME).get();
+ assertThat(activeApex.versionCode).isEqualTo(2);
+ assertThat(getDevice().doesFileExist(
+ DECOMPRESSED_DIR_PATH + COMPRESSED_APEX_PACKAGE_NAME + "@2"
+ + DECOMPRESSED_APEX_SUFFIX)).isTrue();
+ assertThat(getDevice().doesFileExist(
+ APEX_ACTIVE_DIR + COMPRESSED_APEX_PACKAGE_NAME + "@2"
+ + DECOMPRESSED_APEX_SUFFIX)).isFalse();
+ assertThat(getDevice().doesFileExist(
+ APEX_ACTIVE_DIR + COMPRESSED_APEX_PACKAGE_NAME + "@2.apex")).isFalse();
+ }
+
+ @Test
+ @LargeTest
+ public void testCapexToApexSwitch() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ assertThat(getFilesInDir(DECOMPRESSED_DIR_PATH))
+ .contains(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+
+ // Now replace the CAPEX with an uncompressed APEX
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand("rm -rf /system/apex/"
+ + COMPRESSED_APEX_PACKAGE_NAME + "*apex");
+ pushTestApex(ORIGINAL_APEX_FILE_NAME);
+ runPhase("testCapexToApexSwitch");
+
+ // Ensure active apex is running from /system
+ final ITestDevice.ApexInfo activeApex = getActiveApexInfo(COMPRESSED_APEX_PACKAGE_NAME)
+ .orElseThrow(() -> new AssertionError(
+ "Can't find " + COMPRESSED_APEX_PACKAGE_NAME));
+ assertThat(activeApex.sourceDir).startsWith("/system");
+ // Ensure previous decompressed APEX has been cleaned up
+ assertThat(getFilesInDir(DECOMPRESSED_DIR_PATH))
+ .doesNotContain(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ }
+
+ @Test
+ @LargeTest
+ public void testDecompressedApexVersionAlwaysHasSameVersionAsCapex() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v2.capex");
+ // Now replace /system APEX with v1
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand("rm -rf /system/apex/"
+ + COMPRESSED_APEX_PACKAGE_NAME + "*apex");
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+ runPhase("testDecompressedApexVersionAlwaysHasSameVersionAsCapex");
+ }
+
+ @Test
+ @LargeTest
+ public void testCompressedApexCanBeRolledBack() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+
+ // Now install update with rollback
+ runPhase("testCompressedApexCanBeRolledBack_Commit");
+ getDevice().reboot();
+
+ // Rollback the apex
+ runPhase("testCompressedApexCanBeRolledBack_Rollback");
+ getDevice().reboot();
+
+ runPhase("testCompressedApexCanBeRolledBack_Verify");
+ }
+
+ @Test
+ @LargeTest
+ public void testOrphanedDecompressedApexInActiveDirIsIgnored() throws Exception {
+ final File apex = mHostUtils.getTestFile(
+ COMPRESSED_APEX_PACKAGE_NAME + ".v1_original.apex");
+ // Prepare an APEX in active directory with .decompressed.apex suffix.
+ // Place the same apex in system too. When booting, system APEX should
+ // be mounted while the decomrpessed APEX in active direcotyr should
+ // be ignored.
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex,
+ APEX_ACTIVE_DIR + COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX));
+ assertTrue(getDevice().pushFile(apex,
+ "/system/apex/" + COMPRESSED_APEX_PACKAGE_NAME + ".v1.apex"));
+ getDevice().reboot();
+ // Ensure active apex is running from /system
+ final ITestDevice.ApexInfo activeApex = getActiveApexInfo(COMPRESSED_APEX_PACKAGE_NAME)
+ .orElseThrow(() -> new AssertionError(
+ "Can't find " + COMPRESSED_APEX_PACKAGE_NAME));
+ assertThat(activeApex.sourceDir).startsWith("/system");
+ // Ensure orphaned decompressed APEX has been cleaned up
+ assertThat(getFilesInDir(APEX_ACTIVE_DIR))
+ .doesNotContain(COMPRESSED_APEX_PACKAGE_NAME + "@1" + DECOMPRESSED_APEX_SUFFIX);
+ }
+}
+
diff --git a/tests/src/com/android/tests/util/ModuleTestUtilsTest.java b/tests/src/com/android/tests/util/ModuleTestUtilsTest.java
deleted file mode 100644
index 833974f8..00000000
--- a/tests/src/com/android/tests/util/ModuleTestUtilsTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.util;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class ModuleTestUtilsTest extends BaseHostJUnit4Test {
- private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
- private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
-
- @Before
- public void setUp() throws Exception {
- mUtils.abandonActiveStagedSession();
- mUtils.uninstallShimApexIfNecessary();
- }
- /**
- * Uninstalls any version greater than 1 of shim apex and reboots the device if necessary
- * to complete the uninstall.
- */
- @After
- public void tearDown() throws Exception {
- mUtils.abandonActiveStagedSession();
- mUtils.uninstallShimApexIfNecessary();
- }
-
- /**
- * Unit test for {@link ModuleTestUtils#abandonActiveStagedSession()}
- */
- @Test
- public void testAbandonActiveStagedSession() throws Exception {
- File apexFile = mUtils.getTestFile(SHIM_V2);
-
- // Install apex package
- String installResult = getDevice().installPackage(apexFile, false, "--wait");
- assertWithMessage(String.format("failed to install apex first time %s. Reason: %s",
- SHIM_V2, installResult)).that(installResult).isNull();
-
- // Abandon ready session
- mUtils.abandonActiveStagedSession();
-
- // Install apex again
- installResult = getDevice().installPackage(apexFile, false, "--wait");
- assertWithMessage(String.format("failed to install apex again %s. Reason: %s",
- SHIM_V2, installResult)).that(installResult).isNull();
- }
-}
diff --git a/tests/statsd-e2e-tests.xml b/tests/statsd-e2e-tests.xml
deleted file mode 100644
index b3a270ce..00000000
--- a/tests/statsd-e2e-tests.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Configuration for statsd module e2e test cases">
- <option name="test-suite-tag" value="statsd_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
-
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="statsd_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.os.statsd.apex" />
- </test>
-</configuration>
diff --git a/tests/testdata/init.rc b/tests/testdata/init.rc
index 1c90c830..4e89f444 100644
--- a/tests/testdata/init.rc
+++ b/tests/testdata/init.rc
@@ -6,7 +6,7 @@ service surfaceflinger /apex/com.android.apex.test/bin/surfaceflinger
user system
group graphics drmrpc readproc
onrestart restart zygote
- writepid /dev/stune/foreground/tasks
+ task_profiles HighPerformance
socket pdx/system/vr/display/client stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0
socket pdx/system/vr/display/manager stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0
socket pdx/system/vr/display/vsync stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0
diff --git a/tests/testdata/sharedlibs/README.md b/tests/testdata/sharedlibs/README.md
new file mode 100644
index 00000000..a59686da
--- /dev/null
+++ b/tests/testdata/sharedlibs/README.md
@@ -0,0 +1,13 @@
+### Test artifacts for shared libraries APEX support
+
+This directory contains APEX packages used for testing the platform support for
+moving shared libraries used by binaries within an APEX package into another
+APEX package.
+
+Due to the peculiarity of the build needs, this directory contains prebuilt
+artifacts used by tests. In order to regenerate these artifacts, run from the
+root of the tree:
+
+```shell script
+./system/apex/tests/testdata/sharedlibs/build/build_artifacts.sh
+``` \ No newline at end of file
diff --git a/tests/testdata/sharedlibs/build/Android.bp b/tests/testdata/sharedlibs/build/Android.bp
new file mode 100644
index 00000000..74b08a25
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/Android.bp
@@ -0,0 +1,86 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "noop",
+ srcs: ["noop.cc"],
+ shared_libs: [
+ "libsharedlibtest",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ compile_multilib: "both",
+
+ apex_available: [
+ "com.android.apex.test.sharedlibs_stub",
+ "com.android.apex.test.sharedlibs_secondary_stub",
+ ],
+}
+
+python_binary_host {
+ name: "shared_libs_repack",
+ srcs: [
+ "shared_libs_repack.py",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ },
+ libs: [
+ "apex_build_info_proto",
+ "apex_manifest_proto",
+ ],
+ required: [
+ "apexer",
+ "signapk",
+ ],
+}
+
+cc_library_shared {
+ name: "libsharedlibtest",
+ srcs: [ "sharedlibstest.cpp", ],
+
+ local_include_dirs: [
+ "include",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+ apex_available: [
+ "com.android.apex.test.bar",
+ "com.android.apex.test.baz",
+ "com.android.apex.test.foo",
+ "com.android.apex.test.pony",
+ "com.android.apex.test.sharedlibs_stub",
+ "com.android.apex.test.sharedlibs_secondary_stub",
+ ],
+}
diff --git a/tests/testdata/sharedlibs/build/build_artifacts.sh b/tests/testdata/sharedlibs/build/build_artifacts.sh
new file mode 100755
index 00000000..8dbc64f5
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/build_artifacts.sh
@@ -0,0 +1,206 @@
+#!/bin/bash -e
+
+# List of files required in output. Every other file generated will be skipped.
+OUTFILES=(
+ com.android.apex.test.bar_stripped.v1.libvX.apex
+ com.android.apex.test.bar_stripped.v2.libvY.apex
+ com.android.apex.test.bar.v1.libvX.apex
+ com.android.apex.test.bar.v2.libvY.apex
+ com.android.apex.test.baz_stripped.v1.libvX.apex
+ com.android.apex.test.foo_stripped.v1.libvX.apex
+ com.android.apex.test.foo_stripped.v2.libvY.apex
+ com.android.apex.test.foo.v1.libvX.apex
+ com.android.apex.test.foo.v2.libvY.apex
+ com.android.apex.test.pony_stripped.v1.libvZ.apex
+ com.android.apex.test.pony.v1.libvZ.apex
+ com.android.apex.test.sharedlibs_generated.v1.libvX.apex
+ com.android.apex.test.sharedlibs_generated.v2.libvY.apex
+ com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
+)
+
+# "apex" type build targets to build.
+APEX_TARGETS=(
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.bar:com.android.apex.test.bar
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.foo:com.android.apex.test.foo
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.pony:com.android.apex.test.pony
+)
+
+# "genrule" type build targets to build, and directory they are built from.
+GENRULE_TARGETS=(
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.bar:com.android.apex.test.bar_stripped
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.baz:com.android.apex.test.baz_stripped
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.foo:com.android.apex.test.foo_stripped
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.pony:com.android.apex.test.pony_stripped
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs:com.android.apex.test.sharedlibs_generated
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary:com.android.apex.test.sharedlibs_secondary_generated
+)
+
+if [ ! -e "build/make/core/Makefile" ]; then
+ echo "$0 must be run from the top of the tree"
+ exit 1
+fi
+
+OUT_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var OUT_DIR)
+DIST_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var DIST_DIR)
+TMPDIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var TMPDIR)
+
+manifestdirs=()
+
+for t in "${APEX_TARGETS[@]}" "${GENRULE_TARGETS[@]}"; do
+ IFS=: read -a ar <<< "${t}"
+ manifestdirs+=( ${ar[0]})
+done
+
+manifestdirs=($(printf "%s\n" "${manifestdirs[@]}" | sort -u))
+
+generated_artifacts=()
+
+archs=(
+ arm
+ arm64
+ x86
+ x86_64
+)
+
+apexversions=(
+ 1
+ 2
+)
+
+libversions=(
+ X
+ Y
+ Z
+)
+
+for arch in "${archs[@]}"; do
+ for apexversion in "${apexversions[@]}"; do
+ apexfingerprint="VERSION_${apexversion}"
+ sed -i "s/#define FINGERPRINT .*/#define FINGERPRINT \"${apexfingerprint}\"/g" \
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.bar/bar_test.cc \
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.baz/baz_test.cc \
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.foo/foo_test.cc \
+ system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.pony/pony_test.cc
+
+ for d in "${manifestdirs[@]}"; do
+ sed -i "s/ \"version\": .*/ \"version\": ${apexversion}/g" \
+ ${d}/manifest.json
+ done
+ for libversion in "${libversions[@]}"; do
+ # Check if we need to build this combination of versions.
+ found=n
+ for t in "${APEX_TARGETS[@]}" "${GENRULE_TARGETS[@]}"; do
+ IFS=: read -a ar <<< "${t}"
+ outfile=${ar[1]}.v${apexversion}.libv${libversion}.apex
+ if printf '%s\n' "${OUTFILES[@]}" | grep -q -F "${outfile}"; then
+ found=y
+ break
+ fi
+ done
+ if [ "${found}" != "y" ]; then
+ # Skipping this combination.
+ continue
+ fi
+
+ echo "Building combination arch: ${arch}, apexversion: ${apexversion}, libversion: ${libversion}"
+ libfingerprint="VERSION_${libversion}"
+ sed -i "s/#define FINGERPRINT .*/#define FINGERPRINT \"${libfingerprint}\"/g" \
+ system/apex/tests/testdata/sharedlibs/build/sharedlibstest.cpp
+
+ build/soong/soong_ui.bash \
+ --make-mode \
+ TARGET_PRODUCT=aosp_${arch} \
+ dist sharedlibs_test
+
+ for t in "${APEX_TARGETS[@]}" "${GENRULE_TARGETS[@]}"; do
+ IFS=: read -a ar <<< "${t}"
+ outfile=${ar[1]}.v${apexversion}.libv${libversion}.apex
+ if printf '%s\n' "${OUTFILES[@]}" | grep -q -P "^${outfile}\$"; then
+ cp -v \
+ "${DIST_DIR}"/"${ar[1]}".apex \
+ system/apex/tests/testdata/sharedlibs/prebuilts/${arch}/${outfile}
+ generated_artifacts+=(system/apex/tests/testdata/sharedlibs/prebuilts/${arch}/${outfile})
+ fi
+ done
+ done
+ done
+done
+
+# Generate the Android.bp file for the prebuilts.
+tmpfile=$(mktemp)
+
+cat > "${tmpfile}" << EOF
+// Copyright (C) 2020 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.
+
+// This file is auto-generated by
+// ./system/apex/tests/testdata/sharedlibs/build/build_artifacts.sh
+// Do NOT edit manually.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+EOF
+
+artifacts_filenames=()
+for artifact in "${generated_artifacts[@]}"; do
+ artifacts_filenames+=($(basename ${artifact}))
+done
+
+artifacts_filenames=($(printf '%s\n' "${artifacts_filenames[@]}" | sort -u))
+
+for artifact in "${artifacts_filenames[@]}"; do
+ outfile=$(basename "${artifact}")
+ # remove .apex suffix
+ rulename=${outfile%.apex}
+
+ cat >> "${tmpfile}" << EOF
+
+prebuilt_apex {
+ name: "${rulename}_prebuilt",
+ arch: {
+EOF
+
+ for arch in "${archs[@]}"; do
+ cat >> "${tmpfile}" << EOF
+ ${arch}: {
+ src: "${arch}/${outfile}",
+ },
+EOF
+ done
+
+ cat >> "${tmpfile}" << EOF
+ },
+ filename: "${outfile}",
+ installable: false,
+}
+EOF
+done
+
+mv "${tmpfile}" system/apex/tests/testdata/sharedlibs/prebuilts/Android.bp
+
+# Restore the default version string to avoid bogus diffs.
+sed -i "s/#define FINGERPRINT .*/#define FINGERPRINT \"VERSION_XXX\"/g" \
+system/apex/tests/testdata/sharedlibs/build/sharedlibstest.cpp \
+system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.bar/bar_test.cc \
+system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.baz/baz_test.cc \
+system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.foo/foo_test.cc \
+system/apex/tests/testdata/sharedlibs/build/com.android.apex.test.pony/pony_test.cc
+
+for d in "${manifestdirs[@]}"; do
+ sed -i "s/ \"version\": .*/ \"version\": 1/g" \
+ ${d}/manifest.json
+done
+
+ls -l "${generated_artifacts[@]}"
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/Android.bp b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/Android.bp
new file mode 100644
index 00000000..0c62e645
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/Android.bp
@@ -0,0 +1,96 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.test.bar.key",
+ public_key: "com.android.apex.test.bar.avbpubkey",
+ private_key: "com.android.apex.test.bar.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.test.bar.certificate",
+ certificate: "com.android.apex.test.bar",
+}
+
+apex {
+ name: "com.android.apex.test.bar",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test.bar.key",
+ installable: false,
+ binaries: [ "bar_test" ],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ updatable: false,
+ compile_multilib: "both",
+ multilib: {
+ both: {
+ binaries: [
+ "bar_test",
+ ],
+ },
+ },
+}
+
+cc_binary {
+ name: "bar_test",
+ srcs: ["bar_test.cc"],
+ shared_libs: [
+ "libsharedlibtest",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ compile_multilib: "both",
+
+ apex_available: [ "com.android.apex.test.bar" ],
+}
+
+genrule {
+ name: "com.android.apex.test.bar_stripped",
+ out: ["com.android.apex.test.bar_stripped.apex"],
+ defaults: ["apexer_test_host_tools_list"],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ srcs: [
+ ":com.android.apex.test.bar",
+ "com.android.apex.test.bar.avbpubkey",
+ "com.android.apex.test.bar.pem",
+ "com.android.apex.test.bar.pk8",
+ "com.android.apex.test.bar.x509.pem",
+ ],
+ tools: [
+ "shared_libs_repack",
+ ],
+ cmd: "$(location shared_libs_repack) " +
+ " --mode strip" +
+ " --key $(location com.android.apex.test.bar.pem)" +
+ " --input $(location :com.android.apex.test.bar)" +
+ " --output $(genDir)/com.android.apex.test.bar_stripped.apex" +
+ " --pk8key $(location com.android.apex.test.bar.pk8)" +
+ " --pubkey $(location com.android.apex.test.bar.avbpubkey)" +
+ " --x509key $(location com.android.apex.test.bar.x509.pem)" +
+ " --tmpdir $(genDir)",
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/bar_test.cc b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/bar_test.cc
new file mode 100644
index 00000000..9b397de2
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/bar_test.cc
@@ -0,0 +1,13 @@
+#include <iostream>
+#include <string>
+
+#include "sharedlibstest.h"
+
+// This parameter gets modified by the build_artifacts.sh script.
+#define FINGERPRINT "VERSION_XXX"
+
+int main() {
+ std::cout << "BAR_" << FINGERPRINT << " "
+ << sharedlibstest::getSharedLibsTestFingerprint();
+ return 0;
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.avbpubkey b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.avbpubkey
new file mode 100644
index 00000000..931a477a
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.avbpubkey
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pem
new file mode 100644
index 00000000..9363cda9
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAp/bgVatvywj6wEKD0ldJ+RxfFOv/67norL43z55gd/K/u1W4
+GoFHm0e5T1PV0j5zMi7EraIWy5g9eTmtU9DTwJoIKHw/agrVimxBsd5Los/zHAGJ
+JoRy5Wo7cenpp2QpNzfY3HdN88PMZ3FiRooa/GepoOI7IBs3IEmGJTC3N5CZYsh7
+b3yzZn4cuWuQZ+oD+G/z1AjK9Q1po3auz5lqSu1VD8GoswwfrC7G/MvH/5Jytu/o
+XKESeGIri8YDMqsVX7hHTGbQ8MBpy5RtFfIGND+XulZvTl87bXAhJVbJjD3GlZyM
+i/eZL4pUdxKjv/ZOT5bI1/NTJ6Qtr6yMgJuVbL7VHYFPISxdwXYY/fkr0AvZw+hL
+KopDrb3Jn+79elZqOuZ6l1muCpkXRs4QS4fRSh4Lnk2782p+y070WhdA+zgzDLhX
+4ytyhAGNi9wWNCjGDT3C8AOoRzn8PhG8sHsAHL6sFYpJx3LRyv/nO1jFGHDd3cgj
+Vql0NTyVPlj8JiSQ1aQvXMtf1M7pwp6igjKPf37tVrZ4gDpjQm26VSy8lcgKgR8w
+Ej4Ti4NbkcZuoJNjIfJZJAUDtr+aXJA2bWRO0ggTnuvC3sHCMH4o6i2FdNzsahYn
+z2QY7h7hiuCdXJdoFWBhSOQ9cUcBroymuSNWhliSxj7XObql2hK+u3Bzp3MCAwEA
+AQKCAgAdQrPYGNKT40+TmMLQLOa1IA0sXuSpkyyGk2izoZqaqs5d+1PkQitQUNFm
+kWtJghmdX2ph+T/RXgcvjC220UViYzMSonqFpbeHss5LBzfT+DgY4+eZry846iXK
+9X3/7EIF3ZPI7HvHAJAmYSlGsp565DA319GHCVa0KDrXVcJFSsp93AEs7eNu8n9c
+ifGROMJSUGaAxLter2R81psjjU1oGipcYVdbQbxuyYNe3L1Nt5yGZArtwB2wnSGK
+6wb5l7ZUg4zgMXUqy8pibcwHK6+LAJ0VGCOx2oNG0Gbl01WvOb/Tpn8RjyO/lXCb
+gcLHGUiRMupwPHJ7EG3pEb00VmZUUCd5bBv76A8WyS9hrGZbcHy26nS/9HkadT5Q
+IZVjWSbjZA13Tm0OmSKgwV3UC5yRZnRj4vNoku9bhWhZ+n7GCAYbSBiAVBrlIgM6
+yxw66Sbxq7jLEZEUDWLTGl00W07hvF44FHYIATsOMzHyLPbKAeozimP/sZVw+dFN
+KcNVKJtvxf/xoZIvM32BevrwqlfgQRr6G+sILzP60NlLMqQTsXlqsqZlyZ6ROrWp
+0q1T1NQBqiyNjk5Iq+nfQl0wVK1F8Xj/IrZ4udddKKunWtDIADcdx3HshkmiVVwp
+3lTadEEEA64Jwu9GyoYK78+xINkIm8/GK4LPgo4a5eRO5JaAqQKCAQEA1/DzERsj
+FqmrF3SVKt15ZqAV71YkVmlVTcE62Gr93kE0YEhkK9HrxcddkiPgkaKP/VUXpzwL
+zVsvdIlPaCdLQVoZt/c2weDvNO+8DKtb3v4AYgc8fltSCvPMzdc3VezL1Y0mZmMV
+sZndsw2ZpZnRrtHllqPQR8mC3mLgvhUyQw2yIcWPuI1L3VBTjd/9z6MDgDTZ3gKq
+h5odK5gu+KIT5hUsnxCb1Z7D3WAw/uprwpW4IFIFWbMeXPwVGuwKQ0/ueqSWQPoC
+mO5sVLd43auO9pAtp0BT8shdPkxuJiI7FNckdh8QJJOu2JkvN9O9/GZEDK2NuPZN
+ONo0hAkczxUkjQKCAQEAxx+BeFsY7H2ai55QR8TkEaK4jVuqiJZFjMipQWkM8vyM
+kYXUG/sV61gex5TZEdagxGdGyGsm58kq9cySY75CGPjCm9bPJ2qsKnKEkw4PK6BM
+eqHres2x7XjE64tF4AIYLPNVmMQKc/5Ke7uTsA2W4YYHSwDKODRFGKhgc4oI5G+w
+j5TtnqeOQ+WxseFEQEp9RgUIssO6m0K4lE9lrwTFsu95USrpJQXs6J/nZct/xXAO
+fRuv/nB7GMSj+Ay7TEfFbAw0D8NKEYzpF1/oUjPW8aihXh3Eq6HySBjRbAH8YCjC
+WZYSm7f7TLunIonRL+dqCHB3WFDsvta2nwdPdUb7/wKCAQAZquxZhi4/jV9m5Fau
+x7CcgD7bOhQLqW2YVnWWL/GJL5r4LuKpSsSJt87phhY1eWtAI5MyL7L/b+1OHtwv
+dyw80mboNRxvIzuLwUtK/jtnYC3PeSi5pEU2RBB+Dyzmq8T211ZPKUv01mNB20X+
+JzCDZTOzGjmxrsQ9hudL8N0Ol1wrI36X40O3RMsJvCxBOBE8dgvHle2LPMhm3CoJ
+J8rRuIabSbAcTkjd0YdBZb/1WzKNtPIp3V6oktY3YwM9SQ0ByvqJMq6IWx7JWx2k
+y7WsnSqwDLdtzl82/oLBSaRYL9KHr92NW3iXCm5QZnzYuZcxIpgL+krnjRhc8XBZ
+NRwpAoIBAQCntCU61J6dLvwmcuNyTqU3JTEB/R4Xg1h4RdgnOu6pB4LsXSZTmpjP
+aZwiw34+w+ELCWBYE8bkmE0ST4VLdEX++iQNVFGMBQ+TgHef0st8FrnS3uSQvQUJ
+2BkhuF7VV249DYQd8Z5MKvNYWpb8Q7W7o0IpLTUjOQKozcbOCIeMvXSauPeYE86B
+6MZL5kmxTAtOGZdF2AsmEH+ciXI+gWpwVbh7YASUJfVtxp8A4O9vvfy16cfEJ7/F
+EHh4xWBJ0ni3k1+Vlwie12rJQQFNmlOBnGCr/65QT0ja5+wZZ2LDKhDlmrt5Yu7H
+pZQSRrhj/CcVjIM3YpDB+dw8+880GuDJAoIBAD/3oFK1uG++uU/Mo+3YtZj5YWuE
+Z5FNE7tJGeyKUvKXTLUO1NTG+HEmSGkOyeiXrQNJssFOjgVzNx1kke5Pi4qv4+QL
+087gFnhDnrMHBSwcWIpkEe5zQEYrS/yRJlD63WM72ivLDNTXe/BQfuCIdX9qBHbG
+qQQzjaLdj0xtKQsidL2Cy/PlqfqGzp90I6uXbRiOrC4rsNjr9mX82atNswua0tcD
+mMGi0eJXtKOS2aaJPU9yofd4sBW+n94Ff5ue1cwfjdW/lKaAPDBGNRCgvvcyM39Q
+DWK6NSHzz7pVO5+tb54vMZK4TySPrS1qlin4AALo4tHIBuIDkaIHTmDsF8c=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pk8 b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pk8
new file mode 100644
index 00000000..6119c7f2
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.pk8
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.x509.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.x509.pem
new file mode 100644
index 00000000..e687fc56
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/com.android.apex.test.bar.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF1zCCA78CFEnLOBRbjwndwo8AjZVTyorFhju2MA0GCSqGSIb3DQEBCwUAMIGm
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEiMCAGA1UEAwwZY29t
+LmFuZHJvaWQuYXBleC50ZXN0LmJhcjAgFw0yMDEwMDUxNjMzMDVaGA80NzU4MDkw
+MTE2MzMwNVowgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQL
+DAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMSIw
+IAYDVQQDDBljb20uYW5kcm9pZC5hcGV4LnRlc3QuYmFyMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEA42Gi4Y3vNA1zQh+IDY/mQbRfvy5ikmWFTP4PwKcO
+XSg21BiTx7o7k+cKbuHjCWp3eh2O1fRXB9WXLQ3//8BNLZLUuaGWM7+fcsN5V+aK
+yPxzt/6fYBOzb4BkYnMgJjmOHBo9TL1g35IPM6O5Y3oSvCBhQuMdvZYbBDBAZ4Zn
++0PoP7fWPPUne4PGL5KF48ttpKbXeFhUjWcVnh440Amfs4+dHrblf4/f1iKUIrT8
+hNWgOBhUrHAKyKAyE/FYydVm6smLZN39m/5ac7LDwYzRBP5IDVKbLxfmwSOfj0n9
+1YvEyl/BSwY0V29AbCbsl9eVQkE2Jy1UqS7we8rK+V+Z3b/dTOh7skdAwMHGDi6v
+qIxjNr9tAX1Pxhj1PbqYM7nIa7hLmNfJk6N/k72VjJdiWyp2WcQFIJQduSbtgMS3
+CA+LzCJqEbqz5SyaFHh89KsqHPGOxcsVHI5mIRWhBioCKlCbsqvcZg+ChzdY7Hbk
+ViBhqmZr7cAXyZEqUXApSmX3E1yKCXry1WF9hl8homsG0rO1C+seS+R3OiWRNdrK
+o9Aik1gtwpoxbJH+Hxc0usS4yW3b3YyFN2tPUN2NoTFrkKNSRfDwqlWCJH8wd1Og
+4dTxyYesVLCKW0/YfPKBCPFlAqx+yDBOe7lxP/1kuWdKeIHxdhlGpjqUoaeqi+46
++xMCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAlGQyBIzKvdstBN/6uASKOHcWuqfD
+COyBXnUmMHEWawtS0C4aL0rJmvILQaVrFp3dPkjI3RpN8TZqVnQfawOewOBe/2Vk
+cpzGxsIJQpjmFmMJr730LX/RDELnTnz3VKwIbCoZBZ3qktiAI5+rGmmP4FrU8MVl
+m+VnShyxFSmLakhcyezOos3+ibJDgFsoNFDQ9b8aTUFKy4Xsa5OMSYeAJc4L2IWY
+dowDHu5wnRTfy0uCXn095GdgSiYAAvSp58M/bXuVaHXm/Qg0upWdEaouSMecAYwz
+PTF2sEpmoAUVIHOusm1Chqopa4kQtQCcVB3b/1YDZTeodjKHI7w9WLqjEeq7+fYl
+msvxlYtyL3r6JpQXyYhy1643zmI8/P1TYTi1AbsVNwz/87xmHyrldLKH+jmWMspx
+hiynKOTZahtO48WhkRGLa+iJk14ztUdD3MxAJwZiMUMFhLth/zYLGSyBwqR1smrs
+J5k7BL67b07JApjoPL1OHn9ypBrV5L2CKwSfEGEzp65BtfGIGDYyoNxEv8zky8KY
+XHxZaSWor6RQRp1QX6VRcXYk7XQHWuIevlW5W2APRXCAC05+rlVvi98swJQUcR3/
+oZCgxdr9mSV01fRZMkXv5exkhu/KR8c1ZstqRcSswRv4vrFFTFRahrTWGk0DQXU3
+EH5MwrfZ9sUq7Iw=
+-----END CERTIFICATE-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.bar/manifest.json b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/manifest.json
new file mode 100644
index 00000000..da13d6d8
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.bar/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.test.bar",
+ "version": 1
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/Android.bp b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/Android.bp
new file mode 100644
index 00000000..6076d71d
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/Android.bp
@@ -0,0 +1,77 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.test.baz.key",
+ public_key: "com.android.apex.test.baz.avbpubkey",
+ private_key: "com.android.apex.test.baz.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.test.baz.certificate",
+ certificate: "com.android.apex.test.baz",
+}
+
+apex {
+ name: "com.android.apex.test.baz",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test.baz.key",
+ installable: false,
+ binaries: [ "baz_test" ],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ updatable: false,
+}
+
+cc_binary {
+ name: "baz_test",
+ srcs: ["baz_test.cc"],
+ shared_libs: [
+ "libsharedlibtest",
+ ],
+ apex_available: [ "com.android.apex.test.baz" ],
+}
+
+genrule {
+ name: "com.android.apex.test.baz_stripped",
+ out: ["com.android.apex.test.baz_stripped.apex"],
+ defaults: ["apexer_test_host_tools_list"],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ srcs: [
+ ":com.android.apex.test.baz",
+ "com.android.apex.test.baz.avbpubkey",
+ "com.android.apex.test.baz.pem",
+ "com.android.apex.test.baz.pk8",
+ "com.android.apex.test.baz.x509.pem",
+ ],
+ tools: [
+ "shared_libs_repack",
+ ],
+ cmd: "$(location shared_libs_repack) " +
+ " --mode strip" +
+ " --key $(location com.android.apex.test.baz.pem)" +
+ " --input $(location :com.android.apex.test.baz)" +
+ " --output $(genDir)/com.android.apex.test.baz_stripped.apex" +
+ " --pk8key $(location com.android.apex.test.baz.pk8)" +
+ " --pubkey $(location com.android.apex.test.baz.avbpubkey)" +
+ " --x509key $(location com.android.apex.test.baz.x509.pem)" +
+ " --tmpdir $(genDir)",
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/baz_test.cc b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/baz_test.cc
new file mode 100644
index 00000000..ea5b341a
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/baz_test.cc
@@ -0,0 +1,13 @@
+#include <iostream>
+#include <string>
+
+#include "sharedlibstest.h"
+
+// This parameter gets modified by the build_artifacts.sh script.
+#define FINGERPRINT "VERSION_XXX"
+
+int main() {
+ std::cout << "BAZ_" << FINGERPRINT << " "
+ << sharedlibstest::getSharedLibsTestFingerprint();
+ return 0;
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.avbpubkey b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.avbpubkey
new file mode 100644
index 00000000..ef865d7a
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.avbpubkey
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pem
new file mode 100644
index 00000000..ee8b8a66
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAu48tcVfIiZ9syLm4CqkRE5/MOhfzro4epTGV0VrkOc8JYQic
+YjRAN02/7QfGk/aei3xRuVKqAqDxnZ5IE5ii6/HT9TOInMK08/vr7jc6N7dfQCSW
+nyvG3WBC2HWEmuVCGJrY1j/lmIzKuUcMgpF0ZaqB1Qm/viI/ppXZiEm234SFAg0y
+RZbiiGRy5K388xkTA0nbIAi3iRq8RSfDN4kFzIH/Y7AsNF/tzI2K+4PziGmReH/e
+ekgPhC7XUUySNrjctl7naDgvuihu18QwsC+WNCop7aHLe/ByOrOFq5R/mGsPCP6q
+JyUJ0P9YotI28y+h7rd88W6IsTQMz9jw1JENx2YGJHkjRYIy97vDDbeeyBm5qI3I
+Suy8IgzG/JW0w4pvUB6pWzfDuw+Hr4sqU7Bjb/CFeFUfzSGTg7efDN8W3L/O/wtH
++hR1t0B0BZBtW9TOFCo4z30C1u2OltT5Vr8GgsODMdR6tQCNpu4WmWfImriETuKQ
+OzPYo2eKCbsoLEQxda+EUu4h5FDH703JYWvrxCCmHeW624iXy1LavEc9YYOe0oRT
+DslIvYSHimH9jvgXRU7HLz0obljwwYRM+7aHWmTClrIC+aSEq1x0COHt8fWtbJ3f
+gBCdNSBcd0GEX55V6Ez1lytGWLzwPqedYbQnoKdIJPu5Ge7GMm75de0lgR8CAwEA
+AQKCAgBoV2e1dVt3zHwtUrxjGdkJLM3lx6tmAWRlDCfHlyP+UQJru+mb7GuJGLTb
+/YZojDt5Z8jjK2yvF7AyunpohHKmhhsffvLSGrOmRBDlrk2x706LFY/Brw3r3AB0
+ATSrIz1ZCNP2pQdqjXC+EBuSi67QXEHsLYdBFDaKyzSAUFnvEP8ZvBOqiR0vOYp9
+U5mz99AO9Uh1EsRf/sKcSlmdDJpwQiW85KZC4NcfA+M8txSFYA1wltpC9tHC/HgG
+n218Ce2nezaLUS6kBphbaqaXbXHHRWmb7HWSVpqFs5d6c5tkRLLRkzM/oahLX7KE
+qiOtuGMCtYtJmO9sfYNfIdYguy2JOylBQU1HLT2eTp/be29aUfblGpvG+4WM49+e
+da33R65viP6uvwcTuiQIZdfouJLgADutfmLAX4XGk110k/AOzSFCeIvX0oeoc/Nv
+IZDgGQjELBuyKdCEnheo3UkngXZg2hbVSoI+EQE900wm8s3vi8GoKq32VuZAiNGi
+mW6ebs1A3fOLCPrK9GdbLu6A2uXyeeQH7NMiyK9o5jWPIU7NJ3KUgGQmbNz0/aqi
+jN93DdRjeoYXUdD2oYC8S9XMqtdiUubVEMqDeGg2jhLxYDk9EfKD9KZnmFSBM7a0
+WlZlixTXi77ktJS7+1YlRWluPIhD0MXdEkHeRmgGZoc0w9ayoQKCAQEA6IrCf8U6
+SUG2QaLrIjAVcR76UETGR4gE5s5E1qvZB3/yGzou9QeBOGFz5lRQbrGD9nrNFuqq
+Z/VJmsL2aia4dPDjjJLPW3budcq8GjR/ZdGCkE+VWc3D/CUf8ja4Lx3MxDjym0cw
+i+pMysym/6QNo/sAltzQYehtQlGNUisII+p1FbHKQAZwLgczvglZObgynegSUyKT
+xrXcUyqOozU933YPkPE1X3cKyF2JFoK/YzxMCfDlYIs4SfwlFnFELbz5ug4Zx2WU
+Ouc9JWhAELYzHnbHP0OkP6mUTjSJC52XCdHg8b2Qtrof/PEuJ3Xoyz8PFBQIeRjD
+HzTJ4i0Pzwd/rwKCAQEAznrE11R6IbsAznmE5d2nmrph8n0qnpXi9Ryd5c4W6VNm
++hnelTnhAkGtNHJCloL24zPlAlb9CAGKJM7U1I21PqgZpIJ7hHTU1FLiwc83dkal
+IQ5yVbAPoa4O/qn0Omo+MQ+tYCygDHiAJFQFah+pX1Jj+6djn7kKNCzpVeN6aVFu
+hblHAOgwhDlt+l461wjYsI39osPfgInO/z/oY0WuIFEYCA5kWXEuNImqZi7GTcs3
+EQVR+OxTxwt9IUJVeTAusv8V5DFqlojF9jZVBB60VcfMAEyjsAF0/ZsweuBU3jnD
+mAln3pIS4AON0zGM/eZFKKkHZT+ZVqIUOVCFoyGBkQKCAQEAwmX10SCM6F7hwR80
+WCFAW4/dDCtiYrwn9NctHxUMWsOwHujWBosekIaPgFat4svNmMjyGJ1WlY+t143y
+t6zk+QXEBGlapYjYMmqoM3P9qJ2r+348SZXFqE1U1oS+Fs1fuA4vanXp9J2LUuIh
+HYcEzDfyNywjnCXU6OMKNE27AWNoPBmkDUAUmbX1oIFqMOF2lyFB6HP4e97ecDwc
+f/3rWpr0ymOLDeKThgsDpmjpHEl0+76B0uKvzNHYI1nO+DmJvus4y8N0VoWnTVVI
+cXAPbgE38gBXF81pKLOseaRldpUY6p5hkxAn26m3vs9ILFjr/wn8R1fXDohv2P94
+vsbzCwKCAQAkXsu9gkvhFSeXNyCJvPmA78PBCvsu5AgOVPQbPqoaf25sL5Jdhsxz
+sU3pJxdDm94RN1rnhpsbhenngedLaYq7drDNoY5QTqQOomr+6JlEZD1CDWFmZpTa
+TeamRRmYEI7T5YcMoc+vYqpvu70YbGtRNxoVge6ye82oUyDm2CL/2jA1reUr67pg
+EB2nNGH47r38m4ZJ3WbJJX0oyQEOO3/ogWBSSvayKpWQ+47gYOzdVyZkASPnTPmU
+3hk0epLDvhD7xqL8hxfXXFBChl+DUkVBtufgRZ+vqRIKegOYIVvRqSsi5MU/F0vr
+2bRptxi2wJD+EIgU9Zb1A6e8UMq5aXWBAoIBAQDS8w0TG4R8SLHXOs9JagC53zAx
+qVV8mrL9BEYzeuM6vNpyZIO75O94q2ifGvzsISxLf2xom99BxpcWo9UAyHFgubL3
++P0spdJSeP0OWJgldEqwWGMrzQbYZomzi/QUlFpXNZfHgHZLaCK7qKu9RDQLVtug
+5i+yVjSKl6RcaCCG9E2u68yxKlI246RNK1HZXQgxsnz6BnP/cqAn9G+yzT9p6nmK
+tt2d2s35MS1zV9YzACi2idsZBeio7bghC1maj/TJvq9gRwIRN4UpbPRsflpdejFY
+nEEW+tEbNzFrepkw08+9dRGnPU1G1NYvyQqMhc98lVBiMoDiH8X18gVWafhR
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pk8 b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pk8
new file mode 100644
index 00000000..37948c1a
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.pk8
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.x509.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.x509.pem
new file mode 100644
index 00000000..d41d86c7
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/com.android.apex.test.baz.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF1zCCA78CFAgqiNJewYXPYz+41LOIDLe9lXnzMA0GCSqGSIb3DQEBCwUAMIGm
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEiMCAGA1UEAwwZY29t
+LmFuZHJvaWQuYXBleC50ZXN0LmJhejAgFw0yMDExMTAwOTIzNTJaGA80NzU4MTAw
+NzA5MjM1MlowgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQL
+DAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMSIw
+IAYDVQQDDBljb20uYW5kcm9pZC5hcGV4LnRlc3QuYmF6MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAtm4cczWBEzR+kzRPHsBh61ihAQNtWN2mKLhZZKRS
+YrNZ59+M53menjsc1Bs5fYvKTMGj2u2a/yvq0QiSAlgVMY+1aZ40FN22tBTmy4yr
+6iTMi9Q+uUbQJlHLWSy8n3oFuxEHAP96ZPDJyqPHSJpKeSPKIgwyw3I+nXb/fmB4
+HvBmjHbFnwvKqHFyoEm5p/wGZn2vMtxZqlignNtUZr2zYTJW9YCuL80L/o1jdAFk
+5sHH1tBEcp1ZTXRgKm2XFfp7fqfOWkS9XL3VUF0LVWMgyv0u2fr7n8TPZb8iz5PA
+naCUN9hCoPrj4vg29eVYy2IoeSrLOAUiK986mZDToKbhxuLSHVV+IRNnqIQV0+mu
+rR8OCqDlWbD+fU29cacAjGFt72unLAIEE49GHZFS2pTZ0cDXX1bBUrBVTMlcnCjn
+YiU7XtPaJGelBkJEu/ErjHE9TrsQGKkzMwd2ySTOsk3K4OtWl+E9i50TW8Vu7gR+
+Qr4lZvY90OmXw+k02pkCo5g1GU8uXoTPjd0JQsmiEk01dMIPyWxXBYiuiRUxu3mc
+vYJJGKZSMX3VKKYmtFN9lTuf/OyjztHjRQPit9sWVcYgKuy0kw1LQSJxtyNQIeFm
+ipZVzh8Wo2TMm8argWMPHxyIfjEiVbNhXirC4My0Z02agyyB4Edxg2jkiKQBss3y
+/IECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEArOMz9Hn03yy2ano628v0wXMFixVA
+/XzSpb8GWi4GzJxV96c9t6QQPVdo/XS0uBuEa2Uc0/W5icU9+iKzBvQHM3MI1jI6
+/oj8/mGAzvyvIA0pdKP1XOvugsgi2UDNr5QNgZ1UPIpVkzeBQpLgTiWL70Pl/znE
+b7Q1nKEFeNWxBaMk6u6n6gNh6sMLv1doSCi0FM1cnWHv/0qxPizGjTHKmGs4TbMZ
+zWnBoH8XSMxruAEbucl1E7vYXgYthOW0I+SCFpyP53e9VdoNNYWMeNKCm3EoIY86
+XWLArBBQcCCrsLas45670ouQ/H9Yn498MwFinuWcsfROghXQwhn7fPaDQ2oVlftj
+4LQS2vD20mJV15wa/1n/VDzRAxIZwPdtbJX6JZO8E7Oc0+WSehwhaU+x7k9GYyBv
+tiEGS297qZC0/WvoE3VtRHZjOzphxt6PLelCEoqhdZy+q0uGxj8TmPRXo9xtmBLH
+6GPgZe3dR2SJ4uMqBjt6/6/Rki3du3btAn2O4b0Zf6h3wftqK6INbsJ7fRpIgLaU
+GvCZiCUZaxkNcCO94RnZJrTTVO1rJ1ZBkcqyMHvrSRhPCAyH2zVtHcNDCiR06GNA
++7P5SuyntvmnZKteKc2HdTnm42ewIXyDIwTs1crc8/luevw81s+SJqWumSGTxpfU
+IAM423xlNZfBXZA=
+-----END CERTIFICATE-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.baz/manifest.json b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/manifest.json
new file mode 100644
index 00000000..1c57f3af
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.baz/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.test.baz",
+ "version": 1
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/Android.bp b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/Android.bp
new file mode 100644
index 00000000..451f8a01
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/Android.bp
@@ -0,0 +1,77 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.test.foo.key",
+ public_key: "com.android.apex.test.foo.avbpubkey",
+ private_key: "com.android.apex.test.foo.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.test.foo.certificate",
+ certificate: "com.android.apex.test.foo",
+}
+
+apex {
+ name: "com.android.apex.test.foo",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test.foo.key",
+ installable: false,
+ binaries: [ "foo_test" ],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ updatable: false,
+}
+
+cc_binary {
+ name: "foo_test",
+ srcs: ["foo_test.cc"],
+ shared_libs: [
+ "libsharedlibtest",
+ ],
+ apex_available: [ "com.android.apex.test.foo" ],
+}
+
+genrule {
+ name: "com.android.apex.test.foo_stripped",
+ out: ["com.android.apex.test.foo_stripped.apex"],
+ defaults: ["apexer_test_host_tools_list"],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ srcs: [
+ ":com.android.apex.test.foo",
+ "com.android.apex.test.foo.avbpubkey",
+ "com.android.apex.test.foo.pem",
+ "com.android.apex.test.foo.pk8",
+ "com.android.apex.test.foo.x509.pem",
+ ],
+ tools: [
+ "shared_libs_repack",
+ ],
+ cmd: "$(location shared_libs_repack) " +
+ " --mode strip" +
+ " --key $(location com.android.apex.test.foo.pem)" +
+ " --input $(location :com.android.apex.test.foo)" +
+ " --output $(genDir)/com.android.apex.test.foo_stripped.apex" +
+ " --pk8key $(location com.android.apex.test.foo.pk8)" +
+ " --pubkey $(location com.android.apex.test.foo.avbpubkey)" +
+ " --x509key $(location com.android.apex.test.foo.x509.pem)" +
+ " --tmpdir $(genDir)",
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.avbpubkey b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.avbpubkey
new file mode 100644
index 00000000..575ba511
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.avbpubkey
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pem
new file mode 100644
index 00000000..1d651b58
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAz2ykMX5GHx1z+1BpFcNwnZv/8AladKxjG962kBuZrxSpu3pb
+8JKTqajY1TNmM7Hs0P8RjDY0M2ryNWodNu7GZKSOz7L+LztHeJFuZByCHjh4em79
+sYivhJKqlX0EVCnxW+IPQPQqxJKDoFBGWo/HfbhXiZJR5OT/XrNhl9KfpFL1NHfU
+dmZHIqFJHCrcQ4jQNxAh9XIvw0bwEwWe/q3Hdxs7UC4mH4m0Hz3cmKe73H0al3FW
+2L3xgy5vmc93SgzO0Csrmcrsp84B3qtNd6B0Xna52AEmBMuhWMRiFlIpV/Pf9utS
+PYoxU3IRjUJ0FacMbhgQOZdtXZGW+G3FLnH2XHX9rUNnrlMmgTmoCGH/JD4ZwXvF
+KoEUZ8wlGVZksNz8i79yPMxR/QyEppf56YSD72FrfCBMVdBDi0vYk7/nhqJU97ws
+VFwhFDTxp0//rca8UOnn0pP9HKylxT6sff/w2U+zzzn1MLsu/Tx6JEbDj+464RnK
+zxBL0wyzUUkwOodgctW8UFPSrttbQD5+7G+kQUl3q1854bX1EdW5jGF3l/OxE9P6
+mbsZlHPOjd4nFOtju7cgKpDbFtVwyDZRBtoPBIbBqS9bf7Y3oaesVeFkDfeC5SkC
+bUIDlri8Sysuse9uoNtaGzvYu5Wnuv3FE1V4X9Ak92S8VyUT2a2miW6Hj9sCAwEA
+AQKCAgAefqdpC1p9ypO5l+nLJE+TLFMlVAqzaoCroUOPzi76+Xu2r1eC99mzsLoo
+JgVZhkf9tfI7feCQyqFPTwl6gQIz26mPSY5rHTj1tdPX7gUHMmAsB9NOXX0IbZOc
+pKOVSBFO495AO2VqPuwRDpw5Rjga+JYOCK/3id8tagvoCTQlMXkRPKjEu2ar5bBc
+7sQxPZT28206q43wFKbI9SOZ56ySizNeJ1q9ej479ZlP7CEHWnElYKlW9h3inloT
+79dm0Jk7K42eb6H5TaUiumaKNtHE7YmHAyw2ukU/SqftBilD3/vGTnRpzb5QuU1x
+ShrM8CE4slr4TJXskrHyVhkOKf0A+RNKcZ6YICrEK1GGOTItNMcAGbHSTQBqVr5I
+MApXfiEQfZetgSv0e9s+DGecB016QG/uJis6OyABV0hYBcnrNPNgpUhmDfy+IN6A
+dpdjeSD/7iOXTEnCe9jrY5+0ot4rKZbl00GoZpb+3pYoBcpDiIVJvPxrWUZeC6lc
+aZjgmjkg2IDXE7swevsStLqSRY3cusMTnRS1Ay37/gobE+f2rJZArY4GAobckT3R
+im6q8cfzTH46wAZBuO6wHlmNfbDVZlvXx6zFhkqpefxRQ1V6Fa9LvkICcGmuhn1O
+9p3DBphflcbHtMGwJpmmr0A3T/aakw2aMT9Qzda91SItlqxy+QKCAQEA8pE4vt3R
+a9PEABO3rF5oWT8UaHfGBTDhoZnG1KDofDvoKrw2R09mz5nhROIDnVaQMtkWChhi
+YhChOHZANp1K2xaCXqDRzbFVq1e/HB9Ag6mTZkT5R9Z088/vOWRIA9H3rk74GB+y
+9iaCusk8NiIqlubvc7XaoTO4ChQ9X+p9QDAmvZVaDfsJGSXYvqfVfMy179mIlzCN
+DSTePQsn1qEiPKltRNBysuluDJqa/PhcsGd7Oeo8SJuLFVOHk9rTuemvIbkxI8v8
+PZ6LD4ORCDsEKst5giqvVBl3yZ2ntuLlouxQGAg0F+VGxSvDYz81zwUHUmsSXDFo
+gw22tWGCQpeFvQKCAQEA2uk2jzEnMvhklfh+3mAUNpPRUyAC7KHIB0vSgvkRTK1w
+aqWF5jCx0onz9sW7lGYcM6RRXBJNi7z8NxouY2Bdw7ijGd7ZuL2ybpbsIetfMV16
+xGZCEL1JEHOnglEochGzspwKmADqWpV/suXKcOlrAZeH1y28TNOgaprTG8zr02DJ
+CQrV8uJnE3HArEHtQdtjsPv/OhDEjEA3Gl2k3hRuhsf0rfeMby0QvvqyVb0Y4SEM
+iIq8MP/dZPgNrDi4r3s0aps10ciAHyHtyuLv2PRkc5ddsBh5V2uEc+S0TTFRRW5N
+uuM6E7uiuCbNrdd/1dfl55ojIYgI4TgR6lH5kmPJdwKCAQBPAK9rstE/fkRLBiD/
+WexAjQP3lnL/Q9FpEa2pmRK/S7+tE4nWJe1FVkgBaF9nAkeK2BuOhCye5e2sdw8o
++ofj3WvuqBBNHyHY4YZUAXXArB1e5L4QALAsrJ+soJW38M3rjrrNGJ3v/9D6Rwp+
+Uxht958rn6IqeK7LUZY/xB6xJj2n55niDc4Dy8jRJ9anhAEJsl8DZwO5sTVUympa
+RDbjbQcyr3V8Af0ey8gI9lcx+TIwRbMGrupYstDofhARcCPjJu7zSr/HzfhawC4f
+cSFFUuorU/2wtW7HUrrKHRJPwwm/GgTld35aP4uuqmq7F1cwJ8FeF5WDgZbtcmm7
+iKA9AoIBAHaOWSsBns4e8jK6atM6S5gnQ/V137+R+ofhC3g9NZ5GTByl2jeJZbS1
+W7fo7Kb5Cgr50cpAa1jjl+CrwDW3yfAmvcZUB6viqJD2EZppI5vTmZpmGx9/s+NC
+D5UnKPVmGuD/W0lpLYKzdn5HrvSppXcuPrZNoa4l6rnxcaWbvJg00YuhH6+z58kD
+ESr5ZWoGTB5cy6QB0sB2QqF318MiY52BC0VwTNElIe2cThrbF29Ne8EzCaqr15ZI
+NPdxnKwE2KVnu6UKpkC2GleHwgfIi+KCNo4ZIxYyN4CgevlXXUFx9IzjZN+s/fon
+obqlfCkvDOb6dk5BozV+LU2u6a/bdQ8CggEBAK/n4ELmvMRbkB4lr9BEN5GyWwvq
+Zkd3e1EcefMpR/dXv+2YD95BlUa2kOhvGVNb2TtnTGcodtYgmWgkSCNwYGO15YX+
+f1OdqWJdJs/FEgMWyAElLHcrwFUJIDuBBLCZrYYKSgNoUM/Ddk/CaAGWT4lsq1U7
+X+GC8SVGkxyqV4GzI4uQ9/vxgbxP9zIbHQqa4BUUM4kzaWxTtJuaF+fHsafgTsDb
+BDmOR/vVAhbM02Bw6xCqWNB337dIC2LJNmvmbTz/4lxe9mZIvN56UzcXAHSB8Xkj
+fbn4ipVY7l/NC4LBLzRvEItTdNr7zAC4QpsU2/upw5PPhv5R4Q2FKV96zM8=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pk8 b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pk8
new file mode 100644
index 00000000..fa38e322
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.pk8
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.x509.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.x509.pem
new file mode 100644
index 00000000..145a6560
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/com.android.apex.test.foo.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF1zCCA78CFGuWfjBtDvTQFqx0afFbqDDoozbTMA0GCSqGSIb3DQEBCwUAMIGm
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEiMCAGA1UEAwwZY29t
+LmFuZHJvaWQuYXBleC50ZXN0LmZvbzAgFw0yMDEwMDUxNDU2NTJaGA80NzU4MDkw
+MTE0NTY1MlowgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQL
+DAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMSIw
+IAYDVQQDDBljb20uYW5kcm9pZC5hcGV4LnRlc3QuZm9vMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAuB+RzoO8K96g3xYQng3cToQGYfCFVA47S3qUBlpj
+Qr4IIw/R/Mo/0/LoK3OMBbjrpwYm0mBRrBttoX0jqlANDOrsk0T6jrWsIiz0iDar
+5PEJjZpnfQILN76shJ/YgW4GQdLvjrZMtksBxKXLbID1uxBBD7KZzIpb7euiV06w
+gaNJZyZg+2J0Isj8qI4H34x2GtjQxd2rN8KTLYOgatekkQCauHH/LLCxVA0K62v5
++NPmrhBTQNO8TSMLmqci13jYcvs9Oj2qkwQKD9i4SMn1VoHrswjulXMibWEyu5xB
+72vjwd9+xpLDcLxHDCQW8uFS7z/omhIE/DTC4QnmGmfz3gyi8O3sekLAdrP5YMz6
++GrrNN8dwKr470g02oMtdpvTIC+4CcIMUuNBPvzsLUnCAxYTWo2QM8hcHx7Bs3XA
+gRaeC2pEIcz9oWoTr/G/5ipdmLSDUVtBQAiNa3KouY9OAO7RtqmweLiXaBfPQSBM
+lBWnyNhfEnhp++4Ef3LFDRpFfyo12XUysdmdreVjlZJ8MykJD5AU0EZ3gQbjcmWX
+vnIWJTc3045dMCDn/1pOss8//q8doIhwVoTCRK0UAofZEajbtfDQTtggWyjMTPtR
+pDtAJA1kmBLnCpjcrnj995pkz6rbMU76zDE7SzoVHE0zRnjDIiLCvNNScJ6oxY/R
+/wcCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAp5kVSkIcuFWMl2X7yTyC3AuOfsOS
+nqnEGs5ns1ij1SjSsogwt4apnjfgXdc3sye2eX3s1SWnxkIBwBQVOIrvSWgN1Wda
+9UQ5uKcrbiz2yT1QMBD1VYsv/zzRPWNP7rzcR7szfaNQOje3BoCaQOkWutgDKi3O
+kN8mz7VtfQniKvw/bbrSQMyVkQpy40XQTyJckfizomTVXlI79AoOVayER/Osgjp9
+8qwjtKVp/o3f3Nd5g2yS9GwsGBbXST0KhSB7bmsLHxPGyF1Zw8i6kKMuS9PX0E8r
+lBGGMZceKDp0eGDXeEUdVIn7labsS2UpMKRuDurpsvol8s3lVBFhAD8yT3yLRNhM
+c573H1ttrE/tlDOj6pdE/uz24WG+M11iKNuqW+/XnUiUjZpK+2Bt6ev4Yg9cDknn
+ih9dr+/YEvCopBCgwaURhTOxDKNNDQCQbu10NnT+apnomWVgWIAmu9SJRE0dFHj/
+46TIUnmmnjg1tTq90yJYoZyXLib6r9PUIXtUEGtBbbjg0axhUqP1jr6FKYnnK3TP
+NkxrkUVDUuI76qDhPIfqECymJz8fx97AJcPGT4qwOJKcCgft86RHOJIFr1AGVGSw
+b+Vw2IWKNwyAqixjN/hhmNH2nlUdhuPPDk1GuTwlODuAvC/GTfOms7yacSeEO0wk
+Sw6cwZWCm3nMiiE=
+-----END CERTIFICATE-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/foo_test.cc b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/foo_test.cc
new file mode 100644
index 00000000..34d44d4f
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/foo_test.cc
@@ -0,0 +1,13 @@
+#include <iostream>
+#include <string>
+
+#include "sharedlibstest.h"
+
+// This parameter gets modified by the build_artifacts.sh script.
+#define FINGERPRINT "VERSION_XXX"
+
+int main() {
+ std::cout << "FOO_" << FINGERPRINT << " "
+ << sharedlibstest::getSharedLibsTestFingerprint();
+ return 0;
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.foo/manifest.json b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/manifest.json
new file mode 100644
index 00000000..c986f711
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.foo/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.test.foo",
+ "version": 1
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/Android.bp b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/Android.bp
new file mode 100644
index 00000000..3dd0349e
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/Android.bp
@@ -0,0 +1,77 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.test.pony.key",
+ public_key: "com.android.apex.test.pony.avbpubkey",
+ private_key: "com.android.apex.test.pony.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.test.pony.certificate",
+ certificate: "com.android.apex.test.pony",
+}
+
+apex {
+ name: "com.android.apex.test.pony",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test.pony.key",
+ installable: false,
+ binaries: [ "pony_test" ],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ updatable: false,
+}
+
+cc_binary {
+ name: "pony_test",
+ srcs: ["pony_test.cc"],
+ shared_libs: [
+ "libsharedlibtest",
+ ],
+ apex_available: [ "com.android.apex.test.pony" ],
+}
+
+genrule {
+ name: "com.android.apex.test.pony_stripped",
+ out: ["com.android.apex.test.pony_stripped.apex"],
+ defaults: ["apexer_test_host_tools_list"],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ srcs: [
+ ":com.android.apex.test.pony",
+ "com.android.apex.test.pony.avbpubkey",
+ "com.android.apex.test.pony.pem",
+ "com.android.apex.test.pony.pk8",
+ "com.android.apex.test.pony.x509.pem",
+ ],
+ tools: [
+ "shared_libs_repack",
+ ],
+ cmd: "$(location shared_libs_repack) " +
+ " --mode strip" +
+ " --key $(location com.android.apex.test.pony.pem)" +
+ " --input $(location :com.android.apex.test.pony)" +
+ " --output $(genDir)/com.android.apex.test.pony_stripped.apex" +
+ " --pk8key $(location com.android.apex.test.pony.pk8)" +
+ " --pubkey $(location com.android.apex.test.pony.avbpubkey)" +
+ " --x509key $(location com.android.apex.test.pony.x509.pem)" +
+ " --tmpdir $(genDir)",
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.avbpubkey b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.avbpubkey
new file mode 100644
index 00000000..f7af6e90
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.avbpubkey
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pem
new file mode 100644
index 00000000..c0e2965d
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAu5za6xUAaREb/RXJGh0QOi+tPLjtmDkYL3COBcZ6dTsBYZuE
+TkBwNgsvfk713Ddk6iOirjcakRLdj1K9lJdyBHMocNz8TjizO/3uRhLA34Fgog6Y
+h0xwpDfb5gPyb18OvCVFuRLIremGa68zPnXl+p0kxzqafKICGjAp3x3ugNcSMXlN
+0nKm4MSDiqZbTlAYutW+jTFr+e2VABbbCY6ijgQJMFDHNkA4QgW2rwEK63OaTImT
+OKT44JnQ5VJpl8VN7HRaAyg5MUjVfuo/HSIkFpYZ5rxgWOZioqvJw+lFV6/TSrxk
+P2JWQuu9mBDmuAoaEHE/yyLPLDKtQUquc6IwGBFG2/dn1WKoTB1X0TsCaIwTBGVV
+n23foz/+uE2FJuh0mNaGWMHN4cOekbYhti1xIvOnHFlOIWdpmRRkIvUwjZYVkCu9
+UnPDHmhbA+3cr8scqc66JTESc1A/uoIGBtK10aY5jUyg4Tvdjc+PlwapeIAseYzK
+x4eW1ujaTP+DEMBaamQLOwhvewxXubTyhjIN4epQma11pVfLwfuM7GAGqlyzTMXE
+BQLkm17TIPEkb+wpU9zP4Xn5FshE8mb6k71fVXwWeBaHtF+tx4Ml5Jv2XUe23EDJ
+5pnIB0sXyJcEjDlqbNCzfZn/LMJQHSd5h+wfwLoeL3U5TigpFSQp9MypSX0CAwEA
+AQKCAgB7LdhaYrabRT2AJI6eE5j06xqt9KkiudHUS+0jg5YhZDVa9bWffxVtllh/
+cK5iAQjD5dPI2Ksbtyw7DtMkPW8B1u4ldCI/5WBgsi+AWI3D8XkVzcl9g8WtPHOn
+iM3jK6FMDJjDk76o2NuF1kkp6FSv//8Gw8ZssB37PcYwFMHkW9E5JHDhDJ/ekYfg
+P6tRNquV+AKdR2aieMfMgDUeCEVYQvQZgd/aEb4eMwwnyOJ3hrY3LFi55y70oGkU
+N9DWchfgeOAklINAhZaPNpNruF/DaJfm86W6mMEIFwxpEb6SfQGYXyrept0GISuh
+LO+exBsq0oBVCizF0xwH81Wo3EMAWnjAGMNxzKOdtJhP0VCL5asgMb6/QbKecko3
+2kZNAeW0aC8wFajCOarQQ5JgMcPdC2gGqjUakqHj+1QlbvG/KI1Xav0ET/OIFOuY
+RAneqIaWVkU76tldm3mnp6b4vS6lsT/v/56sHtCUWGDmVF8zNkZ94LF6XcbEt6ZJ
+p0Ssq0e35Z60ikAPuxNXNA0kgJBpq/V6j0Iqf9otGuEFLCy9AEtCKSYHVQvwRmMC
+wLGr0L47p7RSKyMqWwJFZmMPfbAUz+IlYtxqzQ316L2NSCRnTHPV+thh9W/HIXvu
+pciYkbjk2m4cpjiYEZRS6wLadfPGqMosNJX42G1INM2wEzfeQQKCAQEA35iBH9P+
+hWGYl1rmZBQfv2uYJPo6TDyM7kOrIctmPek8XPWNI9jnayE0Mmpf/eA1sTZpOzg2
+hVVc2QK8Baqxaq6JwNz/WVIvvD8kuCnsBLVYfAxEXgS5aF6NYbSg2oZf0CaMAFtM
+xf0SIyhol9Xl9d48CrmumNZnkvpa7AzOvb9PRn8TXFJFkmkyDmDWvQVaBZbbkKMC
+Ak3UA2nB8ypXfujLc2aBAeoSSw2d/EdoZW4TI/v5sFI+FM9Eg8KG3MP7rhaemptl
+6DNh5PpFYl6smqE87Cb+0jmR3ek9jkjxXJxE4VrkBrDT4BYogb4TnNP7wdux5GJ7
+NqBAGQ8Mjj4ezQKCAQEA1s1dt/VxbOBPCRWlmIlWnmiY4pf3dkl1HOClNOwtviG7
+gCRmeIwtt4O2uwZOMd/JyYLBg4yieT8cCDbXSPXVop765bftBz/Ce5xU9sadLzI5
+sUdHRJkBtHsC12vXZrlrcnVauKAoArDcz5AgndqZ6i7lw5wsB2fQhITGJ1fYMW0L
+RRugKU+wuAgur5GOoTPmryrf8WAbKYh9eeyoBHvK2u8JxsiUXUuJIPvOPjArmHLe
+fxsoN8G3o4Rud9ugPEjsfT3RU8oA+9+LUMVbNl6q8RF9qherxKqmXwukh4C4iuVn
+MyJ3FBAq4Z4BSbyirl4APC4Q41z//ZTYrAuFx9J1cQKCAQBY99ihLnw+3GeYCe5U
+cgFz7D78r6hUv18gS0Kjzsge6FhBcN85HUxvvyWCzfrmDLmwisLyclqXUTEBlGn2
+I0Y2+b4MRKNCCka+M63Lrbqg4PuVWFg3xM91bPH6p6G9cexb6YqZdbqlqR33aVO8
+3rqCy2u+pMWJQP6zZ/SXqjz1GVNU7Klqeb3/FOZ6/CNV0PRR9wXklkftXMR4mzM2
+K2nnMIALqgS5G0cuH/v17v/mJBdvoQpoE0FqjFJpzxRUcZMKYSu4vw6chx1zu/Wx
+v5QUbwXLvXR1d7zHvM/mdrW7MN7jgIPs+Z1Es+xoO5aYN20cZOtywZDfWoJGtks7
+qhIdAoIBAQCqntwXmH2tRwtgovIzlLvZ/imaq61kJvtAoex4ejXndfHy2ncOwAI8
+aAJI0rxf/2vQhe1iqd4QwyFoIO+mw6cbkn6m5A8CGBJKj6YpkyAd8h5Dg+PHSGZD
+Twa1yLKDpTsE4tTaHFVLteLfeJN/77kcfH4Df9S1WTAXY0Pm0m8m63/tOAFjbypn
+NBCpYsxRneFaOItDttw8hG9u3p2jWhWLDB7O6Fp5NNvK+FkdqrOmV3AGtLKgf154
+I2SADlNcL2yyGt1gWe+oIiwOT4WhTVcpP4R7DGxjPk4C50OcYpGzun7b7j96D1GQ
+fyp0wMLUEFTNeKXvg9rPOWFWX5y3WaPxAoIBAQCiWoixjIhHK0f2yMvcYAzqb3A3
+AZs8IjU7phUxAa8LB4R2V5NOYsUl8xIZqZgoPjyJvlI0S82IjEuLqnsmh8GgtJxa
+vOL8JLUX27kr/eOTkz6EnzYVtViFM+D2re7QvakGSh+D43sC29S+dUSMrB6XvFdP
+pBbD6paLmefvgRyP8kxMTujsqVS9TSAqZLirxGSC28wgrRx9CRTy93MxUfw9spCO
+V4/TO384iLZBlj6bDJc4g7YL1KYvZ6ZCCLWoq2TXyBHAOjfThttOoGFj1B4KqdZP
+rO7xAyJCS3t++Nm1AWqmnA7wmNEhMruEZchqGOP7A5f/xcxdsjY0m5LTth50
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pk8 b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pk8
new file mode 100644
index 00000000..104a5830
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.pk8
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.x509.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.x509.pem
new file mode 100644
index 00000000..2f96a293
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/com.android.apex.test.pony.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8ECFBuIrFu5kQxwNJM09/WyY4JaskQLMA0GCSqGSIb3DQEBCwUAMIGn
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEjMCEGA1UEAwwaY29t
+LmFuZHJvaWQuYXBleC50ZXN0LnBvbnkwIBcNMjAxMTI3MTA1MTI1WhgPNDc1ODEw
+MjQxMDUxMjVaMIGnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW
+MBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UE
+CwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEj
+MCEGA1UEAwwaY29tLmFuZHJvaWQuYXBleC50ZXN0LnBvbnkwggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQCrNP9VtH73dqoDIKN9GQ/XTizM2y79KgiUah8J
+4w2FLn3Q/TKxH/NRJVhunAbNLbu8bqfOhETFFWWr8FD2CNq7j5ZSN/xZHHgo40F+
+U1+SisPErTVa9oma4fn62qrzwOZ/uDqwCwzv2Qx7SAYE52M4JAwMI16sV7KZGXBI
+3MZApfRWcuk9FcH0SPCQReY+P4nRqpNqSdsDw0u6BFnPmc2oymkUy7VV7KLjpNo1
+si5M43U3QyLhx6abVdeeIdcC1ycNYEyYKWigBqWOWHIxj2FWUA/vF1OLJm6AR5Z/
+VYh9/7EPvPW+QsMwaM/YRgoCXAnbWA1T7XDJx6aksy6CHt53FjufNwqKC9npthYl
+rdy7rv8XBfrp6/ZXzRhsEWXMHliGrDQF0vyHPgs9CsfdPQtR3H+7tV5nXfa4vj2S
+WbkmZyOyDLTQku7luh2dDcn4Jw9yOoAc2cD7ql6H/Hd4MKrPX/SKahpK7ypZebgY
+RRovxgwxrgFkP9J8/xW1GCY767a9E/sjVaFdQb1rAUWkNG2NBUDtFKdu7mg1KFyE
+c1dTY4cpLiahWLyD845bLnunWaYqutRm3ufu10lBstqF1RkBHcqvoxfoa2IeNB23
+MPqKGaovQuFj+cRHsetC5W3VmROcvAvIJzhcvy3CK5JhvXdvGTkFaoA+6hifY2WU
+Chli5wIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCKEncgnGCwsHkC9UGg/HDwU97Q
+ZH2kqNXdFRYURp8BmQbtNYuYSs9CYDpGugOjW7RILmr954KO+KNCo1OX6c4RWwQ0
+5LAh5+/FmBHiTJlWHRFGhL9WR8Mn1iwEsrANMyhoeoLwlKDVORM2/rS7APBM1pQl
+gQfQHLt0cWoFpoL+pHElyuoVVKGB8sf06atcB/U8HND/xSY8fa7YjBOPYIMoUEvQ
+aeN4JbxYNStqGDEz7+FoguXudQAvq8JFECsFwjWZd0/I5AjqcHiAG9ZaOrUiVRl2
+MdZSf2fjeSHXV3TH7i9f5vmcPUNERZxo/dutOTTVxlS42qrUaVwuSJzDG/MfXZWB
+A6IB3P2qe5lzGl8mmvGKbX3ZXakf0OJaGb/Wn7GpsTynS3P6Zqa02oz89kquERey
+PoHrR3tzNbUbDnjc/px2h2esG7E7E4bRc1PE6ndpvhs2vKypTpYO2v0cXWSk+xiR
+eUZGPIXS7yMHSu4yHxoWUnWoEfzhKJ87gpk87wkSieL73zC/uMkjjyksmngN59VU
+ODkUALrflWdHHynYiaA09zsDCjDuWDBXl17DNPV9lb4L0wLjJSklhuGsHo+wtW9k
+HJx+H972Qk79C4/ZuNiRx83xt8hruqlky2wkZKB9cxYTiizoD9CXwpBl+KGo5oFX
+ZjsDT4tnAHMa69yUoA==
+-----END CERTIFICATE-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/manifest.json b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/manifest.json
new file mode 100644
index 00000000..d1b5a60d
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.test.pony",
+ "version": 1
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.pony/pony_test.cc b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/pony_test.cc
new file mode 100644
index 00000000..761d2410
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.pony/pony_test.cc
@@ -0,0 +1,13 @@
+#include <iostream>
+#include <string>
+
+#include "sharedlibstest.h"
+
+// This parameter gets modified by the build_artifacts.sh script.
+#define FINGERPRINT "VERSION_XXX"
+
+int main() {
+ std::cout << "PONY_" << FINGERPRINT << " "
+ << sharedlibstest::getSharedLibsTestFingerprint();
+ return 0;
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/Android.bp b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/Android.bp
new file mode 100644
index 00000000..78816364
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/Android.bp
@@ -0,0 +1,74 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.test.sharedlibs.key",
+ public_key: "com.android.apex.test.sharedlibs.avbpubkey",
+ private_key: "com.android.apex.test.sharedlibs.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.test.sharedlibs.certificate",
+ certificate: "com.android.apex.test.sharedlibs",
+}
+
+apex {
+ name: "com.android.apex.test.sharedlibs_stub",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test.sharedlibs.key",
+ installable: false,
+ // We want to force libc++.so to be available in this stub APEX, so put an empty binary.
+ binaries: [ "noop" ],
+ updatable: false,
+ compile_multilib: "both",
+ multilib: {
+ both: {
+ binaries: [
+ "noop",
+ ],
+ },
+ },
+}
+
+genrule {
+ name: "com.android.apex.test.sharedlibs_generated",
+ out: ["com.android.apex.test.sharedlibs_generated.apex"],
+ defaults: ["apexer_test_host_tools_list"],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ srcs: [
+ ":com.android.apex.test.sharedlibs_stub",
+ "com.android.apex.test.sharedlibs.avbpubkey",
+ "com.android.apex.test.sharedlibs.pem",
+ "com.android.apex.test.sharedlibs.pk8",
+ "com.android.apex.test.sharedlibs.x509.pem",
+ ],
+ tools: [
+ "shared_libs_repack",
+ ],
+ cmd: "$(location shared_libs_repack) " +
+ " --mode sharedlibs" +
+ " --key $(location com.android.apex.test.sharedlibs.pem)" +
+ " --input $(location :com.android.apex.test.sharedlibs_stub)" +
+ " --output $(genDir)/com.android.apex.test.sharedlibs_generated.apex" +
+ " --pk8key $(location com.android.apex.test.sharedlibs.pk8)" +
+ " --pubkey $(location com.android.apex.test.sharedlibs.avbpubkey)" +
+ " --x509key $(location com.android.apex.test.sharedlibs.x509.pem)" +
+ " --tmpdir $(genDir)",
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.avbpubkey b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.avbpubkey
new file mode 100644
index 00000000..b9a268d9
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.avbpubkey
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pem
new file mode 100644
index 00000000..ce939dcc
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA2EG+ZcurX/nUUNhpbARwgQGgLTQkXU3yNYRk6t5WoXI6t3D2
+/bePE27RRzUhplvs5SZthRl3pgkzhZtajObm31VaUX5cXGveux1bQdtfTeMmEfPl
+TZ5bjM2aWV8QOpPQxUbL3tjCr2NB6sRX864nxl6RvFSgbVACBDVDBbdiHfq4UTGM
+oL0APyO9PCfPueFXwOJyW+gtMc50m+s46E/7Je0hmCfx5xlTjnue0CzF9oDPvat6
+72hX5JFOxefHPIVW2/IFnjzMsyCwjF4WyB2eZjmH3Mtx44Xn5hH/a+1wMvvcJmjx
+W4rexJVdDSr+RZTQC0Wr2eObk/6ib4gjXcjCr8cAdTB5g9IbKmGdOlHM3VavlkL9
+CMzNy+/w5UnqWrLJanC9nJba6PmwxG7n5doFODwB1ZypX3crrxjyT5saKN0KnSF3
+Ux3yt8SPnAr5QZ01ta4H26I65YwQi+Bqf/MOy+DgEDv3GEUTGLvK5YK2sDSWdIr4
+s3NRpkkUpAo/RZsqlxYCkYXtrdngP5r1ej0+uuzXt8cqXytcTqMpJzwDtNvlJiRG
+iqYzQZE+p3JrLgUTnT+clezOU4SbbaK17z+gLFoy9rs15ChF+VM0GFeuWh0pTOv8
+UfpsOZrI4Cwgr1q8oUGhcXwj6cgqdxzlrRFqb7no6L0rajk2dQLBoCFIV4ECAwEA
+AQKCAgAgHQMtEqWMRwkkSD6/b5lVTux+SfPsdxq0n8hsqD+tEc1uWDQVUSDJ/fbN
+4DHzBkuTa7VvwmxmF4+zE3LK4a7/EymqWF1WzB3zI1Td3rm0UzrgB5vRfuaRbiax
+htBeIn0qDm1P1lhyuwaa2jVFVmNJrdluYhLAqNTj0xT00FqdoRGl3PnJFMfomGIN
+gMv0CmaBmh7pTv0HHGVske2NcfMVmrUWZzgg3T3vNqRKvZtYE6DFxaUn0BLdOka8
+VMLdVd+kIbh72wN6xivxbDdt2BghjgGC5CMxaj0ZiSqo2EWFDKmQepz8vw59msCK
+qAvCQWrzgZEXdhkwTOvKLCk0UA+4zJoZ1hF3tZWStftqHB+zl1nsz3H8vBHzUaGJ
+1ufJqBZrmKqJvMEMlxoV+A8ftV/SZTmdXrEb/CNLGAfNdf8mhjc5hU58HLQPHQxA
+3IAj1Jyllc5hZHYXKAvu89ift0ZxZel8Nim+STmzpXiTvhia8+NEJlbOIp7JJf9L
+OOhj33PUiIR9e2iPOrhPOkV4o3HVW/Dpwu2P1DSqOIoGxB0zFE+eZGGf9HVNOyy9
+xUUGRpzdD8M09gnI3Yszabo2HjKdKYQdmoSc7jzMMwvQGd9Zt08KvpsWj8mry7l9
+VVy8h4rWlxmJtC8aXOHI5thV/4jmwfRYbDEzh4Qt2XxVQCyvUQKCAQEA9uviNj0Q
+FWtpeYULYMksz63JryaPeI3wAJtz6efYTcJ025qobTiHhoIER3TInsBBUQ3/YRYy
+ZBtCeKpLxYA81IabIX9lO757RMslEb5KeD3Da2HqNnrh3hyRXgXXcUMbed8JunlC
+5FRCRafikkj5ABvLHB9AYgAhS/vnTGQ7+XgpFsYi1kUGbJw+t4ly/ydWLjs5XoY7
+JdAuwnI/T8z31yYGG8T3oMSQV+BXT1doGNalKGupGMrUyvvqJQvScvxsVSWS8cRS
+A1Xu91R0oMYhwnUkMZc7qYEVdGqcXFagSbnXufT4J7Bw+H7wbY17fYOC3cGkh3AK
+FCa1d74Zd/3aswKCAQEA4DU7vukSOSDAf1hChkYXPzUJdrk31AVfeISYZmKqYah6
+65I2kzOnaszDQRg11wHXHSRhU0oVQxA77VTQBNQzOy+F7oB3o0cuQ+n6X5U8QQy4
+2dmgiYp3rkrqoH9KVn5VsYdwyYQHmE1IVMazo2YByv2N7aq4KN0MEgYUzizF38jX
+hf7ubQA7mGr9I0Wm1keQJ1m9VZ06mPKLfG70LViJAAvCYzSLY/31FBGVNq4JlreP
+EI4hgrYv7tzU6BqzrIdAmFOYOOsdRnUUW9+OhVzreU2EBAAmjK1jWa6FFAXGJEhi
+/qyO2suvNdWsIGxfqt5yhPMevMqjyEPmvwpLF+5u+wKCAQEAj9ShZVS2bLOvsdB0
+60DkMGkcBUGh6uhK+B+VKpgZYFo4Nb9mApEeKJTNp034mriEk5FixAvo+HUEiEMy
+de4YAPgTnzSVJHL1XQI0Kpy8xkO79G4JvwhfT0E20Bz4/QnJFHl+Mjf2ZghKvkZn
+7SxClvSZoFz35N4MhzVJ6y6r3MpIrPJnUobMkjGFOuX+rXAdfDqVVWE9TO5yfmOM
+S5CqgZGtlzlpwSUeq4GLejUA9w75D41+52knAMIzBrdXNBGjjQmhCeGAoF7LHxj8
+ArbG7X3MwnJEl50QgUqkoAj5v1hYuAJhFsVpWOagaEA0wcz8Su5ER3xU8p4FsKV0
+MngVjwKCAQEAqVuMpcioWz7CKW8h0Qtgw/3sCCIgaaclVoPSGoSs7te1AfyP/OEn
+tSS22JTRFnftZbX1TlTHesDog31tJDil+i8Lm/yuYkeCSwqSdWDlAr35Y5VgDoTp
+ol40nMeJ/4uub0s/hviURBcca+0sBGEpOYwNiVlLgpJ2a6bsUFDBpyiupCjNMMjc
+O2WVkO8r9vBXk2HWArWhbabIdlXZW+dklQRM8WLfZ8iNN3uQmp0b4R0GlBrIdVPp
+ISTuLeT9k3UW9fkvIs92baJCnqNfpJ1rwVUsQ1lZxSmzwipxm45A/WcwX+84eU0i
+LCgavOMf4JHnL0X2EeV/kea4hdXgo1MXwQKCAQEApKpBnJ2DPxN/I0cHbOFLH1rS
+A3lZvx0iOz7AFVK9laJ/794s5RdugXQ0MO4D0u+QrYN/Q3GnMpuNmHM8FRRNRIte
+jdpPmFLyPOrqOCaeISmMnwZl1GZtOjZwOWRWP/7pK8cv6bI4H8tDyYq9T3b0K+TK
+mqhqoHyN4aNrLCxggmhFS8wlK5UmuwJRfbAx4KGbfYk7fy4THkGjn5ZL3q9PO9Fa
+1jWsiBFdnmRQvD4svnTswPEts/rJ9o1P5+AnWtVKK7Npq9eOFhPRJ3R3hruHJath
+Cw999aol+hd093kcd7RzRmRjxUZ6oKxs1yRF7o9QQxnsmHRUT79Yy9LBOprvVA==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pk8 b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pk8
new file mode 100644
index 00000000..933eb474
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.pk8
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.x509.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.x509.pem
new file mode 100644
index 00000000..95f96af8
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/com.android.apex.test.sharedlibs.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5TCCA80CFBhOpuxTEQRD+MtXkeI8yP5rnPrwMA0GCSqGSIb3DQEBCwUAMIGt
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEpMCcGA1UEAwwgY29t
+LmFuZHJvaWQuYXBleC50ZXN0LnNoYXJlZGxpYnMwIBcNMjAxMDA1MTYzMzIwWhgP
+NDc1ODA5MDExNjMzMjBaMIGtMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZv
+cm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQ
+MA4GA1UECwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lk
+LmNvbTEpMCcGA1UEAwwgY29tLmFuZHJvaWQuYXBleC50ZXN0LnNoYXJlZGxpYnMw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDjKxHP9SI0nMgAqYsSueNU
+042arnj7pjdzhqHhRWr8Ub0mwMXx0ulkgZcnhCbdg52670T2wPhl/xi4/HL7PybU
+sjt1QsjOaYcDdSrdxnsPe4RXZG4aE/+z06pm7TJ2jMGjJzS9x4AXbFNBxLsLp+gk
+fuxhpGQklgymRUojtUFMWhnXURQz5wyYJGUrV32FpOo5JKtO4A42pNhcHNBzeQp0
+LVAwcQalmxra1maf0PXTg6L4yukyNzfLYcukjkQilzrkWGPqGexCzLF+WXiuznNj
+BqPguKXvdsmSUIqAKGw6QHnuD6cAm3d2CuMzd4fjdOwzxFtfDEXS8f8ai/K+UEvx
+eqf8eXV0cpKROrbf0CafcUli/3CKIc3UXoQr5kx4LSp0eJHOyaoGEF3Dex/k0dgJ
+O80nNGTydhwvfMoZ4MOm+4yzDTqKu/Hw2ebM9po3vsfF3oy/hjEX0CaS4DnKDugg
+WUwAbTHG5k/lbzy6mkjRDxwSzn6sDrhHM1rs45thiQF9hfZuDwUbaGmxFAq8+Gtu
+xcr6bhryxaiO10MdMHI5KP1ZfZT49c+K8oVlTOsHxxxSK3eiQOs4k8A0jef7gfTP
+rnDKbdq7JHr73bHXv495UtgZMCKtS3p4kvHs5PYblWvJmYImMrLjqdtuckYOeEIO
+2N+79Rm8YAItX+SigohnZwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAiJdApc590
+zoYl2w6NOj1xMljzta5Ld92lbJ3O4r1IoFQ6p+bSuV5RlPEC/kzR1G2WrZf66IrW
+UOSxtDjY6Bt1GKO949BKTp4/gdrnanst7ai5VnPeGWstUIEJ5SmF7C9QBhWnGnFT
+9zGdtbKnkcreZ81yabbNwAAwZWgX5hfkLSuMu7SzgLnzVDQOvbg96esWCbbBNPAl
+KhZb5Bzc42TDlUxWfqIC0Of3GjcLu1Ukn4fwFphMD4wGoHIgpGD6975BVWESpmnV
+tPDwiI+02Nha1aySZr/TiTId0AUucb6fqySqjCbOowv3DimKt+anwZjk1k/12TLb
+Uro5nOPbWwQkQws7tnfNb8VBWoGNc+SJbh260rhv7gpwsvXOdbKbyR7mSqXylreh
+DBUd/UL2eR1IrMuixK4bvmfVd7y+lxYKutEk+ifwSEuAubcbZ/dKH4PzjRnu1evv
+4M/1sLH4LRd+qHoR4ylopX8dWn3xh2Xq9KFrnTXRk1nd1YeGqcyTz+H7bBhYZKLO
+vPYTUb8ZYc/h0VGaABVywnb8wWZH4Op7ytKDybV2PGjhdLWaA3Y758ySkEab80ye
+XSuSPevgTDc7ZV3/Ijs7cW6+XVKnWWl4H7DQxG4B9vWrKl0YejNJBUNaXoE5t1hN
+TG2GM5D9sA/8HiW1JygwH0CBXMT5drYYjA==
+-----END CERTIFICATE-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/manifest.json b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/manifest.json
new file mode 100644
index 00000000..abdb7c42
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.test.sharedlibs",
+ "version": 1
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/Android.bp b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/Android.bp
new file mode 100644
index 00000000..9e803e32
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/Android.bp
@@ -0,0 +1,66 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+ name: "com.android.apex.test.sharedlibs_secondary.key",
+ public_key: "com.android.apex.test.sharedlibs_secondary.avbpubkey",
+ private_key: "com.android.apex.test.sharedlibs_secondary.pem",
+}
+
+android_app_certificate {
+ name: "com.android.apex.test.sharedlibs_secondary.certificate",
+ certificate: "com.android.apex.test.sharedlibs_secondary",
+}
+
+apex {
+ name: "com.android.apex.test.sharedlibs_secondary_stub",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.test.sharedlibs_secondary.key",
+ installable: false,
+ // We want to force libc++.so to be available in this stub APEX, so put an empty binary.
+ binaries: [ "noop" ],
+ updatable: false,
+}
+
+genrule {
+ name: "com.android.apex.test.sharedlibs_secondary_generated",
+ out: ["com.android.apex.test.sharedlibs_secondary_generated.apex"],
+ defaults: ["apexer_test_host_tools_list"],
+ dist: {
+ targets: ["sharedlibs_test"],
+ },
+ srcs: [
+ ":com.android.apex.test.sharedlibs_secondary_stub",
+ "com.android.apex.test.sharedlibs_secondary.avbpubkey",
+ "com.android.apex.test.sharedlibs_secondary.pem",
+ "com.android.apex.test.sharedlibs_secondary.pk8",
+ "com.android.apex.test.sharedlibs_secondary.x509.pem",
+ ],
+ tools: [
+ "shared_libs_repack",
+ ],
+ cmd: "$(location shared_libs_repack) " +
+ " --mode sharedlibs" +
+ " --key $(location com.android.apex.test.sharedlibs_secondary.pem)" +
+ " --input $(location :com.android.apex.test.sharedlibs_secondary_stub)" +
+ " --output $(genDir)/com.android.apex.test.sharedlibs_secondary_generated.apex" +
+ " --pk8key $(location com.android.apex.test.sharedlibs_secondary.pk8)" +
+ " --pubkey $(location com.android.apex.test.sharedlibs_secondary.avbpubkey)" +
+ " --x509key $(location com.android.apex.test.sharedlibs_secondary.x509.pem)" +
+ " --tmpdir $(genDir)",
+}
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.avbpubkey b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.avbpubkey
new file mode 100644
index 00000000..3ccad0fa
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.avbpubkey
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pem
new file mode 100644
index 00000000..5d4e1e1d
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA2U7XN6RmrgLci4ZK2Osai9MqNIfx0gBX43DeIt6/1mQ5T590
+016wrGN6Oux0+LcdPU4PmpcyHw8+JVgdZjxG5c4icfWEuuc3Y+Pb6/FhvNwNrN5U
+wx7/DFGyEWaRZEe3pk6oDETSYvY5CzQjrFz+mDDwNnho0zxsRD74GWumQOkdAg0W
+7ihUq4/gCnjx+SeijTJAZcN0+oSldRa8+yN5zYdtO9gt9PGZLr87pvIFbcKp8QIT
+sReN9eFoLHYYktq5y4YXIhhV7W0imsPzAq6gtzHC6+qgDPCvwD6/GTvGHgF+DEUl
+/NuQgqgeVrFOEnHZ5qsawRwVeVq6sRd2ZQUdj+ix5KTS9/iKpg/cyLHCaXzEWWcZ
+DtKnZKxdFTyXa4w+UshQkKDHPYOKfOXLaTTywz9oAZNDBQsLjUUIDZhyxRq7a5Zk
+gUi5zG+Wx6r46pdSmMb76Qa2vasMQUYFoRINRfrsU6EJp55fjWCDzFXMIdm8nSkz
+4DY6T+XLu76zAMrZZPHrQle2D5zi/mu2yvbHi0sbujUCFBFrPpLkaD8TE1gqxqfh
+p5+xXD8tal6Mj7exLvRCadNaQ1R8CtcZhVxtUPmGVwlUZ6aF1QpARQtfZuVBEiut
+kyfTb9ns8r3xmxsUOSwJA6SgRuN0gvXIfDnNumDaP9dkuGE98uQpTu8kZbsCAwEA
+AQKCAgEAg0oR1yk5bAqIirdxAwtP94h16FT18eWJM/2eB71Cc9oLkiKJp6Z+4Tgc
+wfrYVOf0/3PpE4IjowZHirJo2Lq0LuVShD1MmstU+MHSvgMRBNSCYp3U4ioY961o
+AwFP+CEoQI8nEnqGDYorPqyanOl2XCa9CnvHAVBxLO5KYLlcMb1lbDbSUsMFHL4J
+IuqdbuXWXK4uoAzt4OlBObOqK6TsUxNuGIjsgx9waADbnmp8gyroF5ckpIrRlus/
+UBVtlVQWinMSCORhDdgw3wZiDI3KxcOHu5b+abMEzAZc2Hb3pGtMZ0djwxg4f+fo
+pIHs5FHqz2Uy/dbk1nPNdW7ydegYShuQmsvbOT6R8Qi25c9SiirXKOtdCM50JTW7
+SRaReqhiy828m9DOrwSXXiHrmyNevpwGQtRELGi14rgPqnpuUNaOmMEGAstQ+xcU
+Hl0DVuxWXN37iEKa4zv/LKwRzBVxlrSC1M3ufiOi2tVl5btMrdz5jZwDLYQxI7TX
+Nu0l/tEWD98/sJyOk4bmGUFrD5+oBpPGw/MrK6k7DmJjp8W1NRiiIACI/flzRWcA
+la7lPk23kG0/lmrWY87NQhEaPR9RMqmizlnF3VzWyDxXgZcn4ucjRmH7qhQLsZCC
+VIEI3oPoQSd7w+pHghLTA4R0KqW2ur/d05mkOmLfGnL27wPqaJECggEBAPX2tgHr
+DLIapja0HtxYNx0I5H/lHi3bzBI426h7317oIQpASKsqaIu/jOSDt5gLC0g+qSJi
+gT/l454bkdN4lTAMvY+3vJg3cN4U8J9GAejZi2X23Hed1QVuBSckz3CrHanqVBMN
+Nj9FsfQpqLJpewVqqdKR9rK+1ZNyMWfEO9M3pxlpZyWhPHg7KtbqBo0hS5Stlqpy
+xOo4nF4WC+OGNUo02R12ETJM4AmMs28Z5oNYZZ6K4cqKReNcDqt891ryXrow6aq9
+JBYz8sCeBQSTnPNnhbLJH/YOVMlhdAJhBcxEDbNKM6vmFQOuBEp12tjQoXsiv54C
+uBou7aRft8uGeHMCggEBAOIszCN1ZvD+xbaLDtKgPrAuDVRaZuZ9vb2TVB7Gcpto
+hCXU5GoapqQgjGplsXOQ1FMRozeRL4lQWfPfiueLAV3AeIHm0qjD2aanhJPvPrnb
+UCOdWRID99q9/s6bP98XiDseZiTHNlWitwpbWPAvmW2/08otmf4TgeLrA8p75CtK
+qJXU7o7fWTBFHF238rusK3N9t0Vikb0Cx1xDui6b5PeWtqAREV1/swP0J0av1fwK
+OuvZ8VdUjb5KMUMtGgMEWA277kYuryalu+oQH+bfe3XXUJS7fAI7GlokVqhJ8CLZ
+eSSZDtdqHib968cA4wr55nt1rEiDWzG7f2GeZra6s5kCggEAN53QrABdP4ydFvOF
+oudjlvIi0PSa7V2s+FXY/XD9IjW0+t9sTx/owejPUACkrAGbTHu2vOqvNSajYGX1
+hG7YtSO8XVn7kCPBJsZvXmRzHBbM2YKHeZi7yV2GVsKREXXv4DL3TdOH96inw4ED
+/0uwoJnsyots0CAspQmGOGN775e+9hUKWMzrongmiLAkSRdFQto5nlMTSa8BVJkB
+mTIIrL3kdi/zVX9ijWY+UJn3sK11VPMseSLpCK8RNh+swujZGJrky1G3bjnS41EX
+62ABdlxrM/EchAPbkimyFLOhnv2oZ2kY4/7Ds7BOkhOyJ6KNUQ2bbHxK6si/vZJT
+OfcvFwKCAQEA0nnVzvmmPocY/vMRbDjrnZB9nw4xzDUfqZe9JJaQeMcekwY3OfZr
+NTmE8k6IgH8618MGHOPjVOmNjEFvRmI5d0Fx45EmYR9BILGr0u9FdDf/r+Txyq4e
+rVU6FpKrMbT4deuoKnmourCdnem8LmhdY6CsOu2M7MDCkqUZ9gitIQxtLmHlTtfS
+a/UknKJeJP/nv6YyM0OzVC2N0PLGBDHXNgDvGq5HdrcrpHZFRqbDf7UVd/5tdVOe
+RINOrLEAD+au+rj02CMBo/l/kiZHSdaXUeZ5eq+ui3Ts5Q4EBsAn1IaFEeXNxfFe
+9fI+xAazQrekISg0l5aF+xX9SJ7b/xhnoQKCAQEA7vvi5zUalTNRreeExX0wiKRj
+sCvUhQoykimKpWzSUg6DpdF+DhK59/ERVSkA4C2WwHEYqZdrgEKnmqOZQkqgtq1B
+O5xxioRtn6VsZRYjaRw5Cq/Ej1AeDgQr+SH29kBtym81mMBbkPnFoBxKCjuuVn9u
+4Kjg910wxErzipjDB2alfkuFCqNz9IZd2XSmpmGhpE/hBlZP4slJXTX77nIxRTHB
+Vq5gehJoEhLNsFAu34BgrXRIhzSBALdalbepNrHh2br87rJpjxeRASbuyyNdgZyl
+wk+aJxHo6RpMD7xHxsxv5kmRcLITPICk+ysLZ5ZHMaEuGIVFxDdOoFGylRV4wQ==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pk8 b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pk8
new file mode 100644
index 00000000..2eee60f0
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.pk8
Binary files differ
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.x509.pem b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.x509.pem
new file mode 100644
index 00000000..74dd144f
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/com.android.apex.test.sharedlibs_secondary.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF+TCCA+ECFGnrAltwoToH4tPw7aN36oFBb8zxMA0GCSqGSIb3DQEBCwUAMIG3
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEzMDEGA1UEAwwqY29t
+LmFuZHJvaWQuYXBleC50ZXN0LnNoYXJlZGxpYnNfc2Vjb25kYXJ5MCAXDTIwMTEy
+NzEwNTE0N1oYDzQ3NTgxMDI0MTA1MTQ3WjCBtzELMAkGA1UEBhMCVVMxEzARBgNV
+BAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoM
+B0FuZHJvaWQxEDAOBgNVBAsMB0FuZHJvaWQxIjAgBgkqhkiG9w0BCQEWE2FuZHJv
+aWRAYW5kcm9pZC5jb20xMzAxBgNVBAMMKmNvbS5hbmRyb2lkLmFwZXgudGVzdC5z
+aGFyZWRsaWJzX3NlY29uZGFyeTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBANLZWZWEtzYdHL+yio1j16r4aDCtRMdTFT8dWhXi/khEodZ9PZHV73eAWl7o
+5AHO2h/4To6hqxKlzl5lbNWT/Xu7OFvhaqy1gD2XiOyZxqNITba3SV4qDnWYAMHc
+JdcYKv/F5UMeiXQueBJ2HaWcWoZPOmHwfJbMxSvr+LOwtI1vvR38G68uIFyR0Rlb
+jQggv/WsudLlB++0c+jOtPLmD65h7dXXXlqGvu+7Rt5GWYxtN/sIZ0KPqtLk1JQD
+nIa5E05UDcqfGnyo5mz7t9Tj+4dyH/B+fY9JMXJMNMcy47CfdhR4NfnsUTmLqXRc
+fOwSRfuodMbCd4WAgA6e6KHjlBO89NOA3QamQDyhT3XURrMCMeRQvb155dEoHCs5
+FDBu138hku5aylLfiHkEHfwEDu9wsWAd3CWsBfqOMAEQG7AB+4lCaGVhYKMxKWjM
+Pdqzfy47ODHrMEprfrYlOkdJ+9Fu9HJZvEMQXh7tleoOSIuvqmL1ksRSUXsE2JHJ
+KC/hoBCcVsMsujxn7tQilB0e+TLDml08dIrrGLV1UiqU/80q0MC2Bwinne21KAtY
+UMJd2pImZr3H4z7J6wQmuevvcZksaEwWXtQzIoXenmaSW9bwqVy6k+DushOghGy/
+CAeeJAc35sUFsa8ysp0aPARZIqCYiesGdiO/wW+UVibo2JyLAgMBAAEwDQYJKoZI
+hvcNAQELBQADggIBAFE7g7ZTGqSDpJ/PyLKLp+oItJ+JNJVd5UbLvLMiA4t7QTtE
+0aPCxMGybJGeYbs16OB3ZqWZlVyjirEXAmSH2HZxO1uIeDcCjndSCfz+oxmBs27j
+C8t5BzExaelpP/J9nyKObzaZ1EJ+KdTqPVmrhgN8uUU82mRt5oJZuxGbHNdbzxGh
+zXHi+q5oecWaiBFDSF9pvKFp7nvbW4MIaGcm9Flx9JdYhgzRfXeJV7EsKE2Kxflj
+nojgyUzjvkNTa/wRASgwr/hgpPL0mpD9gSejxOA5wRGLknDlIfsfEh5cJTFiBO5b
+MZiSHe7Ds+8GWyKk8y8G2YtB0Z5HxD91rzPA2W/JEDHhoI8jT47I3JuOyJMjC9DV
+kAu4nhReBilOmU+oS1Iv+TheslPGLEZ7JOHsmyQh5X5H0D+YH7H9L8BOWEBlNBhy
+zPlMKNWsvMZmem/fhuXK1xUUWZuqoj2tF2fMmiYf6fSNRb7dQI10uBslxX7myNyj
+pqZNSk/E9V650UC5JixpOtPxrSsPXifOLwB7OEkT5v7v/MoBqlSfqve9/62Ktnuc
+NC9Rt3fqU+VOs6tVUPGnSJRDc2uhvU8rJZN86e1xX8d7lhHZwO8sJtaxZ2KYduM1
+ncEODTAxK7wQ041CS7H/kULG6CEPnIg0a1ZjASWhQRbQ1Rj/Y40CmHxThnyx
+-----END CERTIFICATE-----
diff --git a/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/manifest.json b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/manifest.json
new file mode 100644
index 00000000..80da6f14
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/com.android.apex.test.sharedlibs_secondary/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.test.sharedlibs_secondary",
+ "version": 1
+}
diff --git a/tests/src/com/android/tests/apex/AdbdHostTest.java b/tests/testdata/sharedlibs/build/include/sharedlibstest.h
index 445ca88b..a2f0cdc9 100644
--- a/tests/src/com/android/tests/apex/AdbdHostTest.java
+++ b/tests/testdata/sharedlibs/build/include/sharedlibstest.h
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.tests.apex;
+#ifndef SHAREDLIBSTEST_H_
+#define SHAREDLIBSTEST_H_
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+#include <string>
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
+namespace sharedlibstest {
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@Ignore // TODO(b/148759193): remove when test is fixed
-public class AdbdHostTest extends ApexE2EBaseHostTest {
-}
+std::string getSharedLibsTestFingerprint();
+
+} // namespace sharedlibstest
+
+#endif // SHAREDLIBSTEST_H_
diff --git a/tests/testdata/sharedlibs/build/noop.cc b/tests/testdata/sharedlibs/build/noop.cc
new file mode 100644
index 00000000..cfd2ef57
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/noop.cc
@@ -0,0 +1,9 @@
+#include <iostream>
+
+#include "sharedlibstest.h"
+
+int main() {
+ std::cout << "This binary should never be executed";
+ std::cout << sharedlibstest::getSharedLibsTestFingerprint();
+ return 1;
+} \ No newline at end of file
diff --git a/tests/testdata/sharedlibs/build/shared_libs_repack.py b/tests/testdata/sharedlibs/build/shared_libs_repack.py
new file mode 100644
index 00000000..31b9a6bc
--- /dev/null
+++ b/tests/testdata/sharedlibs/build/shared_libs_repack.py
@@ -0,0 +1,421 @@
+# Copyright (C) 2020 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.
+#
+# 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.
+"""Repacking tool for Shared Libs APEX testing."""
+
+import argparse
+import hashlib
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+from zipfile import ZipFile
+
+import apex_build_info_pb2
+import apex_manifest_pb2
+
+logger = logging.getLogger(__name__)
+
+def comma_separated_list(arg):
+ return arg.split(',')
+
+
+def parse_args(argv):
+ parser = argparse.ArgumentParser(
+ description='Repacking tool for Shared Libs APEX testing')
+
+ parser.add_argument('--input', required=True, help='Input file')
+ parser.add_argument('--output', required=True, help='Output file')
+ parser.add_argument(
+ '--key', required=True, help='Path to the private avb key file')
+ parser.add_argument(
+ '--pk8key',
+ required=True,
+ help='Path to the private apk key file in pk8 format')
+ parser.add_argument(
+ '--pubkey', required=True, help='Path to the public avb key file')
+ parser.add_argument(
+ '--tmpdir', required=True, help='Temporary directory to use')
+ parser.add_argument(
+ '--x509key',
+ required=True,
+ help='Path to the public apk key file in x509 format')
+ parser.add_argument(
+ '--mode', default='strip', choices=['strip', 'sharedlibs'])
+ parser.add_argument(
+ '--libs',
+ default='libc++.so,libsharedlibtest.so',
+ type=comma_separated_list,
+ help='Libraries to strip/repack. Expects comma separated values.')
+ return parser.parse_args(argv)
+
+
+def run(args, verbose=None, **kwargs):
+ """Creates and returns a subprocess.Popen object.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+ universal_newlines will default to True, as most of the users in
+ releasetools expect string output.
+
+ Returns:
+ A subprocess.Popen object.
+ """
+ if 'stdout' not in kwargs and 'stderr' not in kwargs:
+ kwargs['stdout'] = subprocess.PIPE
+ kwargs['stderr'] = subprocess.STDOUT
+ if 'universal_newlines' not in kwargs:
+ kwargs['universal_newlines'] = True
+ if verbose:
+ logger.info(' Running: \"%s\"', ' '.join(args))
+ return subprocess.Popen(args, **kwargs)
+
+
+def run_and_check_output(args, verbose=None, **kwargs):
+ """Runs the given command and returns the output.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+
+ Returns:
+ The output string.
+
+ Raises:
+ ExternalError: On non-zero exit from the command.
+ """
+ proc = run(args, verbose=verbose, **kwargs)
+ output, _ = proc.communicate()
+ if output is None:
+ output = ''
+ # Don't log any if caller explicitly says so.
+ if verbose:
+ logger.info('%s', output.rstrip())
+ if proc.returncode != 0:
+ raise RuntimeError(
+ 'Failed to run command \'{}\' (exit code {}):\n{}'.format(
+ args, proc.returncode, output))
+ return output
+
+
+def get_container_files(apex_file_path, tmpdir):
+ dir_name = tempfile.mkdtemp(prefix='container_files_', dir=tmpdir)
+ with ZipFile(apex_file_path, 'r') as zip_obj:
+ zip_obj.extractall(path=dir_name)
+ files = {}
+ for i in [
+ 'apex_manifest.json', 'apex_manifest.pb', 'apex_build_info.pb', 'assets',
+ 'apex_payload.img', 'apex_payload.zip'
+ ]:
+ file_path = os.path.join(dir_name, i)
+ if os.path.exists(file_path):
+ files[i] = file_path
+
+ image_file = files.get('apex_payload.img')
+ if image_file is None:
+ image_file = files.get('apex_payload.zip')
+
+ files['apex_payload'] = image_file
+
+ return files
+
+
+def extract_payload_from_img(img_file_path, tmpdir):
+ dir_name = tempfile.mkdtemp(prefix='extracted_payload_', dir=tmpdir)
+ cmd = [
+ _get_host_tools_path('debugfs_static'), '-R',
+ 'rdump ./ %s' % dir_name, img_file_path
+ ]
+ run_and_check_output(cmd)
+
+ # Remove payload files added by apexer and e2fs tools.
+ for i in ['apex_manifest.json', 'apex_manifest.pb']:
+ if os.path.exists(os.path.join(dir_name, i)):
+ os.remove(os.path.join(dir_name, i))
+ if os.path.isdir(os.path.join(dir_name, 'lost+found')):
+ shutil.rmtree(os.path.join(dir_name, 'lost+found'))
+ return dir_name
+
+
+def run_apexer(container_files, payload_dir, key_path, pubkey_path, tmpdir):
+ apexer_cmd = _get_host_tools_path('apexer')
+ cmd = [
+ apexer_cmd, '--force', '--include_build_info', '--do_not_check_keyname'
+ ]
+ cmd.extend([
+ '--apexer_tool_path',
+ os.path.dirname(apexer_cmd) + ':prebuilts/sdk/tools/linux/bin'
+ ])
+ cmd.extend(['--manifest', container_files['apex_manifest.pb']])
+ if 'apex_manifest.json' in container_files:
+ cmd.extend(['--manifest_json', container_files['apex_manifest.json']])
+ cmd.extend(['--build_info', container_files['apex_build_info.pb']])
+ if 'assets' in container_files:
+ cmd.extend(['--assets_dir', container_files['assets']])
+ cmd.extend(['--key', key_path])
+ cmd.extend(['--pubkey', pubkey_path])
+
+ # Decide on output file name
+ apex_suffix = '.apex.unsigned'
+ fd, fn = tempfile.mkstemp(prefix='repacked_', suffix=apex_suffix, dir=tmpdir)
+ os.close(fd)
+ cmd.extend([payload_dir, fn])
+
+ run_and_check_output(cmd)
+ return fn
+
+
+def _get_java_toolchain():
+ java_toolchain = 'java'
+ if os.path.isfile('prebuilts/jdk/jdk11/linux-x86/bin/java'):
+ java_toolchain = 'prebuilts/jdk/jdk11/linux-x86/bin/java'
+
+ java_dep_lib = (
+ os.path.join(os.path.dirname(_get_host_tools_path()), 'lib64') + ':' +
+ os.path.join(os.path.dirname(_get_host_tools_path()), 'lib'))
+
+ return [java_toolchain, java_dep_lib]
+
+
+def _get_host_tools_path(tool_name=None):
+ # This script is located at e.g.
+ # out/soong/host/linux-x86/bin/shared_libs_repack/shared_libs_repack.py.
+ # Find the host tools dir by going up two directories.
+ dirname = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+ if tool_name:
+ return os.path.join(dirname, tool_name)
+ return dirname
+
+
+def sign_apk_container(unsigned_apex, x509key_path, pk8key_path, tmpdir):
+ fd, fn = tempfile.mkstemp(prefix='repacked_', suffix='.apex', dir=tmpdir)
+ os.close(fd)
+ java_toolchain, java_dep_lib = _get_java_toolchain()
+
+ cmd = [
+ java_toolchain, '-Djava.library.path=' + java_dep_lib, '-jar',
+ os.path.join(
+ os.path.dirname(_get_host_tools_path()), 'framework', 'signapk.jar'),
+ '-a', '4096', x509key_path, pk8key_path, unsigned_apex, fn
+ ]
+ run_and_check_output(cmd)
+ return fn
+
+
+def compute_sha512(file_path):
+ block_size = 65536
+ hashbuf = hashlib.sha512()
+ with open(file_path, 'rb') as f:
+ fb = f.read(block_size)
+ while len(fb) > 0:
+ hashbuf.update(fb)
+ fb = f.read(block_size)
+ return hashbuf.hexdigest()
+
+
+def parse_fs_config(fs_config):
+ configs = fs_config.splitlines()
+ # Result is set of configurations.
+ # Each configuration is set of items as [file path, uid, gid, mode].
+ # All items are stored as string.
+ result = []
+ for config in configs:
+ result.append(config.split())
+ return result
+
+
+def config_to_str(configs):
+ result = ''
+ for config in configs:
+ result += ' '.join(config) + '\n'
+ return result
+
+
+def _extract_lib_or_lib64(payload_dir, lib_full_path):
+ # Figure out if this is lib or lib64:
+ # Strip out the payload_dir and split by /
+ libpath = lib_full_path[len(payload_dir):].lstrip('/').split('/')
+ return libpath[0]
+
+
+def main(argv):
+ args = parse_args(argv)
+ apex_file_path = args.input
+
+ container_files = get_container_files(apex_file_path, args.tmpdir)
+ payload_dir = extract_payload_from_img(container_files['apex_payload.img'],
+ args.tmpdir)
+ libs = args.libs
+ assert len(libs)> 0
+
+ lib_paths = [os.path.join(payload_dir, lib_dir, lib)
+ for lib_dir in ['lib', 'lib64']
+ for lib in libs
+ if os.path.exists(os.path.join(payload_dir, lib_dir, lib))]
+
+ assert len(lib_paths) > 0
+
+ lib_paths_hashes = [(lib, compute_sha512(lib)) for lib in lib_paths]
+
+ if args.mode == 'strip':
+ # Stripping mode. Add a reference to the version of libc++.so to the
+ # requireSharedApexLibs entry in the manifest, and remove lib64/libc++.so
+ # from the payload.
+ pb = apex_manifest_pb2.ApexManifest()
+ with open(container_files['apex_manifest.pb'], 'rb') as f:
+ pb.ParseFromString(f.read())
+ for lib_path_hash in lib_paths_hashes:
+ basename = os.path.basename(lib_path_hash[0])
+ libpath = _extract_lib_or_lib64(payload_dir, lib_path_hash[0])
+ assert libpath in ('lib', 'lib64')
+ pb.requireSharedApexLibs.append(os.path.join(libpath, basename) + ':'
+ + lib_path_hash[1])
+ # Replace existing library with symlink
+ symlink_dst = os.path.join('/', 'apex', 'sharedlibs',
+ libpath, basename, lib_path_hash[1],
+ basename)
+ os.remove(lib_path_hash[0])
+ os.system('ln -s {0} {1}'.format(symlink_dst, lib_path_hash[0]))
+ #
+ # Example of resulting manifest:
+ # ---
+ # name: "com.android.apex.test.foo"
+ # version: 1
+ # requireNativeLibs: "libc.so"
+ # requireNativeLibs: "libdl.so"
+ # requireNativeLibs: "libm.so"
+ # requireSharedApexLibs: "lib/libc++.so:23c5dd..."
+ # requireSharedApexLibs: "lib/libsharedlibtest.so:870f38..."
+ # requireSharedApexLibs: "lib64/libc++.so:72a584..."
+ # requireSharedApexLibs: "lib64/libsharedlibtest.so:109015..."
+ # --
+ # To print uncomment the following:
+ # from google.protobuf import text_format
+ # print(text_format.MessageToString(pb))
+ with open(container_files['apex_manifest.pb'], 'wb') as f:
+ f.write(pb.SerializeToString())
+
+ if args.mode == 'sharedlibs':
+ # Sharedlibs mode. Mark in the APEX manifest that this package contains
+ # shared libraries.
+ pb = apex_manifest_pb2.ApexManifest()
+ with open(container_files['apex_manifest.pb'], 'rb') as f:
+ pb.ParseFromString(f.read())
+ del pb.requireNativeLibs[:]
+ pb.provideSharedApexLibs = True
+ with open(container_files['apex_manifest.pb'], 'wb') as f:
+ f.write(pb.SerializeToString())
+
+ pb = apex_build_info_pb2.ApexBuildInfo()
+ with open(container_files['apex_build_info.pb'], 'rb') as f:
+ pb.ParseFromString(f.read())
+
+ canned_fs_config = parse_fs_config(pb.canned_fs_config.decode('utf-8'))
+
+ # Remove the bin directory from payload dir and from the canned_fs_config.
+ shutil.rmtree(os.path.join(payload_dir, 'bin'))
+ canned_fs_config = [config for config in canned_fs_config
+ if not config[0].startswith('/bin')]
+
+ # Remove from the canned_fs_config the entries we are about to relocate in
+ # different dirs.
+ source_lib_paths = [os.path.join('/', libpath, lib)
+ for libpath in ['lib', 'lib64']
+ for lib in libs]
+ # We backup the fs config lines for the libraries we are going to relocate,
+ # so we can set the same permissions later.
+ canned_fs_config_original_lib = {config[0] : config
+ for config in canned_fs_config
+ if config[0] in source_lib_paths}
+
+ canned_fs_config = [config for config in canned_fs_config
+ if config[0] not in source_lib_paths]
+
+ # We move any targeted library in lib64/ or lib/ to a directory named
+ # /lib64/libNAME.so/${SHA512_OF_LIBCPP}/ or
+ # /lib/libNAME.so/${SHA512_OF_LIBCPP}/
+ #
+ for lib_path_hash in lib_paths_hashes:
+ basename = os.path.basename(lib_path_hash[0])
+ libpath = _extract_lib_or_lib64(payload_dir, lib_path_hash[0])
+ tmp_lib = os.path.join(payload_dir, libpath, basename + '.bak')
+ shutil.move(lib_path_hash[0], tmp_lib)
+ destdir = os.path.join(payload_dir, libpath, basename, lib_path_hash[1])
+ os.makedirs(destdir)
+ shutil.move(tmp_lib, os.path.join(destdir, basename))
+
+ canned_fs_config.append(
+ ['/' + libpath + '/' + basename, '0', '2000', '0755'])
+ canned_fs_config.append(
+ ['/' + libpath + '/' + basename + '/' + lib_path_hash[1],
+ '0', '2000', '0755'])
+
+ if os.path.join('/', libpath, basename) in canned_fs_config_original_lib:
+ config = canned_fs_config_original_lib[os.path.join(
+ '/',
+ libpath,
+ basename)]
+ canned_fs_config.append([os.path.join('/', libpath, basename,
+ lib_path_hash[1], basename),
+ config[1], config[2], config[3]])
+ else:
+ canned_fs_config.append([os.path.join('/', libpath, basename,
+ lib_path_hash[1], basename),
+ '1000', '1000', '0644'])
+
+ pb.canned_fs_config = config_to_str(canned_fs_config).encode('utf-8')
+ with open(container_files['apex_build_info.pb'], 'wb') as f:
+ f.write(pb.SerializeToString())
+
+ try:
+ for lib in lib_paths:
+ os.rmdir(os.path.dirname(lib))
+ except OSError:
+ # Directory not empty, that's OK.
+ pass
+
+ repack_apex_file_path = run_apexer(container_files, payload_dir, args.key,
+ args.pubkey, args.tmpdir)
+
+ resigned_apex_file_path = sign_apk_container(repack_apex_file_path,
+ args.x509key, args.pk8key,
+ args.tmpdir)
+
+ shutil.copyfile(resigned_apex_file_path, args.output)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/tests/src/com/android/tests/apex/ExtServicesHostTest.java b/tests/testdata/sharedlibs/build/sharedlibstest.cpp
index ff5d6942..bac580ef 100644
--- a/tests/src/com/android/tests/apex/ExtServicesHostTest.java
+++ b/tests/testdata/sharedlibs/build/sharedlibstest.cpp
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-package com.android.tests.apex;
+#include "sharedlibstest.h"
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+#include <string>
-import org.junit.runner.RunWith;
+namespace sharedlibstest {
-/**
- * Test to check if Apex can be staged, activated and uninstalled successfully.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class ExtServicesHostTest extends ApexE2EBaseHostTest {
+// This parameter gets modified by the build_artifacts.sh script.
+#define FINGERPRINT "VERSION_XXX"
+
+std::string getSharedLibsTestFingerprint() {
+ return std::string("SHARED_LIB_") + FINGERPRINT;
}
+
+} // namespace sharedlibstest
diff --git a/tests/testdata/sharedlibs/prebuilts/Android.bp b/tests/testdata/sharedlibs/prebuilts/Android.bp
new file mode 100644
index 00000000..1ef2977e
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/Android.bp
@@ -0,0 +1,301 @@
+// Copyright (C) 2020 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.
+
+// This file is auto-generated by
+// ./system/apex/tests/testdata/sharedlibs/build/build_artifacts.sh
+// Do NOT edit manually.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.bar_stripped.v1.libvX_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.bar_stripped.v1.libvX.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.bar_stripped.v1.libvX.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.bar_stripped.v1.libvX.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.bar_stripped.v1.libvX.apex",
+ },
+ },
+ filename: "com.android.apex.test.bar_stripped.v1.libvX.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.bar_stripped.v2.libvY_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.bar_stripped.v2.libvY.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.bar_stripped.v2.libvY.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.bar_stripped.v2.libvY.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.bar_stripped.v2.libvY.apex",
+ },
+ },
+ filename: "com.android.apex.test.bar_stripped.v2.libvY.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.bar.v1.libvX_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.bar.v1.libvX.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.bar.v1.libvX.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.bar.v1.libvX.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.bar.v1.libvX.apex",
+ },
+ },
+ filename: "com.android.apex.test.bar.v1.libvX.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.bar.v2.libvY_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.bar.v2.libvY.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.bar.v2.libvY.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.bar.v2.libvY.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.bar.v2.libvY.apex",
+ },
+ },
+ filename: "com.android.apex.test.bar.v2.libvY.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.baz_stripped.v1.libvX_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.baz_stripped.v1.libvX.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.baz_stripped.v1.libvX.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.baz_stripped.v1.libvX.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.baz_stripped.v1.libvX.apex",
+ },
+ },
+ filename: "com.android.apex.test.baz_stripped.v1.libvX.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.foo_stripped.v1.libvX_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.foo_stripped.v1.libvX.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.foo_stripped.v1.libvX.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.foo_stripped.v1.libvX.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.foo_stripped.v1.libvX.apex",
+ },
+ },
+ filename: "com.android.apex.test.foo_stripped.v1.libvX.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.foo_stripped.v2.libvY_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.foo_stripped.v2.libvY.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.foo_stripped.v2.libvY.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.foo_stripped.v2.libvY.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.foo_stripped.v2.libvY.apex",
+ },
+ },
+ filename: "com.android.apex.test.foo_stripped.v2.libvY.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.foo.v1.libvX_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.foo.v1.libvX.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.foo.v1.libvX.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.foo.v1.libvX.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.foo.v1.libvX.apex",
+ },
+ },
+ filename: "com.android.apex.test.foo.v1.libvX.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.foo.v2.libvY_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.foo.v2.libvY.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.foo.v2.libvY.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.foo.v2.libvY.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.foo.v2.libvY.apex",
+ },
+ },
+ filename: "com.android.apex.test.foo.v2.libvY.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.pony_stripped.v1.libvZ_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.pony_stripped.v1.libvZ.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.pony_stripped.v1.libvZ.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.pony_stripped.v1.libvZ.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.pony_stripped.v1.libvZ.apex",
+ },
+ },
+ filename: "com.android.apex.test.pony_stripped.v1.libvZ.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.pony.v1.libvZ_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.pony.v1.libvZ.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.pony.v1.libvZ.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.pony.v1.libvZ.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.pony.v1.libvZ.apex",
+ },
+ },
+ filename: "com.android.apex.test.pony.v1.libvZ.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.sharedlibs_generated.v1.libvX_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.sharedlibs_generated.v1.libvX.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.sharedlibs_generated.v1.libvX.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex",
+ },
+ },
+ filename: "com.android.apex.test.sharedlibs_generated.v1.libvX.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.sharedlibs_generated.v2.libvY_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.sharedlibs_generated.v2.libvY.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.sharedlibs_generated.v2.libvY.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex",
+ },
+ },
+ filename: "com.android.apex.test.sharedlibs_generated.v2.libvY.apex",
+ installable: false,
+}
+
+prebuilt_apex {
+ name: "com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ_prebuilt",
+ arch: {
+ arm: {
+ src: "arm/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex",
+ },
+ arm64: {
+ src: "arm64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex",
+ },
+ x86: {
+ src: "x86/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex",
+ },
+ x86_64: {
+ src: "x86_64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex",
+ },
+ },
+ filename: "com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex",
+ installable: false,
+}
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v1.libvX.apex
new file mode 100644
index 00000000..07fcff94
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v2.libvY.apex
new file mode 100644
index 00000000..2361fd60
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v1.libvX.apex
new file mode 100644
index 00000000..20bdd345
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v2.libvY.apex
new file mode 100644
index 00000000..67915351
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.bar_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.baz_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.baz_stripped.v1.libvX.apex
new file mode 100644
index 00000000..e6405b42
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.baz_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v1.libvX.apex
new file mode 100644
index 00000000..799890ab
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v2.libvY.apex
new file mode 100644
index 00000000..906ec226
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v1.libvX.apex
new file mode 100644
index 00000000..5ee73835
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v2.libvY.apex
new file mode 100644
index 00000000..c2f9f687
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.foo_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony.v1.libvZ.apex
new file mode 100644
index 00000000..323b81ff
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony_stripped.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony_stripped.v1.libvZ.apex
new file mode 100644
index 00000000..8a578ba0
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.pony_stripped.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
new file mode 100644
index 00000000..58e7f956
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
new file mode 100644
index 00000000..b0385526
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
new file mode 100644
index 00000000..3ab73afa
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v1.libvX.apex
new file mode 100644
index 00000000..c04a1691
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v2.libvY.apex
new file mode 100644
index 00000000..9bedfa25
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v1.libvX.apex
new file mode 100644
index 00000000..a8459790
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v2.libvY.apex
new file mode 100644
index 00000000..c8f89611
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.bar_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.baz_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.baz_stripped.v1.libvX.apex
new file mode 100644
index 00000000..e2e5b708
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.baz_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v1.libvX.apex
new file mode 100644
index 00000000..d6795a59
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v2.libvY.apex
new file mode 100644
index 00000000..65dcd649
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v1.libvX.apex
new file mode 100644
index 00000000..894afa22
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v2.libvY.apex
new file mode 100644
index 00000000..c1ce6fad
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.foo_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony.v1.libvZ.apex
new file mode 100644
index 00000000..80c64239
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony_stripped.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony_stripped.v1.libvZ.apex
new file mode 100644
index 00000000..4d380da7
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.pony_stripped.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
new file mode 100644
index 00000000..e0cf9e7c
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
new file mode 100644
index 00000000..93f24f25
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
new file mode 100644
index 00000000..21a6dc3c
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/arm64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v1.libvX.apex
new file mode 100644
index 00000000..4767f4a3
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v2.libvY.apex
new file mode 100644
index 00000000..cff076ac
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v1.libvX.apex
new file mode 100644
index 00000000..bca5c496
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v2.libvY.apex
new file mode 100644
index 00000000..01f822e9
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.bar_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.baz_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.baz_stripped.v1.libvX.apex
new file mode 100644
index 00000000..96248526
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.baz_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v1.libvX.apex
new file mode 100644
index 00000000..8b352bd0
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v2.libvY.apex
new file mode 100644
index 00000000..71cdc202
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v1.libvX.apex
new file mode 100644
index 00000000..eccb3d4e
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v2.libvY.apex
new file mode 100644
index 00000000..0265ac95
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.foo_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony.v1.libvZ.apex
new file mode 100644
index 00000000..ef1d1d90
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony_stripped.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony_stripped.v1.libvZ.apex
new file mode 100644
index 00000000..8439fe3c
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.pony_stripped.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
new file mode 100644
index 00000000..8ba0e5b4
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
new file mode 100644
index 00000000..2928193e
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
new file mode 100644
index 00000000..e849bcd0
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v1.libvX.apex
new file mode 100644
index 00000000..e733e1d2
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v2.libvY.apex
new file mode 100644
index 00000000..522b9c8e
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v1.libvX.apex
new file mode 100644
index 00000000..eec0184b
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v2.libvY.apex
new file mode 100644
index 00000000..cbd96978
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.bar_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.baz_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.baz_stripped.v1.libvX.apex
new file mode 100644
index 00000000..b2ca4130
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.baz_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v1.libvX.apex
new file mode 100644
index 00000000..2f5a2c27
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v2.libvY.apex
new file mode 100644
index 00000000..1bc4774e
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v1.libvX.apex
new file mode 100644
index 00000000..77db6dff
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v2.libvY.apex
new file mode 100644
index 00000000..042d4bc4
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.foo_stripped.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony.v1.libvZ.apex
new file mode 100644
index 00000000..5a7c5fb3
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony_stripped.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony_stripped.v1.libvZ.apex
new file mode 100644
index 00000000..bb99f7ef
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.pony_stripped.v1.libvZ.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
new file mode 100644
index 00000000..2ff31ca4
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v1.libvX.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
new file mode 100644
index 00000000..013f2754
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_generated.v2.libvY.apex
Binary files differ
diff --git a/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
new file mode 100644
index 00000000..3c6cf339
--- /dev/null
+++ b/tests/testdata/sharedlibs/prebuilts/x86_64/com.android.apex.test.sharedlibs_secondary_generated.v1.libvZ.apex
Binary files differ
diff --git a/tests/util/com/android/tests/util/ModuleTestUtils.java b/tests/util/com/android/tests/util/ModuleTestUtils.java
deleted file mode 100644
index 8f62bc33..00000000
--- a/tests/util/com/android/tests/util/ModuleTestUtils.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.tests.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.ITestDevice.ApexInfo;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.RunUtil;
-import com.android.tradefed.util.SystemUtil.EnvVariable;
-
-import com.google.common.base.Stopwatch;
-
-import org.junit.Assert;
-
-import java.io.File;
-import java.io.IOException;
-import java.time.Duration;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Stream;
-
-public class ModuleTestUtils {
- private static final String SHIM = "com.android.apex.cts.shim";
- private static final String APEX_INFO_EXTRACT_REGEX =
- ".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*";
-
- private static final Duration WAIT_FOR_SESSION_READY_TTL = Duration.ofSeconds(10);
- private static final Duration SLEEP_FOR = Duration.ofMillis(200);
-
- protected final Pattern mIsSessionReadyPattern =
- Pattern.compile("(isReady = true)|(isStagedSessionReady = true)");
- protected final Pattern mIsSessionAppliedPattern =
- Pattern.compile("(isApplied = true)|(isStagedSessionApplied = true)");
-
- private IRunUtil mRunUtil = new RunUtil();
- private BaseHostJUnit4Test mTest;
-
- private IBuildInfo getBuild() {
- return mTest.getBuild();
- }
-
- public ModuleTestUtils(BaseHostJUnit4Test test) {
- mTest = test;
- }
-
- /**
- * Retrieve package name and version code from test apex file.
- *
- * @param apex input apex file to retrieve the info from
- */
- public ApexInfo getApexInfo(File apex) {
- String aaptOutput = runCmd(String.format(
- "aapt dump badging %s", apex.getAbsolutePath()));
- String[] lines = aaptOutput.split("\n");
- Pattern p = Pattern.compile(APEX_INFO_EXTRACT_REGEX);
- for (String l : lines) {
- Matcher m = p.matcher(l);
- if (m.matches()) {
- ApexInfo apexInfo = new ApexInfo(m.group(1), Long.parseLong(m.group(2)));
- return apexInfo;
- }
- }
- return null;
- }
-
- /**
- * Get the test file.
- *
- * @param testFileName name of the file
- */
- public File getTestFile(String testFileName) throws IOException {
- File testFile = null;
-
- String testcasesPath = System.getenv(EnvVariable.ANDROID_HOST_OUT_TESTCASES.toString());
- if (testcasesPath != null) {
- testFile = searchTestFile(new File(testcasesPath), testFileName);
- }
- if (testFile != null) {
- return testFile;
- }
-
- File hostLinkedDir = getBuild().getFile(BuildInfoFileKey.HOST_LINKED_DIR);
- if (hostLinkedDir != null) {
- testFile = searchTestFile(hostLinkedDir, testFileName);
- }
- if (testFile != null) {
- return testFile;
- }
-
- // Find the file in the buildinfo.
- File buildInfoFile = getBuild().getFile(testFileName);
- if (buildInfoFile != null) {
- return buildInfoFile;
- }
-
- throw new IOException("Cannot find " + testFileName);
- }
-
- private String runCmd(String cmd) {
- CLog.d("About to run command: %s", cmd);
- CommandResult result = mRunUtil.runTimedCmd(1000 * 60 * 5, cmd.split("\\s+"));
- Assert.assertNotNull(result);
- Assert.assertTrue(
- String.format("Command %s failed", cmd),
- result.getStatus().equals(CommandStatus.SUCCESS));
- CLog.v("output:\n%s", result.getStdout());
- return result.getStdout();
- }
-
- /**
- * Searches the file with the given name under the given directory, returns null if not found.
- */
- private File searchTestFile(File baseSearchFile, String testFileName) {
- if (baseSearchFile != null && baseSearchFile.isDirectory()) {
- File testFile = FileUtil.findFile(baseSearchFile, testFileName);
- if (testFile != null && testFile.isFile()) {
- return testFile;
- }
- }
- return null;
- }
-
- public void waitForStagedSessionReady() throws DeviceNotAvailableException {
- // TODO: implement wait for session ready logic inside PackageManagerShellCommand instead.
- boolean sessionReady = false;
- Duration spentWaiting = Duration.ZERO;
- while (spentWaiting.compareTo(WAIT_FOR_SESSION_READY_TTL) < 0) {
- CommandResult res = mTest.getDevice().executeShellV2Command("pm get-stagedsessions");
- Assert.assertEquals("", res.getStderr());
- sessionReady = Stream.of(res.getStdout().split("\n")).anyMatch(this::isReadyNotApplied);
- if (sessionReady) {
- CLog.i("Done waiting after " + spentWaiting);
- break;
- }
- try {
- Thread.sleep(SLEEP_FOR.toMillis());
- spentWaiting = spentWaiting.plus(SLEEP_FOR);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException(e);
- }
- }
- Assert.assertTrue("Staged session wasn't ready in " + WAIT_FOR_SESSION_READY_TTL,
- sessionReady);
- }
-
- /**
- * Abandons any staged session that is marked {@code ready}
- */
- public void abandonActiveStagedSession() throws DeviceNotAvailableException {
- CommandResult res = mTest.getDevice().executeShellV2Command("pm list staged-sessions "
- + "--only-ready --only-parent --only-sessionid");
- assertThat(res.getStderr()).isEqualTo("");
- String activeSessionId = res.getStdout();
- if (activeSessionId != null && !activeSessionId.equals("")) {
- res = mTest.getDevice().executeShellV2Command("pm install-abandon "
- + activeSessionId);
- if (!res.getStderr().equals("") || res.getStatus() != CommandStatus.SUCCESS) {
- CLog.d("Failed to abandon session " + activeSessionId
- + " Error: " + res.getStderr());
- }
- }
- }
-
- /**
- * Uninstalls a shim apex only if its latest version is installed on /data partition
- *
- * <p>This is purely to optimize tests run time, since uninstalling an apex requires a reboot.
- */
- public void uninstallShimApexIfNecessary() throws Exception {
- if (!isApexUpdateSupported()) {
- return;
- }
-
- final String errorMessage = mTest.getDevice().uninstallPackage(SHIM);
- if (errorMessage == null) {
- CLog.i("Uninstalling shim apex");
- mTest.getDevice().reboot();
- } else {
- // Most likely we tried to uninstall system version and failed. It should be fine to
- // continue tests.
- // TODO(b/140813980): use ApexInfo.sourceDir to decide whenever to issue an uninstall.
- CLog.w("Failed to uninstall shim APEX: " + errorMessage);
- }
- assertThat(getShimApex().versionCode).isEqualTo(1L);
- }
-
- private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
- return mTest.getDevice().getActiveApexes().stream().filter(
- apex -> apex.name.equals(SHIM)).findAny().orElseThrow(
- () -> new AssertionError("Can't find " + SHIM));
- }
-
- /**
- * Return {@code true} if and only if device supports updating apex.
- */
- public boolean isApexUpdateSupported() throws Exception {
- return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false);
- }
-
- private boolean isReadyNotApplied(String sessionInfo) {
- boolean isReady = mIsSessionReadyPattern.matcher(sessionInfo).find();
- boolean isApplied = mIsSessionAppliedPattern.matcher(sessionInfo).find();
- return isReady && !isApplied;
- }
-
- /**
- * Waits for given {@code timeout} for {@code filePath} to be deleted.
- */
- public void waitForFileDeleted(String filePath, Duration timeout) throws Exception {
- Stopwatch stopwatch = Stopwatch.createStarted();
- while (true) {
- if (!mTest.getDevice().doesFileExist(filePath)) {
- return;
- }
- if (stopwatch.elapsed().compareTo(timeout) > 0) {
- break;
- }
- Thread.sleep(500);
- }
- throw new AssertionError("Timed out waiting for " + filePath + " to be deleted");
- }
-}
diff --git a/tests/wifi-e2e-tests.xml b/tests/wifi-e2e-tests.xml
deleted file mode 100644
index 1a130962..00000000
--- a/tests/wifi-e2e-tests.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<configuration description="Config for wifi apex e2e testing">
- <option name="test-suite-tag" value="wifi_e2e_tests" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="wifi_e2e_tests.jar" />
- <option name="set-option" value="apex_file_name:test_com.android.wifi.apex" />
- </test>
-</configuration>
-
diff --git a/tools/Android.bp b/tools/Android.bp
index 1f119e52..6b4427b1 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
python_binary_host {
name: "deapexer",
srcs: [
@@ -34,3 +38,56 @@ python_binary_host {
],
}
+python_binary_host {
+ name: "apex_compression_tool",
+ srcs: [
+ "apex_compression_tool.py",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ },
+ libs: [
+ "apex_manifest_proto",
+ ],
+ required: [
+ "avbtool",
+ "conv_apex_manifest",
+ ],
+}
+
+python_test_host {
+ name: "apex_compression_test",
+ main: "apex_compression_test.py",
+ srcs: [
+ "apex_compression_test.py",
+ ],
+ data: [
+ ":avbtool",
+ ":com.android.example.apex",
+ ":conv_apex_manifest",
+ ":apex_compression_tool",
+ ":deapexer",
+ ":soong_zip",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ },
+ },
+ libs: [
+ "apex_manifest_proto",
+ ],
+ test_suites: ["general-tests"],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/tools/apex_compression_test.py b/tools/apex_compression_test.py
new file mode 100644
index 00000000..018fc563
--- /dev/null
+++ b/tools/apex_compression_test.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2020 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.
+#
+"""Unit tests for apex_compression_tool."""
+import hashlib
+import logging
+import os
+import shutil
+import subprocess
+import tempfile
+import unittest
+from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
+
+import apex_manifest_pb2
+
+logger = logging.getLogger(__name__)
+
+TEST_APEX = 'com.android.example.apex'
+
+# In order to debug test failures, set DEBUG_TEST to True and run the test from
+# local workstation bypassing atest, e.g.:
+# $ m apex_compression_tool_test && \
+# out/host/linux-x86/nativetest64/apex_compression_tool_test/\
+# apex_compression_tool_test
+#
+# the test will print out the command used, and the temporary files used by the
+# test.
+DEBUG_TEST = False
+
+
+def run(args, verbose=None, **kwargs):
+ """Creates and returns a subprocess.Popen object.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+ universal_newlines will default to True, as most of the users in
+ releasetools expect string output.
+
+ Returns:
+ A subprocess.Popen object.
+ """
+ if 'stdout' not in kwargs and 'stderr' not in kwargs:
+ kwargs['stdout'] = subprocess.PIPE
+ kwargs['stderr'] = subprocess.STDOUT
+ if 'universal_newlines' not in kwargs:
+ kwargs['universal_newlines'] = True
+ if DEBUG_TEST:
+ print('\nRunning: \n%s\n' % ' '.join(args))
+ # Don't log any if caller explicitly says so.
+ if verbose:
+ logger.info(' Running: \'%s\'', ' '.join(args))
+ return subprocess.Popen(args, **kwargs)
+
+
+def run_host_command(args, verbose=None, **kwargs):
+ host_build_top = os.environ.get('ANDROID_BUILD_TOP')
+ if host_build_top:
+ host_command_dir = os.path.join(host_build_top,
+ 'out/soong/host/linux-x86/bin')
+ args[0] = os.path.join(host_command_dir, args[0])
+ return run_and_check_output(args, verbose, **kwargs)
+
+
+def run_and_check_output(args, verbose=None, **kwargs):
+ """Runs the given command and returns the output.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+
+ Returns:
+ The output string.
+
+ Raises:
+ ExternalError: On non-zero exit from the command.
+ """
+
+ proc = run(args, verbose=verbose, **kwargs)
+ output, _ = proc.communicate()
+ if output is None:
+ output = ''
+ # Don't log any if caller explicitly says so.
+ if verbose:
+ logger.info('%s', output.rstrip())
+ if proc.returncode != 0:
+ raise RuntimeError(
+ "Failed to run command '{}' (exit code {}):\n{}".format(
+ args, proc.returncode, output))
+ return output
+
+
+def get_current_dir():
+ """Returns the current dir, relative to the script dir."""
+ # The script dir is the one we want, which could be different from pwd.
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ return current_dir
+
+
+def get_sha1sum(file_path):
+ h = hashlib.sha256()
+
+ with open(file_path, 'rb') as file:
+ while True:
+ # Reading is buffered, so we can read smaller chunks.
+ chunk = file.read(h.block_size)
+ if not chunk:
+ break
+ h.update(chunk)
+
+ return h.hexdigest()
+
+
+class ApexCompressionTest(unittest.TestCase):
+ def setUp(self):
+ self._to_cleanup = []
+
+ def tearDown(self):
+ if not DEBUG_TEST:
+ for i in self._to_cleanup:
+ if os.path.isdir(i):
+ shutil.rmtree(i, ignore_errors=True)
+ else:
+ os.remove(i)
+ del self._to_cleanup[:]
+ else:
+ print('Cleanup: ' + str(self._to_cleanup))
+
+ def _run_apex_compression_tool(self, args):
+ cmd = ['apex_compression_tool']
+ host_build_top = os.environ.get('ANDROID_BUILD_TOP')
+ if host_build_top:
+ os.environ['APEX_COMPRESSION_TOOL_PATH'] = (
+ os.path.join(host_build_top, 'out/soong/host/linux-x86/bin')
+ + ':' + os.path.join(host_build_top, 'prebuilts/sdk/tools/linux/bin'))
+ else:
+ os.environ['APEX_COMPRESSION_TOOL_PATH'] = os.path.dirname(
+ shutil.which('apex_compression_tool'))
+ cmd.extend(args)
+ run_host_command(cmd, True)
+
+ def _get_container_files(self, apex_file_path):
+ dir_name = tempfile.mkdtemp(
+ prefix=self._testMethodName + '_container_files_')
+ self._to_cleanup.append(dir_name)
+ with ZipFile(apex_file_path, 'r') as zip_obj:
+ zip_obj.extractall(path=dir_name)
+ files = {}
+ for i in ['apex_manifest.json', 'apex_manifest.pb', 'apex_pubkey',
+ 'apex_build_info.pb', 'apex_payload.img', 'apex_payload.zip',
+ 'AndroidManifest.xml', 'original_apex']:
+ file_path = os.path.join(dir_name, i)
+ if os.path.exists(file_path):
+ files[i] = file_path
+
+ image_file = files.get('apex_payload.img', None)
+ if image_file is None:
+ image_file = files.get('apex_payload.zip', None)
+ else:
+ files['apex_payload'] = image_file
+ # Also retrieve the root digest of the image
+ avbtool_cmd = ['avbtool',
+ 'print_partition_digests', '--image', files['apex_payload']]
+ # avbtool_cmd output has format "<name>: <value>"
+ files['digest'] = run_host_command(
+ avbtool_cmd, True).split(': ')[1].strip()
+
+ return files
+
+ def _get_manifest_string(self, manifest_path):
+ cmd = ['conv_apex_manifest']
+ cmd.extend([
+ 'print',
+ manifest_path
+ ])
+ return run_host_command(cmd, 'True')
+
+ # Mutates the manifest located at |manifest_path|
+ def _unset_original_apex_digest(self, manifest_path):
+ # Open the protobuf
+ with open(manifest_path, 'rb') as f:
+ pb = apex_manifest_pb2.ApexManifest()
+ pb.ParseFromString(f.read())
+ pb.ClearField('capexMetadata')
+ with open(manifest_path, 'wb') as f:
+ f.write(pb.SerializeToString())
+
+ def _compress_apex(self, uncompressed_apex_fp):
+ """Returns file path to compressed APEX"""
+ fd, compressed_apex_fp = tempfile.mkstemp(
+ prefix=self._testMethodName + '_compressed_',
+ suffix='.capex')
+ os.close(fd)
+ self._to_cleanup.append(compressed_apex_fp)
+ self._run_apex_compression_tool([
+ 'compress',
+ '--input', uncompressed_apex_fp,
+ '--output', compressed_apex_fp
+ ])
+ return compressed_apex_fp
+
+ def _decompress_apex(self, compressed_apex_fp):
+ """Returns file path to decompressed APEX"""
+ decompressed_apex_fp = tempfile. \
+ NamedTemporaryFile(prefix=self._testMethodName + '_decompressed_',
+ suffix='.apex').name
+ # Use deapexer to decompress
+ cmd = ['deapexer']
+ cmd.extend([
+ 'decompress',
+ '--input', compressed_apex_fp,
+ '--output', decompressed_apex_fp
+ ])
+ run_host_command(cmd, True)
+
+ self.assertTrue(os.path.exists(decompressed_apex_fp),
+ 'Decompressed APEX does not exist')
+ self._to_cleanup.append(decompressed_apex_fp)
+ return decompressed_apex_fp
+
+ def _get_type(self, apex_file_path):
+ cmd = ['deapexer', 'info', '--print-type', apex_file_path]
+ return run_host_command(cmd, True).strip()
+
+ def test_compression(self):
+ uncompressed_apex_fp = os.path.join(get_current_dir(), TEST_APEX + '.apex')
+ # TODO(samiul): try compressing a compressed APEX
+ compressed_apex_fp = self._compress_apex(uncompressed_apex_fp)
+
+ # Verify output file has been created and is smaller than input file
+ uncompressed_file_size = os.path.getsize(uncompressed_apex_fp)
+ compressed_file_size = os.path.getsize(compressed_apex_fp)
+ self.assertGreater(compressed_file_size, 0, 'Compressed APEX is empty')
+ self.assertLess(compressed_file_size, uncompressed_file_size,
+ 'Compressed APEX is not smaller than uncompressed APEX')
+
+ # Verify type of the apex is 'COMPRESSED'
+ self.assertEqual(self._get_type(compressed_apex_fp), 'COMPRESSED')
+
+ # Verify the contents of the compressed apex files
+ content_in_compressed_apex = self._get_container_files(compressed_apex_fp)
+ self.assertIsNotNone(content_in_compressed_apex['original_apex'])
+ content_in_uncompressed_apex = self._get_container_files(
+ uncompressed_apex_fp)
+ self.assertIsNotNone(content_in_uncompressed_apex['apex_payload'])
+ self.assertIsNotNone(content_in_uncompressed_apex['digest'])
+
+ # Verify that CAPEX manifest contains digest of original_apex
+ manifest_string = self._get_manifest_string(
+ content_in_compressed_apex['apex_manifest.pb'])
+ self.assertIn('originalApexDigest: "'
+ + content_in_uncompressed_apex['digest'] + '"', manifest_string)
+
+ for i in ['apex_manifest.json', 'apex_manifest.pb', 'apex_pubkey',
+ 'apex_build_info.pb', 'AndroidManifest.xml']:
+ if i in content_in_uncompressed_apex:
+ if i == 'apex_manifest.pb':
+ # Get rid of originalApexDigest field, which should be the
+ # only difference
+ self._unset_original_apex_digest(content_in_compressed_apex[i])
+ self.assertEqual(get_sha1sum(content_in_compressed_apex[i]),
+ get_sha1sum(content_in_uncompressed_apex[i]))
+
+ def test_decompression(self):
+ # setup: create compressed APEX
+ uncompressed_apex_fp = os.path.join(get_current_dir(), TEST_APEX + '.apex')
+ compressed_apex_fp = self._compress_apex(uncompressed_apex_fp)
+
+ # Decompress it
+ decompressed_apex_fp = self._decompress_apex(compressed_apex_fp)
+
+ # Verify type of the apex is 'UNCOMPRESSED'
+ self.assertEqual(self._get_type(decompressed_apex_fp), 'UNCOMPRESSED')
+
+ # Verify decompressed APEX is same as uncompressed APEX
+ self.assertEqual(get_sha1sum(uncompressed_apex_fp),
+ get_sha1sum(decompressed_apex_fp),
+ 'Decompressed APEX is not same as uncompressed APEX')
+
+ # Try decompressing uncompressed APEX. It should not work.
+ with self.assertRaises(RuntimeError) as error:
+ self._decompress_apex(uncompressed_apex_fp)
+
+ self.assertIn(uncompressed_apex_fp
+ + ' is not a compressed APEX', str(error.exception))
+
+ def test_only_original_apex_is_compressed(self):
+ uncompressed_apex_fp = os.path.join(get_current_dir(), TEST_APEX + '.apex')
+ compressed_apex_fp = self._compress_apex(uncompressed_apex_fp)
+
+ with ZipFile(compressed_apex_fp, 'r') as zip_obj:
+ self.assertEqual(zip_obj.getinfo('original_apex').compress_type,
+ ZIP_DEFLATED)
+ content_in_uncompressed_apex = self._get_container_files(
+ uncompressed_apex_fp)
+ for i in ['apex_manifest.json', 'apex_manifest.pb', 'apex_pubkey',
+ 'apex_build_info.pb', 'AndroidManifest.xml']:
+ if i in content_in_uncompressed_apex:
+ self.assertEqual(zip_obj.getinfo(i).compress_type, ZIP_STORED)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/tools/apex_compression_tool.py b/tools/apex_compression_tool.py
new file mode 100644
index 00000000..3c0f16d8
--- /dev/null
+++ b/tools/apex_compression_tool.py
@@ -0,0 +1,199 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2020 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.
+
+"""apex_compression_tool is a tool that can compress/decompress APEX.
+
+Example:
+ apex_compression_tool compress --input /apex/to/compress --output output/path
+ apex_compression_tool decompress --input /apex/to/decompress --output dir/
+ apex_compression_tool verify-compressed --input /file/to/check
+"""
+from __future__ import print_function
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+from zipfile import ZipFile
+
+import apex_manifest_pb2
+
+tool_path_list = None
+
+
+def FindBinaryPath(binary):
+ for path in tool_path_list:
+ binary_path = os.path.join(path, binary)
+ if os.path.exists(binary_path):
+ return binary_path
+ raise Exception('Failed to find binary ' + binary + ' in path ' +
+ ':'.join(tool_path_list))
+
+
+def RunCommand(cmd, verbose=False, env=None, expected_return_values=None):
+ expected_return_values = expected_return_values or {0}
+ env = env or {}
+ env.update(os.environ.copy())
+
+ cmd[0] = FindBinaryPath(cmd[0])
+
+ if verbose:
+ print('Running: ' + ' '.join(cmd))
+ p = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
+ output, _ = p.communicate()
+
+ if verbose or p.returncode not in expected_return_values:
+ print(output.rstrip())
+
+ assert p.returncode in expected_return_values, 'Failed to execute: ' \
+ + ' '.join(cmd)
+
+ return output, p.returncode
+
+
+def RunCompress(args, work_dir):
+ """RunCompress takes an uncompressed APEX and compresses into compressed APEX
+
+ Compressed apex will contain the following items:
+ - original_apex: The original uncompressed APEX
+ - Duplicates of various meta files inside the input APEX, e.g
+ AndroidManifest.xml, public_key
+
+ Args:
+ args.input: file path to uncompressed APEX
+ args.output: file path to where compressed APEX will be placed
+ work_dir: file path to a temporary folder
+ Returns:
+ True if compression was executed successfully, otherwise False
+ """
+ global tool_path_list
+ tool_path_list = args.apex_compression_tool_path
+
+ cmd = ['soong_zip']
+ cmd.extend(['-o', args.output])
+
+ # We want to put the input apex inside the compressed APEX with name
+ # "original_apex". So we create a hard link and put the renamed file inside
+ # the zip
+ original_apex = os.path.join(work_dir, 'original_apex')
+ os.link(args.input, original_apex)
+ cmd.extend(['-C', work_dir])
+ cmd.extend(['-f', original_apex])
+
+ # We also need to extract some files from inside of original_apex and zip
+ # together with compressed apex
+ with ZipFile(original_apex, 'r') as zip_obj:
+ extract_dir = os.path.join(work_dir, 'extract')
+ for meta_file in ['apex_manifest.json', 'apex_manifest.pb',
+ 'apex_pubkey', 'apex_build_info.pb',
+ 'AndroidManifest.xml']:
+ if meta_file in zip_obj.namelist():
+ zip_obj.extract(meta_file, path=extract_dir)
+ file_path = os.path.join(extract_dir, meta_file)
+ cmd.extend(['-C', extract_dir])
+ cmd.extend(['-f', file_path])
+ cmd.extend(['-s', meta_file])
+ # Extract the image for retrieving root digest
+ zip_obj.extract('apex_payload.img', path= work_dir)
+ image_path = os.path.join(work_dir, 'apex_payload.img')
+
+ # Set digest of original_apex to apex_manifest.pb
+ apex_manifest_path = os.path.join(extract_dir, 'apex_manifest.pb')
+ assert AddOriginalApexDigestToManifest(apex_manifest_path, image_path)
+
+ # Don't forget to compress
+ cmd.extend(['-L', '9'])
+
+ RunCommand(cmd, verbose=True)
+
+ return True
+
+
+def AddOriginalApexDigestToManifest(capex_manifest_path, apex_image_path):
+ # Retrieve the root digest of the image
+ avbtool_cmd = [
+ 'avbtool',
+ 'print_partition_digests', '--image',
+ apex_image_path]
+ # avbtool_cmd output has format "<name>: <value>"
+ root_digest = RunCommand(avbtool_cmd, True)[0].decode().split(': ')[1].strip()
+ # Update the manifest proto file
+ with open(capex_manifest_path, 'rb') as f:
+ pb = apex_manifest_pb2.ApexManifest()
+ pb.ParseFromString(f.read())
+ # Populate CompressedApexMetadata
+ capex_metadata = apex_manifest_pb2.ApexManifest().CompressedApexMetadata()
+ capex_metadata.originalApexDigest = root_digest
+ # Set updated value to protobuf
+ pb.capexMetadata.CopyFrom(capex_metadata)
+ with open(capex_manifest_path, 'wb') as f:
+ f.write(pb.SerializeToString())
+ return True
+
+
+def ParseArgs(argv):
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(required=True, dest='cmd')
+
+ # Handle sub-command "compress"
+ parser_compress = subparsers.add_parser('compress',
+ help='compresses an APEX')
+ parser_compress.add_argument('--input', type=str, required=True,
+ help='path to input APEX file that will be '
+ 'compressed')
+ parser_compress.add_argument('--output', type=str, required=True,
+ help='output path to compressed APEX file')
+ apex_compression_tool_path_in_environ = \
+ 'APEX_COMPRESSION_TOOL_PATH' in os.environ
+ parser_compress.add_argument(
+ '--apex_compression_tool_path',
+ required=not apex_compression_tool_path_in_environ,
+ default=os.environ['APEX_COMPRESSION_TOOL_PATH'].split(':')
+ if apex_compression_tool_path_in_environ else None,
+ type=lambda s: s.split(':'),
+ help="""A list of directories containing all the tools used by
+ apex_compression_tool (e.g. soong_zip etc.) separated by ':'. Can also
+ be set using the APEX_COMPRESSION_TOOL_PATH environment variable""")
+ parser_compress.set_defaults(func=RunCompress)
+
+ return parser.parse_args(argv)
+
+
+class TempDirectory(object):
+
+ def __enter__(self):
+ self.name = tempfile.mkdtemp()
+ return self.name
+
+ def __exit__(self, *unused):
+ shutil.rmtree(self.name)
+
+
+def main(argv):
+ args = ParseArgs(argv)
+
+ with TempDirectory() as work_dir:
+ success = args.func(args, work_dir)
+
+ if not success:
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/tools/create_apex_skeleton.sh b/tools/create_apex_skeleton.sh
new file mode 100644
index 00000000..818e119a
--- /dev/null
+++ b/tools/create_apex_skeleton.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Creates an apex stub in a subdirectory named after the package name. Edit the APEX_NAME variable
+# before running.
+
+APEX_NAME=com.android.yourpackagenamehere
+
+mkdir ${APEX_NAME}
+cd ${APEX_NAME}
+
+cat > Android.bp <<EOF
+// Copyright (C) 2020 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.
+apex_key {
+ name: "${APEX_NAME}.key",
+ public_key: "${APEX_NAME}.avbpubkey",
+ private_key: "${APEX_NAME}.pem",
+}
+
+android_app_certificate {
+ name: "${APEX_NAME}.certificate",
+ certificate: "${APEX_NAME}",
+}
+
+apex {
+ name: "${APEX_NAME}",
+ manifest: "manifest.json",
+ file_contexts: ":apex.test-file_contexts", // Default, please edit, see go/android-apex-howto
+ key: "${APEX_NAME}.key",
+}
+EOF
+
+openssl genrsa -out ${APEX_NAME}.pem 4096
+avbtool extract_public_key --key ${APEX_NAME}.pem --output ${APEX_NAME}.avbpubkey
+
+cat > csr.conf <<EOF
+[req]
+default_bits = 4096
+distinguished_name = dn
+prompt = no
+
+[dn]
+C="US"
+ST="California"
+L="Mountain View"
+O="Android"
+OU="Android"
+emailAddress="android@android.com"
+CN="${APEX_NAME}"
+EOF
+
+openssl req -x509 -config csr.conf -newkey rsa:4096 -nodes -days 999999 -keyout key.pem -out ${APEX_NAME}.x509.pem
+rm csr.conf
+openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out ${APEX_NAME}.pk8 -nocrypt
+rm key.pem
+
+cat > manifest.json << EOF
+{
+ "name": "${APEX_NAME}",
+ "version": 1
+}
+EOF
diff --git a/tools/deapexer.py b/tools/deapexer.py
index b170d90f..92001c9d 100644
--- a/tools/deapexer.py
+++ b/tools/deapexer.py
@@ -24,23 +24,29 @@ To extract content of an APEX to the given directory:
from __future__ import print_function
import argparse
+import apex_manifest
+import enum
import os
import shutil
import sys
import subprocess
import tempfile
import zipfile
-import apex_manifest
+
+BLOCK_SIZE = 4096
class ApexImageEntry(object):
- def __init__(self, name, base_dir, permissions, size, is_directory=False, is_symlink=False):
+ def __init__(self, name, base_dir, permissions, size, ino, extents, is_directory=False,
+ is_symlink=False):
self._name = name
self._base_dir = base_dir
self._permissions = permissions
self._size = size
self._is_directory = is_directory
self._is_symlink = is_symlink
+ self._ino = ino
+ self._extents = extents
@property
def name(self):
@@ -70,6 +76,14 @@ class ApexImageEntry(object):
def size(self):
return self._size
+ @property
+ def ino(self):
+ return self._ino
+
+ @property
+ def extents(self):
+ return self._extents
+
def __str__(self):
ret = ''
if self._is_directory:
@@ -152,10 +166,43 @@ class Apex(object):
name = parts[5]
if not name:
continue
+ ino = parts[1]
bits = parts[2]
size = parts[6]
+ extents = []
+ is_symlink = bits[1]=='2'
+ is_directory=bits[1]=='4'
+
+ if not is_symlink and not is_directory:
+ process = subprocess.Popen([self._debugfs, '-R', 'dump_extents <%s>' % ino,
+ self._payload], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True)
+ stdout, _ = process.communicate()
+ # Output of dump_extents for an inode fragmented in 3 blocks (length and addresses represent
+ # block-sized sections):
+ # Level Entries Logical Physical Length Flags
+ # 0/ 0 1/ 3 0 - 0 18 - 18 1
+ # 0/ 0 2/ 3 1 - 15 20 - 34 15
+ # 0/ 0 3/ 3 16 - 1863 37 - 1884 1848
+ res = str(stdout).splitlines()
+ res.pop(0) # the first line contains only columns names
+ left_length = int(size)
+ try: # dump_extents sometimes has an unexpected output
+ for line in res:
+ tokens = line.split()
+ offset = int(tokens[7]) * BLOCK_SIZE
+ length = min(int(tokens[-1]) * BLOCK_SIZE, left_length)
+ left_length -= length
+ extents.append((offset, length))
+ if (left_length != 0): # dump_extents sometimes fails to display "hole" blocks
+ raise ValueError
+ except:
+ extents = [] # [] means that we failed to retrieve the file location successfully
+
entries.append(ApexImageEntry(name, base_dir=path, permissions=int(bits[3:], 8), size=size,
- is_directory=bits[1]=='4', is_symlink=bits[1]=='2'))
+ is_directory=is_directory, is_symlink=is_symlink, ino=ino,
+ extents=extents))
+
return ApexImageDirectory(path, entries, self)
def _extract(self, path, dest):
@@ -168,17 +215,38 @@ class Apex(object):
def RunList(args):
+ if GetType(args.apex) == ApexType.COMPRESSED:
+ with tempfile.TemporaryDirectory() as temp:
+ decompressed_apex = os.path.join(temp, 'temp.apex')
+ decompress(args.apex, decompressed_apex)
+ args.apex = decompressed_apex
+
+ RunList(args)
+ return
+
with Apex(args) as apex:
for e in apex.list(is_recursive=True):
if e.is_directory:
continue
+ res = ''
if args.size:
- print(e.size, e.full_path)
- else:
- print(e.full_path)
+ res += e.size + ' '
+ res += e.full_path
+ if args.extents:
+ res += ' [' + '-'.join(str(x) for x in e.extents) + ']'
+ print(res)
def RunExtract(args):
+ if GetType(args.apex) == ApexType.COMPRESSED:
+ with tempfile.TemporaryDirectory() as temp:
+ decompressed_apex = os.path.join(temp, "temp.apex")
+ decompress(args.apex, decompressed_apex)
+ args.apex = decompressed_apex
+
+ RunExtract(args)
+ return
+
with Apex(args) as apex:
if not os.path.exists(args.dest):
os.makedirs(args.dest, mode=0o755)
@@ -186,15 +254,75 @@ def RunExtract(args):
shutil.rmtree(os.path.join(args.dest, "lost+found"))
+class ApexType(enum.Enum):
+ INVALID = 0
+ UNCOMPRESSED = 1
+ COMPRESSED = 2
+
+
+def GetType(apex_path):
+ with zipfile.ZipFile(apex_path, 'r') as zip_file:
+ names = zip_file.namelist()
+ has_payload = 'apex_payload.img' in names
+ has_original_apex = 'original_apex' in names
+ if has_payload and has_original_apex:
+ return ApexType.INVALID
+ if has_payload:
+ return ApexType.UNCOMPRESSED
+ if has_original_apex:
+ return ApexType.COMPRESSED
+ return ApexType.INVALID
+
+
def RunInfo(args):
- manifest = apex_manifest.fromApex(args.apex)
- print(apex_manifest.toJsonString(manifest))
+ if args.print_type:
+ res = GetType(args.apex)
+ if res == ApexType.INVALID:
+ print(args.apex + ' is not a valid apex')
+ sys.exit(1)
+ print(res.name)
+ else:
+ manifest = apex_manifest.fromApex(args.apex)
+ print(apex_manifest.toJsonString(manifest))
+
+
+def RunDecompress(args):
+ """RunDecompress takes path to compressed APEX and decompresses it to
+ produce the original uncompressed APEX at give output path
+
+ See apex_compression_tool.py#RunCompress for details on compressed APEX
+ structure.
+
+ Args:
+ args.input: file path to compressed APEX
+ args.output: file path to where decompressed APEX will be placed
+ """
+ compressed_apex_fp = args.input
+ decompressed_apex_fp = args.output
+ return decompress(compressed_apex_fp, decompressed_apex_fp)
+
+def decompress(compressed_apex_fp, decompressed_apex_fp):
+ if os.path.exists(decompressed_apex_fp):
+ print("Output path '" + decompressed_apex_fp + "' already exists")
+ sys.exit(1)
+
+ with zipfile.ZipFile(compressed_apex_fp, 'r') as zip_obj:
+ if 'original_apex' not in zip_obj.namelist():
+ print(compressed_apex_fp + ' is not a compressed APEX. Missing '
+ "'original_apex' file inside it.")
+ sys.exit(1)
+ # Rename original_apex file to what user provided as output filename
+ original_apex_info = zip_obj.getinfo('original_apex')
+ original_apex_info.filename = os.path.basename(decompressed_apex_fp)
+ # Extract the original_apex as desired name
+ zip_obj.extract(original_apex_info,
+ path=os.path.dirname(decompressed_apex_fp))
def main(argv):
parser = argparse.ArgumentParser()
- debugfs_default = 'debugfs' # assume in PATH by default
+ debugfs_default = None
if 'ANDROID_HOST_OUT' in os.environ:
debugfs_default = '%s/bin/debugfs_static' % os.environ['ANDROID_HOST_OUT']
parser.add_argument('--debugfs_path', help='The path to debugfs binary', default=debugfs_default)
@@ -204,6 +332,7 @@ def main(argv):
parser_list = subparsers.add_parser('list', help='prints content of an APEX to stdout')
parser_list.add_argument('apex', type=str, help='APEX file')
parser_list.add_argument('--size', help='also show the size of the files', action="store_true")
+ parser_list.add_argument('--extents', help='also show the location of the files', action="store_true")
parser_list.set_defaults(func=RunList)
parser_extract = subparsers.add_parser('extract', help='extracts content of an APEX to the given '
@@ -214,10 +343,31 @@ def main(argv):
parser_info = subparsers.add_parser('info', help='prints APEX manifest')
parser_info.add_argument('apex', type=str, help='APEX file')
+ parser_info.add_argument('--print-type',
+ help='Prints type of the apex (COMPRESSED or UNCOMPRESSED)',
+ action='store_true')
parser_info.set_defaults(func=RunInfo)
+ # Handle sub-command "decompress"
+ parser_decompress = subparsers.add_parser('decompress',
+ help='decompresses a compressed '
+ 'APEX')
+ parser_decompress.add_argument('--input', type=str, required=True,
+ help='path to compressed APEX file that '
+ 'will be decompressed')
+ parser_decompress.add_argument('--output', type=str, required=True,
+ help='output directory path where '
+ 'decompressed APEX will be extracted')
+ parser_decompress.set_defaults(func=RunDecompress)
+
args = parser.parse_args(argv)
+ debugfs_required_for_cmd = ['list', 'extract']
+ if args.cmd in debugfs_required_for_cmd and not args.debugfs_path:
+ print('ANDROID_HOST_OUT environment variable is not defined, --debugfs_path must be set',
+ file=sys.stderr)
+ sys.exit(1)
+
args.func(args)