first commit
This commit is contained in:
commit
fdf2a6817f
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v1.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v1.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_additional_file.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_additional_file.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_additional_folder.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_additional_folder.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_different_certificate.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_different_certificate.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_different_package_name.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_different_package_name.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_no_hashtree.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_no_hashtree.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_sdk_target_p.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_sdk_target_p.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_signed_bob.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "6508977"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "rvc-dev"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_unsigned_payload.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_unsigned_payload.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_wrong_sha.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_wrong_sha.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v3.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_signed_bob.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3_signed_bob.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v1.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v1.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_additional_file.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_additional_file.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_additional_folder.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_additional_folder.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_different_certificate.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_different_certificate.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_different_package_name.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_different_package_name.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_no_hashtree.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_no_hashtree.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_sdk_target_p.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_sdk_target_p.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_signed_bob.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "6508977"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "rvc-dev"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_unsigned_payload.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_unsigned_payload.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_wrong_sha.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_wrong_sha.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
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: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_signed_bob.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3_signed_bob.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
drops {
|
||||
android_build_drop {
|
||||
build_id: "8572644"
|
||||
target: "CtsShim"
|
||||
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
|
||||
}
|
||||
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
|
||||
version: ""
|
||||
version_group: ""
|
||||
git_project: "platform/system/apex"
|
||||
git_branch: "tm-dev"
|
||||
transform: TRANSFORM_NONE
|
||||
transform_options {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// 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",
|
||||
],
|
||||
}
|
||||
|
||||
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"],
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
# If you don't need to do a full clean build but would like to touch
|
||||
# a file or delete some intermediate files, add a clean step to the end
|
||||
# of the list. These steps will only be run once, if they haven't been
|
||||
# run before.
|
||||
#
|
||||
# E.g.:
|
||||
# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
|
||||
# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
|
||||
#
|
||||
# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
|
||||
# files that are missing or have been moved.
|
||||
#
|
||||
# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
|
||||
# Use $(OUT_DIR) to refer to the "out" directory.
|
||||
#
|
||||
# If you need to re-do something that's already mentioned, just copy
|
||||
# the command and add it to the bottom of the list. E.g., if a change
|
||||
# that you made last week required touching a file and a change you
|
||||
# made today requires touching the same file, just copy the old
|
||||
# touch step and add it to the end of the list.
|
||||
#
|
||||
# ************************************************
|
||||
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
|
||||
# ************************************************
|
||||
|
||||
# For example:
|
||||
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
|
||||
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
|
||||
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
|
||||
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
|
||||
|
||||
# APEX keys are moved from /*/etc/security/apex to inside of APEXes
|
||||
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/security/apex)
|
||||
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/security/apex)
|
||||
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/security/apex)
|
||||
# ************************************************
|
||||
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
|
||||
# ************************************************
|
|
@ -0,0 +1,6 @@
|
|||
dariofreni@google.com
|
||||
ioffe@google.com
|
||||
jiyong@google.com
|
||||
maco@google.com
|
||||
malchev@google.com
|
||||
narayan@google.com
|
|
@ -0,0 +1,12 @@
|
|||
[Hook Scripts]
|
||||
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
|
||||
|
||||
[Builtin Hooks]
|
||||
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
|
|
@ -0,0 +1 @@
|
|||
BasedOnStyle: Google
|
|
@ -0,0 +1,573 @@
|
|||
// 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", // 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", // found in apexd_utils.h
|
||||
"misc-unused-using-decls",
|
||||
"misc-use-after-move",
|
||||
// "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", // 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 {
|
||||
name: "apex_flags_defaults",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
|
||||
// Some extra flags.
|
||||
"-fstrict-aliasing",
|
||||
"-Wredundant-decls",
|
||||
"-Wshadow",
|
||||
"-Wstrict-aliasing",
|
||||
"-Wthread-safety",
|
||||
"-Wthread-safety-negative",
|
||||
"-Wunreachable-code",
|
||||
"-Wunreachable-code-break",
|
||||
"-Wunreachable-code-return",
|
||||
"-Wunused",
|
||||
"-Wused-but-marked-unused",
|
||||
],
|
||||
tidy: true,
|
||||
tidy_checks: tidy_errors,
|
||||
tidy_checks_as_errors: tidy_errors,
|
||||
tidy_flags: [
|
||||
"-format-style=file",
|
||||
"-header-filter=system/apex/",
|
||||
],
|
||||
}
|
||||
|
||||
soong_config_module_type {
|
||||
name: "apexd_cc_defaults",
|
||||
module_type: "cc_defaults",
|
||||
config_namespace: "ANDROID",
|
||||
bool_variables: [
|
||||
"target_board_auto",
|
||||
],
|
||||
properties: [
|
||||
"cppflags",
|
||||
],
|
||||
}
|
||||
|
||||
apexd_cc_defaults {
|
||||
name: "libapexd-deps",
|
||||
defaults: ["libapex-deps"],
|
||||
shared_libs: [
|
||||
"libbinder",
|
||||
"liblog",
|
||||
"liblogwrap",
|
||||
],
|
||||
static_libs: [
|
||||
"libapex",
|
||||
"libavb",
|
||||
"libdm",
|
||||
"libext2_uuid",
|
||||
"libsigningutils",
|
||||
"libverity_tree",
|
||||
"libvold_binder",
|
||||
"libxml2",
|
||||
],
|
||||
whole_static_libs: ["com.android.sysprop.apex"],
|
||||
soong_config_variables: {
|
||||
target_board_auto: {
|
||||
cppflags: ["-DDISABLE_LOOP_IO_CONFIG"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
aidl_interface {
|
||||
name: "apex_aidl_interface",
|
||||
unstable: true,
|
||||
srcs: [
|
||||
"aidl/android/apex/ApexInfo.aidl",
|
||||
"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",
|
||||
backend: {
|
||||
java: {
|
||||
sdk_version: "28",
|
||||
},
|
||||
ndk: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "apexd",
|
||||
defaults: [
|
||||
"apex_flags_defaults",
|
||||
"libapex-deps",
|
||||
"libapexd-deps",
|
||||
"libapexservice-deps",
|
||||
],
|
||||
srcs: [
|
||||
"apexd_main.cpp",
|
||||
],
|
||||
static_libs: [
|
||||
"libapex",
|
||||
"libapexd",
|
||||
"libapexd_checkpoint_vold",
|
||||
"libapexservice",
|
||||
],
|
||||
init_rc: ["apexd.rc"],
|
||||
// Just like the init, apexd should be able to run without
|
||||
// any APEX activated. To do so, it uses the bootstrap linker
|
||||
// and the bootstrap bionic libraries.
|
||||
bootstrap: true,
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libapexd",
|
||||
defaults: [
|
||||
"apex_flags_defaults",
|
||||
"libapexd-deps",
|
||||
],
|
||||
srcs: [
|
||||
"apex_classpath.cpp",
|
||||
"apex_database.cpp",
|
||||
"apexd.cpp",
|
||||
"apexd_lifecycle.cpp",
|
||||
"apexd_loop.cpp",
|
||||
"apexd_private.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.
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libapexd_checkpoint_vold",
|
||||
defaults: ["apex_flags_defaults"],
|
||||
srcs: [ "apexd_checkpoint_vold.cpp" ],
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libutils",
|
||||
"libvold_binder",
|
||||
],
|
||||
export_include_dirs: ["."],
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
name: "libapexservice-deps",
|
||||
shared_libs: [
|
||||
"apex_aidl_interface-cpp",
|
||||
"libbinder",
|
||||
"libutils",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libapexservice",
|
||||
defaults: [
|
||||
"apex_flags_defaults",
|
||||
"libapexd-deps",
|
||||
"libapexservice-deps",
|
||||
],
|
||||
srcs: ["apexservice.cpp"],
|
||||
static_libs: [
|
||||
"libapexd",
|
||||
],
|
||||
cflags: [
|
||||
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
|
||||
],
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
name: "libapex-deps",
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libcrypto",
|
||||
"libcutils",
|
||||
"libprotobuf-cpp-full",
|
||||
"libziparchive",
|
||||
"libselinux",
|
||||
],
|
||||
static_libs: [
|
||||
"lib_apex_session_state_proto",
|
||||
"lib_apex_manifest_proto",
|
||||
"lib_microdroid_metadata_proto",
|
||||
"libavb",
|
||||
"libverity_tree",
|
||||
],
|
||||
static: {
|
||||
whole_static_libs: ["libc++fs"],
|
||||
},
|
||||
cpp_std: "experimental",
|
||||
shared: {
|
||||
static_libs: ["libc++fs"],
|
||||
},
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libapex",
|
||||
defaults: [
|
||||
"apex_flags_defaults",
|
||||
"libapex-deps"
|
||||
],
|
||||
srcs: [
|
||||
"apex_file.cpp",
|
||||
"apex_file_repository.cpp",
|
||||
"apex_manifest.cpp",
|
||||
"apex_shim.cpp",
|
||||
"apexd_verity.cpp",
|
||||
],
|
||||
host_supported: true,
|
||||
target: {
|
||||
darwin: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
header_libs: [
|
||||
"libutils_headers",
|
||||
],
|
||||
export_header_lib_headers: [
|
||||
"libutils_headers",
|
||||
],
|
||||
export_include_dirs: ["."],
|
||||
}
|
||||
|
||||
genrule {
|
||||
// Generates an apex which has a different manifest outside the filesystem
|
||||
// image.
|
||||
name: "gen_manifest_mismatch_apex",
|
||||
out: ["apex.apexd_test_manifest_mismatch.apex"],
|
||||
srcs: [":apex.apexd_test"],
|
||||
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_manifest_mismatch.apex"
|
||||
}
|
||||
|
||||
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",
|
||||
out: ["apex.apexd_test_corrupt_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=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)/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 {
|
||||
// Extract the root digest with avbtool
|
||||
name: "apex.apexd_test_erofs_digest",
|
||||
out: ["apex.apexd_test_erofs_digest.txt"],
|
||||
srcs: [":apex.apexd_test_erofs"],
|
||||
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: [
|
||||
"apex_flags_defaults",
|
||||
"libapex-deps",
|
||||
"libapexd-deps"
|
||||
],
|
||||
require_root: true,
|
||||
cflags: [
|
||||
// Otherwise libgmock won't compile.
|
||||
"-Wno-used-but-marked-unused",
|
||||
],
|
||||
data: [
|
||||
":apex.apexd_test",
|
||||
":apex.apexd_test_erofs",
|
||||
":apex.apexd_test_f2fs",
|
||||
":apex.apexd_test_digest",
|
||||
":apex.apexd_test_erofs_digest",
|
||||
":apex.apexd_test_f2fs_digest",
|
||||
":apex.apexd_test_classpath",
|
||||
":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_v2",
|
||||
":apex.corrupted_b146895998",
|
||||
":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",
|
||||
":com.android.apex.cts.shim.v2_additional_file_prebuilt",
|
||||
":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",
|
||||
":com.android.sepolicy",
|
||||
":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_classpath_test.cpp",
|
||||
"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,
|
||||
compile_multilib: "first",
|
||||
static_libs: [
|
||||
"apex_aidl_interface-cpp",
|
||||
"libapex",
|
||||
"libapexd",
|
||||
"libfstab",
|
||||
"libgmock",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbinder",
|
||||
"libfs_mgr",
|
||||
"libutils",
|
||||
],
|
||||
generated_sources: ["apex-info-list"],
|
||||
test_suites: ["device-tests"],
|
||||
test_config: "AndroidTest.xml",
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "flattened_apex_test",
|
||||
defaults: [
|
||||
"apex_flags_defaults",
|
||||
"libapex-deps",
|
||||
"libapexd-deps"
|
||||
],
|
||||
srcs: ["flattened_apex_test.cpp"],
|
||||
host_supported: false,
|
||||
compile_multilib: "first",
|
||||
static_libs: [
|
||||
"libapex",
|
||||
"libapexd",
|
||||
],
|
||||
test_suites: ["device-tests"],
|
||||
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,
|
||||
}
|
||||
|
||||
xsd_config {
|
||||
name: "apex-info-list-tinyxml",
|
||||
srcs: ["ApexInfoList.xsd"],
|
||||
package_name: "com.android.apex",
|
||||
api_dir: "apex-info-list-api",
|
||||
gen_writer: true,
|
||||
tinyxml: true,
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?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="Runs ApexTestCases">
|
||||
<option name="test-suite-tag" value="apct" />
|
||||
<option name="test-suite-tag" value="apct-native" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
|
||||
<option name="cleanup" value="true" />
|
||||
<!-- Note: despite how this line reads, it will push the complete testcase directory, thus
|
||||
all apexes that are required by the blueprint's data[] tag. -->
|
||||
<option name="push" value="ApexTestCases->/data/local/tmp/ApexTestCases" />
|
||||
</target_preparer>
|
||||
|
||||
<!-- The test runs as root to prepare the temporary directory, make selinux adjustments
|
||||
and so on to provide files that apexd can consume. This is done to avoid dependencies
|
||||
on higher levels (e.g., PackageInstaller). -->
|
||||
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
|
||||
|
||||
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
|
||||
<option name="cleanup" value="true" />
|
||||
<option name="remount-system" value="true" />
|
||||
<option name="push" value="apex.apexd_test.apex->/system_ext/apex/apex.apexd_test.apex" />
|
||||
<option name="push" value="apex.apexd_test_different_app.apex->/system_ext/apex/apex.apexd_test_different_app.apex" />
|
||||
</target_preparer>
|
||||
|
||||
<!-- system_server might still hold a reference to apexservice. This means that apexd is still
|
||||
running, and test apexes pushed in the PushFilePreparer above are not yet scanned.
|
||||
One way to solve this is to reboot a device, but that would significantly increase
|
||||
execution of this test module. Instead, force a GC in system_server by sending kill -10. -->
|
||||
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
|
||||
<option name="run-command" value="shell kill -10 $(pidof system_server)" />
|
||||
<option name="teardown-command" value="shell kill -10 $(pidof system_server)" />
|
||||
</target_preparer>
|
||||
|
||||
<test class="com.android.tradefed.testtype.GTest" >
|
||||
<!-- Note: despite how these lines read, the test will run nicely separated out
|
||||
of a subfolder. -->
|
||||
<option name="native-test-device-path" value="/data/local/tmp" />
|
||||
<option name="module-name" value="ApexTestCases" />
|
||||
</test>
|
||||
</configuration>
|
|
@ -0,0 +1,39 @@
|
|||
<?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:attribute name="provideSharedApexLibs" type="xs:boolean" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "ApexTestCases"
|
||||
},
|
||||
{
|
||||
"name": "MicrodroidHostTestCases"
|
||||
}
|
||||
],
|
||||
"imports": [
|
||||
{
|
||||
"path": "system/apex/tests"
|
||||
},
|
||||
{
|
||||
"path": "vendor/xts/gts-tests/hostsidetests/stagedinstall"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package android.apex;
|
||||
|
||||
parcelable ApexInfo {
|
||||
@utf8InCpp String moduleName;
|
||||
@utf8InCpp String modulePath;
|
||||
@utf8InCpp String preinstalledModulePath;
|
||||
long versionCode;
|
||||
@utf8InCpp String versionName;
|
||||
boolean isFactory;
|
||||
boolean isActive;
|
||||
|
||||
// Populated only for getStagedApex() API
|
||||
boolean hasClassPathJars;
|
||||
|
||||
// Will be set to true if during this boot a different APEX package of the APEX was
|
||||
// activated, than in the previous boot.
|
||||
// This can happen in the following situations:
|
||||
// 1. It was part of the staged session that was applied during this boot.
|
||||
// 2. A compressed system APEX was decompressed during this boot.
|
||||
// 3. apexd failed to activate an APEX on /data/apex/active (that was successfully
|
||||
// activated during last boot) and needed to fallback to pre-installed counterpart.
|
||||
// Note: this field can only be set to true during boot, after boot is completed
|
||||
// (sys.boot_completed = 1) value of this field will always be false.
|
||||
boolean activeApexChanged;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package android.apex;
|
||||
|
||||
import android.apex.ApexInfo;
|
||||
|
||||
parcelable ApexInfoList {
|
||||
ApexInfo[] apexInfos;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 android.apex;
|
||||
|
||||
parcelable ApexSessionInfo {
|
||||
int sessionId;
|
||||
// Maps to apex::proto::SessionState::State enum.
|
||||
boolean isUnknown;
|
||||
boolean isVerified;
|
||||
boolean isStaged;
|
||||
boolean isActivated;
|
||||
boolean isRevertInProgress;
|
||||
boolean isActivationFailed;
|
||||
boolean isSuccess;
|
||||
boolean isReverted;
|
||||
boolean isRevertFailed;
|
||||
@utf8InCpp String crashingNativeProcess;
|
||||
@utf8InCpp String errorMessage;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 android.apex;
|
||||
|
||||
parcelable ApexSessionParams {
|
||||
int sessionId = 0;
|
||||
int[] childSessionIds = {};
|
||||
boolean hasRollbackEnabled = false;
|
||||
boolean isRollback = false;
|
||||
int rollbackId = 0;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 android.apex;
|
||||
|
||||
parcelable CompressedApexInfo {
|
||||
@utf8InCpp String moduleName;
|
||||
long versionCode;
|
||||
long decompressedSize;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 android.apex;
|
||||
|
||||
import android.apex.CompressedApexInfo;
|
||||
|
||||
parcelable CompressedApexInfoList {
|
||||
CompressedApexInfo[] apexInfos;
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package android.apex;
|
||||
|
||||
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);
|
||||
void markStagedSessionReady(int session_id);
|
||||
void markStagedSessionSuccessful(int session_id);
|
||||
|
||||
ApexSessionInfo[] getSessions();
|
||||
ApexSessionInfo getStagedSessionInfo(int session_id);
|
||||
ApexInfo[] getStagedApexInfos(in ApexSessionParams params);
|
||||
ApexInfo[] getActivePackages();
|
||||
ApexInfo[] getAllPackages();
|
||||
|
||||
void abortStagedSession(int session_id);
|
||||
void revertActiveSessions();
|
||||
|
||||
/**
|
||||
* Copies the CE apex data directory for the given user to the backup
|
||||
* location.
|
||||
*/
|
||||
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
|
||||
* apex. Note the snapshot will be deleted after restoration succeeded.
|
||||
*/
|
||||
void restoreCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
|
||||
|
||||
/**
|
||||
* Deletes device-encrypted snapshots for the given rollback id.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void destroyCeSnapshotsNotSpecified(int user_id, in int[] retain_rollback_ids);
|
||||
|
||||
void unstagePackages(in @utf8InCpp List<String> active_package_paths);
|
||||
|
||||
/**
|
||||
* Returns the active package corresponding to |package_name| and null
|
||||
* if none exists.
|
||||
*/
|
||||
ApexInfo getActivePackage(in @utf8InCpp String package_name);
|
||||
|
||||
/**
|
||||
* Not meant for use outside of testing. The call will not be
|
||||
* functional on user builds.
|
||||
*/
|
||||
void stagePackages(in @utf8InCpp List<String> package_tmp_paths);
|
||||
/**
|
||||
* Not meant for use outside of testing. The call will not be
|
||||
* functional on user builds.
|
||||
*/
|
||||
void resumeRevertIfNeeded();
|
||||
/**
|
||||
* Forces apexd to remount all active packages.
|
||||
*
|
||||
* This call is mostly useful for speeding up development of APEXes.
|
||||
* Instead of going through a full APEX installation that requires a reboot,
|
||||
* developers can incorporate this method in much faster `adb sync` based
|
||||
* workflow:
|
||||
*
|
||||
* 1. adb shell stop
|
||||
* 2. adb sync
|
||||
* 3. adb shell cmd -w apexservice remountPackages
|
||||
* 4. adb shell start
|
||||
*
|
||||
* Note, that for an APEX package will be successfully remounted only if
|
||||
* there are no alive processes holding a reference to it.
|
||||
*
|
||||
* 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 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);
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// 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 boolean getProvideSharedApexLibs();
|
||||
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 setProvideSharedApexLibs(boolean);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
// Signature format: 2.0
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 "apex_classpath.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/scopeguard.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <logwrap/logwrap.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
using ::android::base::Error;
|
||||
using ::android::base::StringPrintf;
|
||||
|
||||
android::base::Result<ClassPath> ClassPath::DeriveClassPath(
|
||||
const std::vector<std::string>& temp_mounted_apex_paths,
|
||||
const std::string& sdkext_module_name) {
|
||||
if (temp_mounted_apex_paths.empty()) {
|
||||
return Error()
|
||||
<< "Invalid argument: There are no APEX to derive claspath from";
|
||||
}
|
||||
// Call derive_classpath binary to generate required information
|
||||
|
||||
// Prefer using the binary from staged session if possible
|
||||
std::string apex_of_binary =
|
||||
StringPrintf("/apex/%s", sdkext_module_name.c_str());
|
||||
for (const auto& temp_mounted_apex_path : temp_mounted_apex_paths) {
|
||||
if (temp_mounted_apex_path.starts_with(apex_of_binary + "@")) {
|
||||
apex_of_binary = temp_mounted_apex_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string binary_path =
|
||||
StringPrintf("%s/bin/derive_classpath", apex_of_binary.c_str());
|
||||
std::string scan_dirs_flag =
|
||||
StringPrintf("--scan-dirs=%s",
|
||||
android::base::Join(temp_mounted_apex_paths, ",").c_str());
|
||||
|
||||
// Create a temp file to write output
|
||||
auto temp_output_path = "/apex/derive_classpath_temp";
|
||||
auto cleanup = [temp_output_path]() {
|
||||
android::base::RemoveFileIfExists(temp_output_path);
|
||||
};
|
||||
auto scope_guard = android::base::make_scope_guard(cleanup);
|
||||
// Cleanup to ensure we are creating an empty file
|
||||
cleanup();
|
||||
// Create the empty file where derive_classpath will write into
|
||||
std::ofstream _(temp_output_path);
|
||||
|
||||
const char* const argv[] = {binary_path.c_str(), scan_dirs_flag.c_str(),
|
||||
temp_output_path};
|
||||
auto rc = logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG,
|
||||
false, nullptr);
|
||||
if (rc != 0) {
|
||||
return Error() << "Running derive_classpath failed; binary path: " +
|
||||
binary_path;
|
||||
}
|
||||
|
||||
return ClassPath::ParseFromFile(temp_output_path);
|
||||
}
|
||||
|
||||
// Parse the string output into structured information
|
||||
// The raw output from derive_classpath has the following format:
|
||||
// ```
|
||||
// export BOOTCLASSPATH path/to/jar1:/path/to/jar2
|
||||
// export DEX2OATBOOTCLASSPATH
|
||||
// export SYSTEMSERVERCLASSPATH path/to/some/jar
|
||||
android::base::Result<ClassPath> ClassPath::ParseFromFile(
|
||||
const std::string& file_path) {
|
||||
ClassPath result;
|
||||
|
||||
std::string contents;
|
||||
auto read_status = android::base::ReadFileToString(file_path, &contents,
|
||||
/*follow_symlinks=*/false);
|
||||
if (!read_status) {
|
||||
return Error() << "Failed to read classpath info from file";
|
||||
}
|
||||
|
||||
// Jars in apex have the following format: /apex/<package-name>/*
|
||||
const std::regex capture_apex_package_name("^/apex/([^/]+)/");
|
||||
|
||||
for (const auto& line : android::base::Split(contents, "\n")) {
|
||||
// Split the line by space. The second element determines which type of
|
||||
// classpath we are dealing with and the third element are the jars
|
||||
// separated by :
|
||||
auto tokens = android::base::Split(line, " ");
|
||||
if (tokens.size() < 3) {
|
||||
continue;
|
||||
}
|
||||
auto jars_list = tokens[2];
|
||||
for (const auto& jar_path : android::base::Split(jars_list, ":")) {
|
||||
std::smatch match;
|
||||
if (std::regex_search(jar_path, match, capture_apex_package_name)) {
|
||||
auto package_name = match[1];
|
||||
result.AddPackageWithClasspathJars(package_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClassPath::AddPackageWithClasspathJars(const std::string& package) {
|
||||
packages_with_classpath_jars.insert(package);
|
||||
}
|
||||
|
||||
bool ClassPath::HasClassPathJars(const std::string& package) {
|
||||
return packages_with_classpath_jars.find(package) !=
|
||||
packages_with_classpath_jars.end();
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEX_CLASSPATH_H_
|
||||
#define ANDROID_APEXD_APEX_CLASSPATH_H_
|
||||
|
||||
#include <android-base/result.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
/**
|
||||
* An utility class that contains logic to extract classpath fragments
|
||||
* information from mounted APEX.
|
||||
*
|
||||
* The bulk of the work is done by derive_classpath binary, which is found
|
||||
* inside sdkext module. This class is a wrapper for calling that binary and
|
||||
* parsing its string output into a structured object.
|
||||
*/
|
||||
class ClassPath {
|
||||
static constexpr const char* kSdkExtModuleName = "com.android.sdkext";
|
||||
|
||||
public:
|
||||
static android::base::Result<ClassPath> DeriveClassPath(
|
||||
const std::vector<std::string>& temp_mounted_apex_paths,
|
||||
const std::string& sdkext_module_name = kSdkExtModuleName);
|
||||
|
||||
bool HasClassPathJars(const std::string& package);
|
||||
|
||||
// Exposed for testing only
|
||||
static android::base::Result<ClassPath> ParseFromFile(
|
||||
const std::string& file_path);
|
||||
|
||||
private:
|
||||
void AddPackageWithClasspathJars(const std::string& package);
|
||||
|
||||
std::set<std::string> packages_with_classpath_jars;
|
||||
};
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEXD_CHECKPOINT_H_
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 "apex_classpath.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/result-gmock.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
using android::base::WriteStringToFile;
|
||||
using android::base::testing::Ok;
|
||||
using android::base::testing::WithMessage;
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
TEST(ApexClassPathUnitTest, ParseFromFile) {
|
||||
TemporaryFile output;
|
||||
WriteStringToFile(
|
||||
"export BOOTCLASSPATH /apex/a/jar1:/apex/b/jar2\n"
|
||||
"export SYSTEMSERVERCLASSPATH\n"
|
||||
"export UNEXPECTED /apex/c/\n",
|
||||
output.path);
|
||||
auto result = ClassPath::ParseFromFile(output.path);
|
||||
ASSERT_THAT(result, Ok());
|
||||
|
||||
ASSERT_THAT(result->HasClassPathJars("a"), true);
|
||||
ASSERT_THAT(result->HasClassPathJars("b"), true);
|
||||
ASSERT_THAT(result->HasClassPathJars("c"), true);
|
||||
ASSERT_THAT(result->HasClassPathJars("d"), false);
|
||||
}
|
||||
|
||||
TEST(ApexClassPathUnitTest, ParseFromFileJarsNotInApex) {
|
||||
TemporaryFile output;
|
||||
// We accept jars with regex: /apex/<package-name>/*
|
||||
WriteStringToFile("export BOOTCLASSPATH a:b\n", output.path);
|
||||
auto result = ClassPath::ParseFromFile(output.path);
|
||||
ASSERT_THAT(result, Ok());
|
||||
|
||||
ASSERT_THAT(result->HasClassPathJars("a"), false);
|
||||
ASSERT_THAT(result->HasClassPathJars("b"), false);
|
||||
}
|
||||
|
||||
TEST(ApexClassPathUnitTest, ParseFromFilePackagesWithSamePrefix) {
|
||||
TemporaryFile output;
|
||||
WriteStringToFile(
|
||||
"export BOOTCLASSPATH /apex/media/:/apex/mediaprovider\n"
|
||||
"export SYSTEMSERVERCLASSPATH /apex/mediafoo/\n",
|
||||
output.path);
|
||||
auto result = ClassPath::ParseFromFile(output.path);
|
||||
ASSERT_THAT(result, Ok());
|
||||
|
||||
ASSERT_THAT(result->HasClassPathJars("media"), true);
|
||||
// "/apex/mediaprovider" did not end with /
|
||||
ASSERT_THAT(result->HasClassPathJars("mediaprovider"), false);
|
||||
// A prefix of an apex name present should not be accepted
|
||||
ASSERT_THAT(result->HasClassPathJars("m"), false);
|
||||
}
|
||||
|
||||
TEST(ApexClassPathUnitTest, ParseFromFileDoesNotExist) {
|
||||
auto result = ClassPath::ParseFromFile("/file/does/not/exist");
|
||||
ASSERT_THAT(result, HasError(WithMessage(HasSubstr(
|
||||
"Failed to read classpath info from file"))));
|
||||
}
|
||||
|
||||
TEST(ApexClassPathUnitTest, ParseFromFileEmptyJars) {
|
||||
TemporaryFile output;
|
||||
WriteStringToFile(
|
||||
"export BOOTCLASSPATH\n"
|
||||
"export SYSTEMSERVERCLASSPATH \n"
|
||||
"export DEX2OATBOOTCLASSPATH \n",
|
||||
output.path);
|
||||
auto result = ClassPath::ParseFromFile(output.path);
|
||||
ASSERT_THAT(result, Ok());
|
||||
}
|
||||
|
||||
TEST(ApexClassPathUnitTest, DeriveClassPathNoStagedApex) {
|
||||
auto result = ClassPath::DeriveClassPath({});
|
||||
ASSERT_THAT(
|
||||
result,
|
||||
HasError(WithMessage(HasSubstr(
|
||||
"Invalid argument: There are no APEX to derive claspath from"))));
|
||||
}
|
||||
|
||||
TEST(ApexClassPathUnitTest, DeriveClassPathPreferBinaryInStagedApex) {
|
||||
// Default location uses provided package name to compose binary path
|
||||
auto result = ClassPath::DeriveClassPath({"/apex/temp@123"}, "different");
|
||||
ASSERT_THAT(result,
|
||||
HasError(WithMessage(HasSubstr(
|
||||
"binary path: /apex/different/bin/derive_classpath"))));
|
||||
|
||||
// When staged apex has same package name, we use that location instead
|
||||
result = ClassPath::DeriveClassPath({"/apex/temp@123"}, "temp");
|
||||
ASSERT_THAT(result,
|
||||
HasError(WithMessage(HasSubstr(
|
||||
"binary path: /apex/temp@123/bin/derive_classpath"))));
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
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";
|
||||
static const std::vector<std::string> kApexPackageBuiltinDirs = {
|
||||
kApexPackageSystemDir,
|
||||
kApexPackageSystemExtDir,
|
||||
"/product/apex",
|
||||
kApexPackageVendorDir,
|
||||
};
|
||||
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";
|
||||
|
||||
static constexpr const char* kDeSysDataDir = "/data/misc";
|
||||
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";
|
||||
static constexpr const char* kMultiApexSelectPersistPrefix =
|
||||
"persist.vendor.apex.";
|
||||
static constexpr const char* kMultiApexSelectBootconfigPrefix =
|
||||
"ro.boot.vendor.apex.";
|
||||
|
||||
static constexpr const char* kVmPayloadMetadataPartitionProp =
|
||||
"apexd.payload_metadata.path";
|
||||
static constexpr const std::chrono::seconds kBlockApexWaitTime(10);
|
||||
|
||||
static constexpr const char* kMetadataSepolicyStagedDir =
|
||||
"/metadata/sepolicy/staged";
|
||||
|
||||
// Banned APEX names
|
||||
static const std::unordered_set<std::string> kBannedApexName = {
|
||||
kApexSharedLibsSubDir, // To avoid conflicts with predefined
|
||||
// /apex/sharedlibs directory
|
||||
};
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* 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_database.h"
|
||||
#include "apex_constants.h"
|
||||
#include "apex_file.h"
|
||||
#include "apexd_utils.h"
|
||||
#include "string_log.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/result.h>
|
||||
#include <android-base/strings.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#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;
|
||||
using android::base::Split;
|
||||
using android::base::StartsWith;
|
||||
using android::base::Trim;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
namespace {
|
||||
|
||||
using MountedApexData = MountedApexDatabase::MountedApexData;
|
||||
|
||||
enum BlockDeviceType {
|
||||
UnknownDevice,
|
||||
LoopDevice,
|
||||
DeviceMapperDevice,
|
||||
};
|
||||
|
||||
const fs::path kDevBlock = "/dev/block";
|
||||
const fs::path kSysBlock = "/sys/block";
|
||||
|
||||
class BlockDevice {
|
||||
std::string name; // loopN, dm-N, ...
|
||||
public:
|
||||
explicit BlockDevice(const fs::path& path) { name = path.filename(); }
|
||||
|
||||
BlockDeviceType GetType() const {
|
||||
if (StartsWith(name, "loop")) return LoopDevice;
|
||||
if (StartsWith(name, "dm-")) return DeviceMapperDevice;
|
||||
return UnknownDevice;
|
||||
}
|
||||
|
||||
fs::path SysPath() const { return kSysBlock / name; }
|
||||
|
||||
fs::path DevPath() const { return kDevBlock / name; }
|
||||
|
||||
Result<std::string> GetProperty(const std::string& property) const {
|
||||
auto property_file = SysPath() / property;
|
||||
std::string property_value;
|
||||
if (!ReadFileToString(property_file, &property_value)) {
|
||||
return ErrnoError() << "Fail to read";
|
||||
}
|
||||
return Trim(property_value);
|
||||
}
|
||||
|
||||
std::vector<BlockDevice> GetSlaves() const {
|
||||
std::vector<BlockDevice> slaves;
|
||||
std::error_code ec;
|
||||
auto status = WalkDir(SysPath() / "slaves", [&](const auto& entry) {
|
||||
BlockDevice dev(entry);
|
||||
if (fs::is_block_file(dev.DevPath(), ec)) {
|
||||
slaves.push_back(dev);
|
||||
}
|
||||
});
|
||||
if (!status.ok()) {
|
||||
LOG(WARNING) << status.error();
|
||||
}
|
||||
return slaves;
|
||||
}
|
||||
};
|
||||
|
||||
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& 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)) {
|
||||
version = -1;
|
||||
}
|
||||
return std::make_pair(split[0], version);
|
||||
}
|
||||
return std::make_pair(package_id, -1);
|
||||
}
|
||||
|
||||
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) {
|
||||
return Error() << "dm device " << top_device.DevPath()
|
||||
<< " has unexpected number of slaves : " << slaves.size();
|
||||
}
|
||||
std::vector<std::string> backing_files;
|
||||
backing_files.reserve(slaves.size());
|
||||
for (const auto& dev : slaves) {
|
||||
if (dev.GetType() != LoopDevice) {
|
||||
return Error() << dev.DevPath() << " is not a loop device";
|
||||
}
|
||||
auto backing_file = dev.GetProperty("loop/backing_file");
|
||||
if (!backing_file.ok()) {
|
||||
return backing_file.error();
|
||||
}
|
||||
backing_files.push_back(std::move(*backing_file));
|
||||
}
|
||||
// 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 (!is_data_loop_device(backing_files[0])) {
|
||||
std::swap(slaves[0], slaves[1]);
|
||||
std::swap(backing_files[0], backing_files[1]);
|
||||
}
|
||||
}
|
||||
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], apex_hash_tree_dir)) {
|
||||
return Error() << "Hashtree loop device " << slaves[1].DevPath()
|
||||
<< " has unexpected backing file " << backing_files[1];
|
||||
}
|
||||
apex_data->hashtree_loop_name = slaves[1].DevPath();
|
||||
}
|
||||
apex_data->loop_name = slaves[0].DevPath();
|
||||
apex_data->full_path = backing_files[0];
|
||||
return {};
|
||||
}
|
||||
|
||||
// This is not the right place to do this normalization, but proper solution
|
||||
// will require some refactoring first. :(
|
||||
// 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;
|
||||
if (ConsumeSuffix(&full_path, "(deleted)")) {
|
||||
apex_data->deleted = true;
|
||||
auto it = full_path.rbegin();
|
||||
while (it != full_path.rend() && isspace(*it)) {
|
||||
it++;
|
||||
}
|
||||
full_path.remove_suffix(it - full_path.rbegin());
|
||||
} else {
|
||||
apex_data->deleted = false;
|
||||
}
|
||||
apex_data->full_path = full_path;
|
||||
}
|
||||
|
||||
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 backing_file = block.GetProperty("loop/backing_file");
|
||||
if (!backing_file.ok()) {
|
||||
return backing_file.error();
|
||||
}
|
||||
auto result = MountedApexData(block.DevPath(), *backing_file, mount_point,
|
||||
/* device_name= */ "",
|
||||
/* hashtree_loop_name= */ "",
|
||||
/* is_temp_mount */ temp_mount);
|
||||
NormalizeIfDeleted(&result);
|
||||
return result;
|
||||
}
|
||||
case DeviceMapperDevice: {
|
||||
auto name = block.GetProperty("dm/name");
|
||||
if (!name.ok()) {
|
||||
return name.error();
|
||||
}
|
||||
MountedApexData result;
|
||||
result.mount_point = mount_point;
|
||||
result.device_name = *name;
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
case UnknownDevice: {
|
||||
return Errorf("Can't resolve {}", block.DevPath().string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// On startup, APEX database is populated from /proc/mounts.
|
||||
|
||||
// /apex/<package-id> can be mounted from
|
||||
// - /dev/block/loopX : loop device
|
||||
// - /dev/block/dm-X : dm-verity
|
||||
|
||||
// In case of loop device, it is from a non-flattened
|
||||
// APEX file. This original APEX file can be tracked
|
||||
// by /sys/block/loopX/loop/backing_file.
|
||||
|
||||
// In case of dm-verity, it is mapped to a loop device.
|
||||
// This mapped loop device can be traced by
|
||||
// /sys/block/dm-X/slaves/ directory which contains
|
||||
// a symlink to /sys/block/loopY, which leads to
|
||||
// the original APEX file.
|
||||
// Device name can be retrieved from
|
||||
// /sys/block/dm-Y/dm/name.
|
||||
|
||||
// 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(
|
||||
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> active_versions;
|
||||
|
||||
std::ifstream mounts("/proc/mounts");
|
||||
std::string line;
|
||||
std::lock_guard lock(mounted_apexes_mutex_);
|
||||
while (std::getline(mounts, line)) {
|
||||
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(mount_point)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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(mount_point);
|
||||
AddMountedApexLocked(package, false, *mount_data);
|
||||
|
||||
auto active = active_versions[package] < version;
|
||||
if (active) {
|
||||
active_versions[package] = version;
|
||||
SetLatestLocked(package, mount_data->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.";
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEX_DATABASE_H_
|
||||
#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 {
|
||||
|
||||
class MountedApexDatabase {
|
||||
public:
|
||||
// Stores associated low-level data for a mounted APEX. To conserve memory,
|
||||
// the APEX file isn't stored, but must be opened to retrieve specific data.
|
||||
struct MountedApexData {
|
||||
std::string loop_name; // Loop device used (fs path).
|
||||
std::string full_path; // Full path to the apex file.
|
||||
std::string mount_point; // Path this apex is mounted on.
|
||||
std::string device_name; // Name of the dm verity device.
|
||||
// Name of the loop device backing up hashtree or empty string in case
|
||||
// hashtree is embedded inside an APEX.
|
||||
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,
|
||||
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),
|
||||
is_temp_mount(is_temp_mount) {}
|
||||
|
||||
inline bool operator<(const MountedApexData& rhs) const {
|
||||
int compare_val = loop_name.compare(rhs.loop_name);
|
||||
if (compare_val < 0) {
|
||||
return true;
|
||||
} else if (compare_val > 0) {
|
||||
return false;
|
||||
}
|
||||
compare_val = full_path.compare(rhs.full_path);
|
||||
if (compare_val < 0) {
|
||||
return true;
|
||||
} else if (compare_val > 0) {
|
||||
return false;
|
||||
}
|
||||
compare_val = mount_point.compare(rhs.mount_point);
|
||||
if (compare_val < 0) {
|
||||
return true;
|
||||
} else if (compare_val > 0) {
|
||||
return false;
|
||||
}
|
||||
compare_val = device_name.compare(rhs.device_name);
|
||||
if (compare_val < 0) {
|
||||
return true;
|
||||
} else if (compare_val > 0) {
|
||||
return false;
|
||||
}
|
||||
return hashtree_loop_name < rhs.hashtree_loop_name;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... 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 =
|
||||
mounted_apexes_.emplace(package, std::map<MountedApexData, bool>());
|
||||
CHECK(insert_it.second);
|
||||
it = insert_it.first;
|
||||
}
|
||||
|
||||
auto check_it = it->second.emplace(
|
||||
MountedApexData(std::forward<Args>(args)...), latest);
|
||||
CHECK(check_it.second);
|
||||
|
||||
CheckAtMostOneLatest();
|
||||
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,
|
||||
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;
|
||||
}
|
||||
|
||||
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 &&
|
||||
pkg_it->first.is_temp_mount == match_temp_mounts) {
|
||||
pkg_map.erase(pkg_it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void SetLatest(const std::string& package,
|
||||
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());
|
||||
|
||||
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) {
|
||||
pkg_it->second = true;
|
||||
for (auto reset_it = pkg_map.begin(); reset_it != pkg_map.end();
|
||||
++reset_it) {
|
||||
if (reset_it != pkg_it) {
|
||||
reset_it->second = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(FATAL) << "Did not find " << package << " " << full_path;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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) {
|
||||
if (pair.first.is_temp_mount == match_temp_mounts) {
|
||||
handler(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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) {
|
||||
if (pair.first.is_temp_mount == match_temp_mounts) {
|
||||
handler(pkg.first, pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(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
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEX_DATABASE_H_
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "apex_database.h"
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
namespace {
|
||||
|
||||
using MountedApexData = MountedApexDatabase::MountedApexData;
|
||||
|
||||
TEST(MountedApexDataTest, LinearOrder) {
|
||||
constexpr const char* kLoopName[] = {"loop1", "loop2", "loop3"};
|
||||
constexpr const char* kPath[] = {"path1", "path2", "path3"};
|
||||
constexpr const char* kMount[] = {"mount1", "mount2", "mount3"};
|
||||
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);
|
||||
|
||||
auto index_fn = [&](size_t i) {
|
||||
const size_t loop_index = i % arraysize(kLoopName);
|
||||
const size_t loop_rest = i / arraysize(kLoopName);
|
||||
const size_t path_index = loop_rest % arraysize(kPath);
|
||||
const size_t path_rest = loop_rest / arraysize(kPath);
|
||||
const size_t mount_index = path_rest % arraysize(kMount);
|
||||
const size_t mount_rest = path_rest / arraysize(kMount);
|
||||
const size_t dm_index = mount_rest % arraysize(kDm);
|
||||
const size_t dm_rest = mount_rest / arraysize(kHashtreeLoopName);
|
||||
const size_t hashtree_loop_index = dm_rest % arraysize(kHashtreeLoopName);
|
||||
CHECK_EQ(dm_rest / arraysize(kHashtreeLoopName), 0u);
|
||||
return std::make_tuple(loop_index, path_index, mount_index, dm_index,
|
||||
hashtree_loop_index);
|
||||
};
|
||||
|
||||
MountedApexData data[kCount];
|
||||
for (size_t i = 0; i < kCount; ++i) {
|
||||
size_t loop_idx, path_idx, mount_idx, dm_idx, hash_loop_idx;
|
||||
std::tie(loop_idx, path_idx, mount_idx, dm_idx, hash_loop_idx) =
|
||||
index_fn(i);
|
||||
data[i] =
|
||||
MountedApexData(kLoopName[loop_idx], kPath[path_idx], kMount[mount_idx],
|
||||
kDm[dm_idx], kHashtreeLoopName[hash_loop_idx]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < kCount; ++i) {
|
||||
size_t loop_idx_i, path_idx_i, mount_idx_i, dm_idx_i, hash_loop_idx_i;
|
||||
std::tie(loop_idx_i, path_idx_i, mount_idx_i, dm_idx_i, hash_loop_idx_i) =
|
||||
index_fn(i);
|
||||
for (size_t j = i; j < kCount; ++j) {
|
||||
size_t loop_idx_j, path_idx_j, mount_idx_j, dm_idx_j, hash_loop_idx_j;
|
||||
std::tie(loop_idx_j, path_idx_j, mount_idx_j, dm_idx_j, hash_loop_idx_j) =
|
||||
index_fn(j);
|
||||
if (loop_idx_i != loop_idx_j) {
|
||||
EXPECT_EQ(loop_idx_i < loop_idx_j, data[i] < data[j]);
|
||||
continue;
|
||||
}
|
||||
if (path_idx_i != path_idx_j) {
|
||||
EXPECT_EQ(path_idx_i < path_idx_j, data[i] < data[j]);
|
||||
continue;
|
||||
}
|
||||
if (mount_idx_i != mount_idx_j) {
|
||||
EXPECT_EQ(mount_idx_i < mount_idx_j, data[i] < data[j]);
|
||||
continue;
|
||||
}
|
||||
if (dm_idx_i != dm_idx_j) {
|
||||
EXPECT_EQ(dm_idx_i < dm_idx_j, data[i] < data[j]);
|
||||
continue;
|
||||
}
|
||||
EXPECT_EQ(hash_loop_idx_i < hash_loop_idx_j, data[i] < data[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t CountPackages(const MountedApexDatabase& db) {
|
||||
size_t ret = 0;
|
||||
db.ForallMountedApexes([&ret](const std::string& a ATTRIBUTE_UNUSED,
|
||||
const MountedApexData& b ATTRIBUTE_UNUSED,
|
||||
bool c ATTRIBUTE_UNUSED) { ++ret; });
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Contains(const MountedApexDatabase& db, const std::string& package,
|
||||
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) {
|
||||
bool found = false;
|
||||
db.ForallMountedApexes([&](const std::string& p, const MountedApexData& d,
|
||||
bool b ATTRIBUTE_UNUSED) {
|
||||
if (package == p && loop_name == d.loop_name && full_path == d.full_path &&
|
||||
mount_point == d.mount_point && device_name == d.device_name &&
|
||||
hashtree_loop_name == d.hashtree_loop_name) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
bool ContainsPackage(const MountedApexDatabase& db, const std::string& package,
|
||||
const std::string& loop_name, const std::string& full_path,
|
||||
const std::string& dm,
|
||||
const std::string& hashtree_loop_name) {
|
||||
bool found = false;
|
||||
db.ForallMountedApexes(
|
||||
package, [&](const MountedApexData& d, bool b ATTRIBUTE_UNUSED) {
|
||||
if (loop_name == d.loop_name && full_path == d.full_path &&
|
||||
dm == d.device_name && hashtree_loop_name == d.hashtree_loop_name) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
TEST(ApexDatabaseTest, AddRemovedMountedApex) {
|
||||
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, false, kLoopName, kPath, kMountPoint, kDeviceName,
|
||||
kHashtreeLoopName);
|
||||
ASSERT_TRUE(Contains(db, kPackage, kLoopName, kPath, kMountPoint, kDeviceName,
|
||||
kHashtreeLoopName));
|
||||
ASSERT_TRUE(ContainsPackage(db, kPackage, kLoopName, kPath, kDeviceName,
|
||||
kHashtreeLoopName));
|
||||
|
||||
db.RemoveMountedApex(kPackage, kPath);
|
||||
EXPECT_FALSE(Contains(db, kPackage, kLoopName, kPath, kMountPoint,
|
||||
kDeviceName, kHashtreeLoopName));
|
||||
EXPECT_FALSE(ContainsPackage(db, kPackage, kLoopName, kPath, kDeviceName,
|
||||
kHashtreeLoopName));
|
||||
}
|
||||
|
||||
TEST(ApexDatabaseTest, MountMultiple) {
|
||||
constexpr const char* kPackage[] = {"package", "package", "package",
|
||||
"package"};
|
||||
constexpr const char* kLoopName[] = {"loop", "loop2", "loop3", "loop4"};
|
||||
constexpr const char* kPath[] = {"path", "path2", "path", "path4"};
|
||||
constexpr const char* kMountPoint[] = {"mount", "mount2", "mount", "mount4"};
|
||||
constexpr const char* kDeviceName[] = {"dev", "dev2", "dev3", "dev4"};
|
||||
constexpr const char* kHashtreeLoopName[] = {"hash-loop", "hash-loop2",
|
||||
"hash-loop3", "hash-loop4"};
|
||||
MountedApexDatabase db;
|
||||
ASSERT_EQ(CountPackages(db), 0u);
|
||||
|
||||
for (size_t i = 0; i < arraysize(kPackage); ++i) {
|
||||
db.AddMountedApex(kPackage[i], false, kLoopName[i], kPath[i],
|
||||
kMountPoint[i], kDeviceName[i], kHashtreeLoopName[i]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(CountPackages(db), 4u);
|
||||
for (size_t i = 0; i < arraysize(kPackage); ++i) {
|
||||
ASSERT_TRUE(Contains(db, kPackage[i], kLoopName[i], kPath[i],
|
||||
kMountPoint[i], kDeviceName[i], kHashtreeLoopName[i]));
|
||||
ASSERT_TRUE(ContainsPackage(db, kPackage[i], kLoopName[i], kPath[i],
|
||||
kDeviceName[i], kHashtreeLoopName[i]));
|
||||
}
|
||||
|
||||
db.RemoveMountedApex(kPackage[0], kPath[0]);
|
||||
EXPECT_FALSE(Contains(db, kPackage[0], kLoopName[0], kPath[0], kMountPoint[0],
|
||||
kDeviceName[0], kHashtreeLoopName[0]));
|
||||
EXPECT_FALSE(ContainsPackage(db, kPackage[0], kLoopName[0], kPath[0],
|
||||
kDeviceName[0], kHashtreeLoopName[0]));
|
||||
EXPECT_TRUE(Contains(db, kPackage[1], kLoopName[1], kPath[1], kMountPoint[1],
|
||||
kDeviceName[1], kHashtreeLoopName[1]));
|
||||
EXPECT_TRUE(ContainsPackage(db, kPackage[1], kLoopName[1], kPath[1],
|
||||
kDeviceName[1], kHashtreeLoopName[1]));
|
||||
EXPECT_TRUE(Contains(db, kPackage[2], kLoopName[2], kPath[2], kMountPoint[2],
|
||||
kDeviceName[2], kHashtreeLoopName[2]));
|
||||
EXPECT_TRUE(ContainsPackage(db, kPackage[2], kLoopName[2], kPath[2],
|
||||
kDeviceName[2], kHashtreeLoopName[2]));
|
||||
EXPECT_TRUE(Contains(db, kPackage[3], kLoopName[3], kPath[3], kMountPoint[3],
|
||||
kDeviceName[3], kHashtreeLoopName[3]));
|
||||
EXPECT_TRUE(ContainsPackage(db, kPackage[3], kLoopName[3], kPath[3],
|
||||
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]
|
||||
#pragma clang diagnostic ignored "-Wused-but-marked-unused"
|
||||
|
||||
TEST(MountedApexDataTest, NoDuplicateLoopDataLoopDevices) {
|
||||
ASSERT_DEATH(
|
||||
{
|
||||
MountedApexDatabase db;
|
||||
db.AddMountedApex("package", false, "loop", "path", "mount", "dm",
|
||||
"hashtree-loop1");
|
||||
db.AddMountedApex("package2", false, "loop", "path2", "mount2", "dm2",
|
||||
"hashtree-loop2");
|
||||
},
|
||||
"Duplicate loop device: loop");
|
||||
}
|
||||
|
||||
TEST(MountedApexDataTest, NoDuplicateLoopHashtreeLoopDevices) {
|
||||
ASSERT_DEATH(
|
||||
{
|
||||
MountedApexDatabase db;
|
||||
db.AddMountedApex("package", false, "loop1", "path", "mount", "dm",
|
||||
"hashtree-loop");
|
||||
db.AddMountedApex("package2", false, "loop2", "path2", "mount2", "dm2",
|
||||
"hashtree-loop");
|
||||
},
|
||||
"Duplicate loop device: hashtree-loop");
|
||||
}
|
||||
|
||||
TEST(MountedApexDataTest, NoDuplicateLoopHashtreeAndDataLoopDevices) {
|
||||
ASSERT_DEATH(
|
||||
{
|
||||
MountedApexDatabase db;
|
||||
db.AddMountedApex("package", false, "loop", "path", "mount", "dm",
|
||||
"hashtree-loop1");
|
||||
db.AddMountedApex("package2", false, "loop2", "path2", "mount2", "dm2",
|
||||
"loop");
|
||||
},
|
||||
"Duplicate loop device: loop");
|
||||
}
|
||||
|
||||
TEST(MountedApexDataTest, NoDuplicateDm) {
|
||||
ASSERT_DEATH(
|
||||
{
|
||||
MountedApexDatabase db;
|
||||
db.AddMountedApex("package", false, "loop", "path", "mount", "dm",
|
||||
/* hashtree_loop_name= */ "");
|
||||
db.AddMountedApex("package2", false, "loop2", "path2", "mount2", "dm",
|
||||
/* hashtree_loop_name= */ "");
|
||||
},
|
||||
"Duplicate dm device: dm");
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "apex_file.h"
|
||||
|
||||
#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 <fcntl.h>
|
||||
#include <libavb/libavb.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <span>
|
||||
|
||||
#include "apex_constants.h"
|
||||
#include "apexd_utils.h"
|
||||
#include "apexd_verity.h"
|
||||
|
||||
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::unique_fd;
|
||||
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"},
|
||||
{"erofs", 1024, 4, "\xe2\xe1\xf5\xe0"}};
|
||||
|
||||
Result<std::string> RetrieveFsType(borrowed_fd fd, uint32_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) {
|
||||
std::optional<uint32_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 ErrnoError() << "Failed to open package " << path << ": "
|
||||
<< "I/O error";
|
||||
}
|
||||
|
||||
ZipArchiveHandle handle;
|
||||
auto handle_guard =
|
||||
android::base::make_scope_guard([&handle] { CloseArchive(handle); });
|
||||
int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle,
|
||||
/*assume_ownership=*/false);
|
||||
if (ret < 0) {
|
||||
return Error() << "Failed to open package " << path << ": "
|
||||
<< ErrorCodeString(ret);
|
||||
}
|
||||
|
||||
bool is_compressed = true;
|
||||
ret = FindEntry(handle, kCompressedApexFilename, &entry);
|
||||
if (ret < 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
ret = FindEntry(handle, kManifestFilenamePb, &entry);
|
||||
if (ret < 0) {
|
||||
return Error() << "Could not find entry \"" << kManifestFilenamePb
|
||||
<< "\" in package " << path << ": " << ErrorCodeString(ret);
|
||||
}
|
||||
|
||||
uint32_t length = entry.uncompressed_length;
|
||||
manifest_content.resize(length, '\0');
|
||||
ret = ExtractToMemory(handle, &entry,
|
||||
reinterpret_cast<uint8_t*>(&(manifest_content)[0]),
|
||||
length);
|
||||
if (ret != 0) {
|
||||
return Error() << "Failed to extract manifest from package " << path << ": "
|
||||
<< ErrorCodeString(ret);
|
||||
}
|
||||
|
||||
ret = FindEntry(handle, kBundledPublicKeyFilename, &entry);
|
||||
if (ret >= 0) {
|
||||
length = entry.uncompressed_length;
|
||||
pubkey.resize(length, '\0');
|
||||
ret = ExtractToMemory(handle, &entry,
|
||||
reinterpret_cast<uint8_t*>(&(pubkey)[0]), length);
|
||||
if (ret != 0) {
|
||||
return Error() << "Failed to extract public key from package " << path
|
||||
<< ": " << ErrorCodeString(ret);
|
||||
}
|
||||
}
|
||||
|
||||
Result<ApexManifest> manifest = ParseManifest(manifest_content);
|
||||
if (!manifest.ok()) {
|
||||
return manifest.error();
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr int kVbMetaMaxSize = 64 * 1024;
|
||||
|
||||
std::string GetSalt(const AvbHashtreeDescriptor& desc,
|
||||
const uint8_t* trailing_data) {
|
||||
const uint8_t* desc_salt = trailing_data + desc.partition_name_len;
|
||||
|
||||
return BytesToHex(desc_salt, desc.salt_len);
|
||||
}
|
||||
|
||||
std::string GetDigest(const AvbHashtreeDescriptor& desc,
|
||||
const uint8_t* trailing_data) {
|
||||
const uint8_t* desc_digest =
|
||||
trailing_data + desc.partition_name_len + desc.salt_len;
|
||||
|
||||
return BytesToHex(desc_digest, desc.root_digest_len);
|
||||
}
|
||||
|
||||
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
|
||||
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";
|
||||
}
|
||||
|
||||
ret = read(fd, footer_data.data(), AVB_FOOTER_SIZE);
|
||||
if (ret != AVB_FOOTER_SIZE) {
|
||||
return ErrnoError() << "Couldn't read AVB footer";
|
||||
}
|
||||
|
||||
if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_data.data(),
|
||||
footer.get())) {
|
||||
return Error() << "AVB footer verification failed.";
|
||||
}
|
||||
|
||||
LOG(VERBOSE) << "AVB footer verification successful.";
|
||||
return footer;
|
||||
}
|
||||
|
||||
bool CompareKeys(const uint8_t* key, size_t length,
|
||||
const std::string& public_key_content) {
|
||||
return public_key_content.length() == length &&
|
||||
memcmp(&public_key_content[0], key, length) == 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
res = avb_vbmeta_image_verify(data, length, &pk, &pk_len);
|
||||
switch (res) {
|
||||
case AVB_VBMETA_VERIFY_RESULT_OK:
|
||||
break;
|
||||
case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
|
||||
case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
|
||||
case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
|
||||
return Error() << "Error verifying " << apex.GetPath() << ": "
|
||||
<< avb_vbmeta_verify_result_to_string(res);
|
||||
case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
|
||||
return Error() << "Error verifying " << apex.GetPath() << ": "
|
||||
<< "invalid vbmeta header";
|
||||
case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION:
|
||||
return Error() << "Error verifying " << apex.GetPath() << ": "
|
||||
<< "unsupported version";
|
||||
default:
|
||||
return Error() << "Unknown vmbeta_image_verify return value : " << res;
|
||||
}
|
||||
|
||||
return std::span<const uint8_t>(pk, pk_len);
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<uint8_t[]>> VerifyVbMeta(const ApexFile& apex,
|
||||
const unique_fd& fd,
|
||||
const AvbFooter& footer,
|
||||
const std::string& public_key) {
|
||||
if (footer.vbmeta_size > kVbMetaMaxSize) {
|
||||
return Errorf("VbMeta size in footer exceeds kVbMetaMaxSize.");
|
||||
}
|
||||
|
||||
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<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,
|
||||
size_t vbmeta_size) {
|
||||
const AvbDescriptor** descriptors;
|
||||
size_t num_descriptors;
|
||||
|
||||
descriptors =
|
||||
avb_descriptor_get_all(vbmeta_data, vbmeta_size, &num_descriptors);
|
||||
|
||||
// avb_descriptor_get_all() returns an internally allocated array
|
||||
// of pointers and it needs to be avb_free()ed after using it.
|
||||
auto guard = android::base::ScopeGuard(std::bind(avb_free, descriptors));
|
||||
|
||||
for (size_t i = 0; i < num_descriptors; i++) {
|
||||
AvbDescriptor desc;
|
||||
if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) {
|
||||
return Errorf("Couldn't validate AvbDescriptor.");
|
||||
}
|
||||
|
||||
if (desc.tag != AVB_DESCRIPTOR_TAG_HASHTREE) {
|
||||
// Ignore other descriptors
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that hashtree descriptor actually fits into memory.
|
||||
const uint8_t* vbmeta_end = vbmeta_data + vbmeta_size;
|
||||
if ((uint8_t*)descriptors[i] + sizeof(AvbHashtreeDescriptor) > vbmeta_end) {
|
||||
return Errorf("Invalid length for AvbHashtreeDescriptor");
|
||||
}
|
||||
return (const AvbHashtreeDescriptor*)descriptors[i];
|
||||
}
|
||||
|
||||
return Errorf("Couldn't find any AVB hashtree descriptors.");
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<AvbHashtreeDescriptor>> VerifyDescriptor(
|
||||
const AvbHashtreeDescriptor* desc) {
|
||||
auto verified_desc = std::make_unique<AvbHashtreeDescriptor>();
|
||||
|
||||
if (!avb_hashtree_descriptor_validate_and_byteswap(desc,
|
||||
verified_desc.get())) {
|
||||
return Errorf("Couldn't validate AvbDescriptor.");
|
||||
}
|
||||
|
||||
return verified_desc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
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);
|
||||
if (!footer.ok()) {
|
||||
return footer.error();
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<uint8_t[]>> vbmeta_data =
|
||||
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);
|
||||
if (!descriptor.ok()) {
|
||||
return descriptor.error();
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<AvbHashtreeDescriptor>> verified_descriptor =
|
||||
VerifyDescriptor(*descriptor);
|
||||
if (!verified_descriptor.ok()) {
|
||||
return verified_descriptor.error();
|
||||
}
|
||||
verity_data.desc = std::move(*verified_descriptor);
|
||||
|
||||
// This area is now safe to access, because we just verified it
|
||||
const uint8_t* trailing_data =
|
||||
(const uint8_t*)*descriptor + sizeof(AvbHashtreeDescriptor);
|
||||
verity_data.hash_algorithm =
|
||||
reinterpret_cast<const char*>((*descriptor)->hash_algorithm);
|
||||
verity_data.salt = GetSalt(*verity_data.desc, trailing_data);
|
||||
verity_data.root_digest = GetDigest(*verity_data.desc, trailing_data);
|
||||
|
||||
return verity_data;
|
||||
}
|
||||
|
||||
Result<void> ApexFile::Decompress(const std::string& dest_path) const {
|
||||
const std::string& src_path = GetPath();
|
||||
|
||||
LOG(INFO) << "Decompressing" << src_path << " to " << dest_path;
|
||||
|
||||
// We should decompress compressed APEX files only
|
||||
if (!IsCompressed()) {
|
||||
return ErrnoError() << "Cannot decompress an uncompressed APEX";
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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); });
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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); });
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Verification complete. Accept the decompressed file
|
||||
decompressed_guard.Disable();
|
||||
LOG(VERBOSE) << "Decompressed " << src_path << " to " << dest_path;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEX_FILE_H_
|
||||
#define ANDROID_APEXD_APEX_FILE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/result.h>
|
||||
#include <libavb/libavb.h>
|
||||
|
||||
#include "apex_manifest.h"
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
// Data needed to construct a valid VerityTable
|
||||
struct ApexVerityData {
|
||||
std::unique_ptr<AvbHashtreeDescriptor> desc;
|
||||
std::string hash_algorithm;
|
||||
std::string salt;
|
||||
std::string root_digest;
|
||||
};
|
||||
|
||||
// Manages the content of an APEX package and provides utilities to navigate
|
||||
// the content.
|
||||
class ApexFile {
|
||||
public:
|
||||
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_; }
|
||||
const std::optional<uint32_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_; }
|
||||
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,
|
||||
const std::optional<uint32_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),
|
||||
fs_type_(fs_type),
|
||||
is_compressed_(is_compressed) {}
|
||||
|
||||
std::string apex_path_;
|
||||
std::optional<uint32_t> image_offset_;
|
||||
std::optional<size_t> image_size_;
|
||||
::apex::proto::ApexManifest manifest_;
|
||||
std::string apex_pubkey_;
|
||||
std::optional<std::string> fs_type_;
|
||||
bool is_compressed_;
|
||||
};
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEX_FILE_H_
|
|
@ -0,0 +1,515 @@
|
|||
/*
|
||||
* 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 <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 <microdroid/metadata.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "apex_constants.h"
|
||||
#include "apex_file.h"
|
||||
#include "apexd_utils.h"
|
||||
#include "apexd_verity.h"
|
||||
|
||||
using android::base::EndsWith;
|
||||
using android::base::Error;
|
||||
using android::base::GetProperty;
|
||||
using android::base::Result;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
std::string ConsumeApexPackageSuffix(const std::string& path) {
|
||||
std::string_view path_view(path);
|
||||
android::base::ConsumeSuffix(&path_view, kApexPackageSuffix);
|
||||
android::base::ConsumeSuffix(&path_view, kCompressedApexPackageSuffix);
|
||||
return std::string(path_view);
|
||||
}
|
||||
|
||||
std::string GetApexSelectFilenameFromProp(
|
||||
const std::vector<std::string>& prefixes, const std::string& apex_name) {
|
||||
for (const std::string& prefix : prefixes) {
|
||||
const std::string& filename = GetProperty(prefix + apex_name, "");
|
||||
if (filename != "") {
|
||||
return ConsumeApexPackageSuffix(filename);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Check if this APEX name is treated as a multi-install APEX.
|
||||
//
|
||||
// Note: apexd is a oneshot service which runs at boot, but can be restarted
|
||||
// when needed (such as staging an APEX update). If a multi-install select
|
||||
// property changes between boot and when apexd restarts, the LOG messages
|
||||
// below will report the version that will be activated on next reboot,
|
||||
// which may differ from the currently-active version.
|
||||
std::string select_filename = GetApexSelectFilenameFromProp(
|
||||
multi_install_select_prop_prefixes_, name);
|
||||
if (!select_filename.empty()) {
|
||||
std::string path;
|
||||
if (!android::base::Realpath(apex_file->GetPath(), &path)) {
|
||||
LOG(ERROR) << "Unable to resolve realpath of APEX with path "
|
||||
<< apex_file->GetPath();
|
||||
continue;
|
||||
}
|
||||
if (enforce_multi_install_partition_ &&
|
||||
!android::base::StartsWith(path, "/vendor/apex/")) {
|
||||
LOG(ERROR) << "Multi-install APEX " << path
|
||||
<< " can only be preinstalled on /vendor/apex/.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& keys = multi_install_public_keys_[name];
|
||||
keys.insert(apex_file->GetBundledPublicKey());
|
||||
if (keys.size() > 1) {
|
||||
LOG(ERROR) << "Multi-install APEXes for " << name
|
||||
<< " have different public keys.";
|
||||
// If any versions of a multi-installed APEX differ in public key,
|
||||
// then no version should be installed.
|
||||
if (auto it = pre_installed_store_.find(name);
|
||||
it != pre_installed_store_.end()) {
|
||||
pre_installed_store_.erase(it);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ConsumeApexPackageSuffix(android::base::Basename(path)) ==
|
||||
select_filename) {
|
||||
LOG(INFO) << "Found APEX at path " << path << " for multi-install APEX "
|
||||
<< name;
|
||||
// Add the APEX file to the store if its filename matches the property.
|
||||
pre_installed_store_.emplace(name, std::move(*apex_file));
|
||||
} else {
|
||||
LOG(INFO) << "Skipping APEX at path " << path
|
||||
<< " because it does not match expected multi-install"
|
||||
<< " APEX property for " << name;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
multi_install_public_keys_.clear();
|
||||
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 {};
|
||||
}
|
||||
|
||||
Result<int> ApexFileRepository::AddBlockApex(
|
||||
const std::string& metadata_partition) {
|
||||
CHECK(!block_disk_path_.has_value())
|
||||
<< "AddBlockApex() can't be called twice.";
|
||||
|
||||
auto metadata_ready = WaitForFile(metadata_partition, kBlockApexWaitTime);
|
||||
if (!metadata_ready.ok()) {
|
||||
LOG(ERROR) << "Error waiting for metadata_partition : "
|
||||
<< metadata_ready.error();
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO(b/185069443) consider moving the logic to find disk_path from
|
||||
// metadata_partition to its own library
|
||||
LOG(INFO) << "Scanning " << metadata_partition << " for host apexes";
|
||||
if (access(metadata_partition.c_str(), F_OK) != 0 && errno == ENOENT) {
|
||||
LOG(WARNING) << metadata_partition << " does not exist. Skipping";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string metadata_realpath;
|
||||
if (!android::base::Realpath(metadata_partition, &metadata_realpath)) {
|
||||
LOG(WARNING) << "Can't get realpath of " << metadata_partition
|
||||
<< ". Skipping";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string_view metadata_path_view(metadata_realpath);
|
||||
if (!android::base::ConsumeSuffix(&metadata_path_view, "1")) {
|
||||
LOG(WARNING) << metadata_realpath << " is not a first partition. Skipping";
|
||||
return {};
|
||||
}
|
||||
|
||||
block_disk_path_ = std::string(metadata_path_view);
|
||||
|
||||
// Read the payload metadata.
|
||||
// "metadata" can be overridden by microdroid_manager. To ensure that
|
||||
// "microdroid" is started with the same/unmodified set of host APEXes,
|
||||
// microdroid stores APEXes' pubkeys in its encrypted instance disk. Next
|
||||
// time, microdroid checks if there's pubkeys in the instance disk and use
|
||||
// them to activate APEXes. Microdroid_manager passes pubkeys in instance.img
|
||||
// via the following file.
|
||||
if (auto exists = PathExists("/apex/vm-payload-metadata");
|
||||
exists.ok() && *exists) {
|
||||
metadata_realpath = "/apex/vm-payload-metadata";
|
||||
LOG(INFO) << "Overriding metadata to " << metadata_realpath;
|
||||
}
|
||||
auto metadata = android::microdroid::ReadMetadata(metadata_realpath);
|
||||
if (!metadata.ok()) {
|
||||
LOG(WARNING) << "Failed to load metadata from " << metadata_realpath
|
||||
<< ". Skipping: " << metadata.error();
|
||||
return {};
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
|
||||
// subsequent partitions are APEX archives.
|
||||
static constexpr const int kFirstApexPartition = 2;
|
||||
for (int i = 0; i < metadata->apexes_size(); i++) {
|
||||
const auto& apex_config = metadata->apexes(i);
|
||||
|
||||
const std::string apex_path =
|
||||
*block_disk_path_ + std::to_string(i + kFirstApexPartition);
|
||||
|
||||
auto apex_ready = WaitForFile(apex_path, kBlockApexWaitTime);
|
||||
if (!apex_ready.ok()) {
|
||||
return Error() << "Error waiting for apex file : " << apex_ready.error();
|
||||
}
|
||||
|
||||
auto apex_file = ApexFile::Open(apex_path);
|
||||
if (!apex_file.ok()) {
|
||||
return Error() << "Failed to open " << apex_path << " : "
|
||||
<< apex_file.error();
|
||||
}
|
||||
|
||||
// When metadata specifies the public key of the apex, it should match the
|
||||
// bundled key. Otherwise we accept it.
|
||||
if (apex_config.public_key() != "" &&
|
||||
apex_config.public_key() != apex_file->GetBundledPublicKey()) {
|
||||
return Error() << "public key doesn't match: " << apex_path;
|
||||
}
|
||||
|
||||
const std::string& name = apex_file->GetManifest().name();
|
||||
|
||||
BlockApexOverride overrides;
|
||||
|
||||
// A block device doesn't have an inherent timestamp, so it is carried in
|
||||
// the metadata.
|
||||
if (int64_t last_update_seconds = apex_config.last_update_seconds();
|
||||
last_update_seconds != 0) {
|
||||
overrides.last_update_seconds = last_update_seconds;
|
||||
}
|
||||
|
||||
// When metadata specifies the root digest of the apex, it should be used
|
||||
// when activating the apex. So we need to keep it.
|
||||
if (auto root_digest = apex_config.root_digest(); root_digest != "") {
|
||||
overrides.block_apex_root_digest =
|
||||
BytesToHex(reinterpret_cast<const uint8_t*>(root_digest.data()),
|
||||
root_digest.size());
|
||||
}
|
||||
|
||||
if (overrides.last_update_seconds.has_value() ||
|
||||
overrides.block_apex_root_digest.has_value()) {
|
||||
block_apex_overrides_.emplace(apex_path, std::move(overrides));
|
||||
}
|
||||
|
||||
// Depending on whether the APEX was a factory version in the host or not,
|
||||
// put it to different stores.
|
||||
auto& store = apex_config.is_factory() ? pre_installed_store_ : data_store_;
|
||||
// We want "uniqueness" in each store.
|
||||
if (auto it = store.find(name); it != store.end()) {
|
||||
return Error() << "duplicate of " << name << " found in "
|
||||
<< it->second.GetPath();
|
||||
}
|
||||
store.emplace(name, std::move(*apex_file));
|
||||
|
||||
ret++;
|
||||
}
|
||||
return {ret};
|
||||
}
|
||||
|
||||
// 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 preinstalled apex";
|
||||
// Ignore data apex without corresponding pre-installed apex
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string select_filename = GetApexSelectFilenameFromProp(
|
||||
multi_install_select_prop_prefixes_, name);
|
||||
if (!select_filename.empty()) {
|
||||
LOG(WARNING) << "APEX " << name << " is a multi-installed APEX."
|
||||
<< " Any updated version in /data will always overwrite"
|
||||
<< " the multi-installed preinstalled version, if possible.";
|
||||
}
|
||||
|
||||
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()) {
|
||||
// Special casing for APEXes backed by block devices, i.e. APEXes in VM.
|
||||
// Inside a VM, we fall back to find the key from data_store_. This is
|
||||
// because an APEX is put to either pre_installed_store_ or data_store,
|
||||
// depending on whether it was a factory APEX or not in the host.
|
||||
it = data_store_.find(name);
|
||||
if (it != data_store_.end() && IsBlockApex(it->second)) {
|
||||
return it->second.GetBundledPublicKey();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
std::optional<std::string> ApexFileRepository::GetBlockApexRootDigest(
|
||||
const std::string& path) const {
|
||||
auto it = block_apex_overrides_.find(path);
|
||||
if (it == block_apex_overrides_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second.block_apex_root_digest;
|
||||
}
|
||||
|
||||
std::optional<int64_t> ApexFileRepository::GetBlockApexLastUpdateSeconds(
|
||||
const std::string& path) const {
|
||||
auto it = block_apex_overrides_.find(path);
|
||||
if (it == block_apex_overrides_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second.last_update_seconds;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool ApexFileRepository::IsBlockApex(const ApexFile& apex) const {
|
||||
return block_disk_path_.has_value() &&
|
||||
apex.GetPath().starts_with(*block_disk_path_);
|
||||
}
|
||||
|
||||
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
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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 <android-base/result.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "apex_constants.h"
|
||||
#include "apex_file.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-tors and d-tor are exposed for testing.
|
||||
explicit ApexFileRepository(
|
||||
const std::string& decompression_dir = kApexDecompressedDir)
|
||||
: decompression_dir_(decompression_dir){};
|
||||
explicit ApexFileRepository(
|
||||
bool enforce_multi_install_partition,
|
||||
const std::vector<std::string>& multi_install_select_prop_prefixes)
|
||||
: multi_install_select_prop_prefixes_(multi_install_select_prop_prefixes),
|
||||
enforce_multi_install_partition_(enforce_multi_install_partition){};
|
||||
|
||||
~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 host-provided apex files via
|
||||
// |metadata_partition|. Host can provide its apexes to a VM instance via the
|
||||
// virtual disk image which has partitions: (see
|
||||
// /packages/modules/Virtualization/microdroid for the details)
|
||||
// - metadata partition(/dev/block/vd*1) should be accessed by
|
||||
// setting the system property apexd.payload_metadata.prop. On microdroid,
|
||||
// this is /dev/block/by-name/payload-metadata.
|
||||
// - each subsequence partition(/dev/block/vd*{2,3,..}) represents an APEX
|
||||
// archive.
|
||||
// It will fail if there is more than one apex with the same name in
|
||||
// pre-installed and block apexes. 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.
|
||||
// This will return the number of block apexes that were added.
|
||||
android::base::Result<int> AddBlockApex(
|
||||
const std::string& metadata_partition);
|
||||
|
||||
// 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;
|
||||
|
||||
// Returns root digest of an apex with the given |path| for block apexes.
|
||||
std::optional<std::string> GetBlockApexRootDigest(
|
||||
const std::string& path) const;
|
||||
|
||||
// Returns timestamp to be used for the block apex of the given |path|.
|
||||
std::optional<int64_t> GetBlockApexLastUpdateSeconds(
|
||||
const std::string& path) 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;
|
||||
|
||||
// Checks if given |apex| is loaded from block device.
|
||||
bool IsBlockApex(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();
|
||||
block_apex_overrides_.clear();
|
||||
decompression_dir_ = decompression_dir;
|
||||
block_disk_path_.reset();
|
||||
}
|
||||
|
||||
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_;
|
||||
|
||||
// Multi-installed APEX name -> all encountered public keys for this APEX.
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
multi_install_public_keys_;
|
||||
|
||||
// Prefixes used when looking for multi-installed APEX sysprops.
|
||||
// Order matters: the first non-empty prop value is returned.
|
||||
std::vector<std::string> multi_install_select_prop_prefixes_ = {
|
||||
// Check persist props first, to allow users to override bootconfig.
|
||||
kMultiApexSelectPersistPrefix,
|
||||
kMultiApexSelectBootconfigPrefix,
|
||||
};
|
||||
|
||||
// Allows multi-install APEXes outside of expected partitions.
|
||||
// Only set false in tests.
|
||||
bool enforce_multi_install_partition_ = true;
|
||||
|
||||
// Decompression directory which will be used to determine if apex is
|
||||
// decompressed or not
|
||||
std::string decompression_dir_;
|
||||
|
||||
// Disk path where block apexes are read from. AddBlockApex() sets this.
|
||||
std::optional<std::string> block_disk_path_;
|
||||
|
||||
// Information from the metadata for block apexes, overriding the file data.
|
||||
struct BlockApexOverride {
|
||||
// Root digest for the APEX. When specified in block apex config, it
|
||||
// should be used/checked when activating the apex to avoid
|
||||
// TOCTOU(time-of-check to time-of-use).
|
||||
std::optional<std::string> block_apex_root_digest;
|
||||
// The last update time of the APEX.
|
||||
std::optional<int64_t> last_update_seconds;
|
||||
};
|
||||
|
||||
// Use "path" as key instead of APEX name because there can be multiple
|
||||
// versions of sharedlibs APEXes.
|
||||
std::unordered_map<std::string, BlockApexOverride> block_apex_overrides_;
|
||||
};
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,822 @@
|
|||
/*
|
||||
* 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 "apex_file_repository.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <errno.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <microdroid/metadata.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "apex_file.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, InitializeMultiInstalledSuccess) {
|
||||
// Prepare test data.
|
||||
TemporaryDir td;
|
||||
std::string apex_file = GetTestFile("apex.apexd_test.apex");
|
||||
fs::copy(apex_file, StringPrintf("%s/version_a.apex", td.path));
|
||||
fs::copy(apex_file, StringPrintf("%s/version_b.apex", td.path));
|
||||
std::string apex_name = ApexFile::Open(apex_file)->GetManifest().name();
|
||||
|
||||
std::string persist_prefix = "debug.apexd.test.persistprefix.";
|
||||
std::string bootconfig_prefix = "debug.apexd.test.bootconfigprefix.";
|
||||
ApexFileRepository instance(/*enforce_multi_install_partition=*/false,
|
||||
/*multi_install_select_prop_prefixes=*/{
|
||||
persist_prefix, bootconfig_prefix});
|
||||
|
||||
auto test_fn = [&](const std::string& selected_filename) {
|
||||
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
|
||||
auto ret = instance.GetPreinstalledPath(apex_name);
|
||||
ASSERT_TRUE(IsOk(ret));
|
||||
ASSERT_EQ(StringPrintf("%s/%s", td.path, selected_filename.c_str()), *ret);
|
||||
instance.Reset();
|
||||
};
|
||||
|
||||
// Start with version_a in bootconfig.
|
||||
android::base::SetProperty(bootconfig_prefix + apex_name, "version_a.apex");
|
||||
test_fn("version_a.apex");
|
||||
// Developer chooses version_b with persist prop.
|
||||
android::base::SetProperty(persist_prefix + apex_name, "version_b.apex");
|
||||
test_fn("version_b.apex");
|
||||
// Developer goes back to version_a with persist prop.
|
||||
android::base::SetProperty(persist_prefix + apex_name, "version_a.apex");
|
||||
test_fn("version_a.apex");
|
||||
|
||||
android::base::SetProperty(persist_prefix + apex_name, "");
|
||||
android::base::SetProperty(bootconfig_prefix + apex_name, "");
|
||||
}
|
||||
|
||||
TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForDifferingKeys) {
|
||||
// Prepare test data.
|
||||
TemporaryDir td;
|
||||
fs::copy(GetTestFile("apex.apexd_test.apex"),
|
||||
StringPrintf("%s/version_a.apex", td.path));
|
||||
fs::copy(GetTestFile("apex.apexd_test_different_key.apex"),
|
||||
StringPrintf("%s/version_b.apex", td.path));
|
||||
std::string apex_name =
|
||||
ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
|
||||
std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
|
||||
std::string prop = prop_prefix + apex_name;
|
||||
android::base::SetProperty(prop, "version_a.apex");
|
||||
|
||||
ApexFileRepository instance(
|
||||
/*enforce_multi_install_partition=*/false,
|
||||
/*multi_install_select_prop_prefixes=*/{prop_prefix});
|
||||
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
|
||||
// Neither version should be have been installed.
|
||||
ASSERT_FALSE(IsOk(instance.GetPreinstalledPath(apex_name)));
|
||||
|
||||
android::base::SetProperty(prop, "");
|
||||
}
|
||||
|
||||
TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForInvalidPartition) {
|
||||
// Prepare test data.
|
||||
TemporaryDir td;
|
||||
// Note: These test files are on /data, which is not a valid partition for
|
||||
// multi-installed APEXes.
|
||||
fs::copy(GetTestFile("apex.apexd_test.apex"),
|
||||
StringPrintf("%s/version_a.apex", td.path));
|
||||
fs::copy(GetTestFile("apex.apexd_test.apex"),
|
||||
StringPrintf("%s/version_b.apex", td.path));
|
||||
std::string apex_name =
|
||||
ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
|
||||
std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
|
||||
std::string prop = prop_prefix + apex_name;
|
||||
android::base::SetProperty(prop, "version_a.apex");
|
||||
|
||||
ApexFileRepository instance(
|
||||
/*enforce_multi_install_partition=*/true,
|
||||
/*multi_install_select_prop_prefixes=*/{prop_prefix});
|
||||
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
|
||||
// Neither version should be have been installed.
|
||||
ASSERT_FALSE(IsOk(instance.GetPreinstalledPath(apex_name)));
|
||||
|
||||
android::base::SetProperty(prop, "");
|
||||
}
|
||||
|
||||
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");
|
||||
},
|
||||
"");
|
||||
}
|
||||
|
||||
struct ApexFileRepositoryTestAddBlockApex : public ::testing::Test {
|
||||
TemporaryDir test_dir;
|
||||
|
||||
struct PayloadMetadata {
|
||||
android::microdroid::Metadata metadata;
|
||||
std::string path;
|
||||
PayloadMetadata(const std::string& path) : path(path) {}
|
||||
PayloadMetadata& apex(const std::string& name,
|
||||
const std::string& public_key = "",
|
||||
const std::string& root_digest = "",
|
||||
int64_t last_update_seconds = 0,
|
||||
bool is_factory = true) {
|
||||
auto apex = metadata.add_apexes();
|
||||
apex->set_name(name);
|
||||
apex->set_public_key(public_key);
|
||||
apex->set_root_digest(root_digest);
|
||||
apex->set_last_update_seconds(last_update_seconds);
|
||||
apex->set_is_factory(is_factory);
|
||||
return *this;
|
||||
}
|
||||
~PayloadMetadata() {
|
||||
metadata.set_version(1);
|
||||
std::ofstream out(path);
|
||||
android::microdroid::WriteMetadata(metadata, out);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex,
|
||||
ScansPayloadDisksAndAddApexFilesToPreInstalled) {
|
||||
// prepare payload disk
|
||||
// <test-dir>/vdc1 : metadata
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
// /vdc3 : apex.apexd_test_different_app.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
const std::string apex_bar_path = test_dir.path + "/vdc3"s;
|
||||
|
||||
PayloadMetadata(metadata_partition_path)
|
||||
.apex(test_apex_foo)
|
||||
.apex(test_apex_bar);
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
|
||||
|
||||
// call ApexFileRepository::AddBlockApex()
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_RESULT_OK(status);
|
||||
|
||||
auto apex_foo = ApexFile::Open(apex_foo_path);
|
||||
ASSERT_RESULT_OK(apex_foo);
|
||||
// block apexes can be identified with IsBlockApex
|
||||
ASSERT_TRUE(instance.IsBlockApex(*apex_foo));
|
||||
|
||||
// "block" apexes are treated as "pre-installed"
|
||||
auto ret_foo = instance.GetPreInstalledApex("com.android.apex.test_package");
|
||||
ASSERT_THAT(ret_foo, ApexFileEq(ByRef(*apex_foo)));
|
||||
|
||||
auto apex_bar = ApexFile::Open(apex_bar_path);
|
||||
ASSERT_RESULT_OK(apex_bar);
|
||||
auto ret_bar =
|
||||
instance.GetPreInstalledApex("com.android.apex.test_package_2");
|
||||
ASSERT_THAT(ret_bar, ApexFileEq(ByRef(*apex_bar)));
|
||||
}
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex,
|
||||
ScansOnlySpecifiedInMetadataPartition) {
|
||||
// prepare payload disk
|
||||
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
// /vdc3 : apex.apexd_test_different_app.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
const std::string apex_bar_path = test_dir.path + "/vdc3"s;
|
||||
|
||||
// metadata lists only "foo"
|
||||
PayloadMetadata(metadata_partition_path).apex(test_apex_foo);
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
|
||||
|
||||
// call ApexFileRepository::AddBlockApex()
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_RESULT_OK(status);
|
||||
|
||||
// foo is added, but bar is not
|
||||
auto ret_foo = instance.GetPreinstalledPath("com.android.apex.test_package");
|
||||
ASSERT_TRUE(IsOk(ret_foo));
|
||||
ASSERT_EQ(apex_foo_path, *ret_foo);
|
||||
auto ret_bar =
|
||||
instance.GetPreinstalledPath("com.android.apex.test_package_2");
|
||||
ASSERT_FALSE(IsOk(ret_bar));
|
||||
}
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex, FailsWhenTheresDuplicateNames) {
|
||||
// prepare payload disk
|
||||
// <test-dir>/vdc1 : metadata with v1 and v2 of apex.apexd_test
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
// /vdc3 : apex.apexd_test_v2.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
const auto& test_apex_bar = GetTestFile("apex.apexd_test_v2.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
const std::string apex_bar_path = test_dir.path + "/vdc3"s;
|
||||
|
||||
PayloadMetadata(metadata_partition_path)
|
||||
.apex(test_apex_foo)
|
||||
.apex(test_apex_bar);
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
|
||||
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_FALSE(IsOk(status));
|
||||
}
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexRootDigest) {
|
||||
// prepare payload disk with root digest
|
||||
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
|
||||
// root digest is stored as bytes in metadata and as hexadecimal in
|
||||
// ApexFileRepository
|
||||
const std::string root_digest = "root_digest";
|
||||
const std::string hex_root_digest = BytesToHex(
|
||||
reinterpret_cast<const uint8_t*>(root_digest.data()), root_digest.size());
|
||||
|
||||
// metadata lists "foo"
|
||||
PayloadMetadata(metadata_partition_path)
|
||||
.apex(test_apex_foo, /*public_key=*/"", root_digest);
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
|
||||
// call ApexFileRepository::AddBlockApex()
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_TRUE(IsOk(status));
|
||||
|
||||
ASSERT_EQ(hex_root_digest, instance.GetBlockApexRootDigest(apex_foo_path));
|
||||
}
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexLastUpdateSeconds) {
|
||||
// prepare payload disk with last update time
|
||||
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
|
||||
const int64_t last_update_seconds = 123456789;
|
||||
|
||||
// metadata lists "foo"
|
||||
PayloadMetadata(metadata_partition_path)
|
||||
.apex(test_apex_foo, /*public_key=*/"", /*root_digest=*/"",
|
||||
last_update_seconds);
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
|
||||
// call ApexFileRepository::AddBlockApex()
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_TRUE(IsOk(status));
|
||||
|
||||
ASSERT_EQ(last_update_seconds,
|
||||
instance.GetBlockApexLastUpdateSeconds(apex_foo_path));
|
||||
}
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex, VerifyPublicKeyWhenAddingBlockApex) {
|
||||
// prepare payload disk
|
||||
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
|
||||
// metadata lists "foo"
|
||||
PayloadMetadata(metadata_partition_path)
|
||||
.apex(test_apex_foo, /*public_key=*/"wrong public key");
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
|
||||
// call ApexFileRepository::AddBlockApex()
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_FALSE(IsOk(status));
|
||||
}
|
||||
|
||||
TEST_F(ApexFileRepositoryTestAddBlockApex, RespectIsFactoryBitFromMetadata) {
|
||||
// prepare payload disk
|
||||
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
|
||||
// /vdc2 : apex.apexd_test.apex
|
||||
|
||||
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
|
||||
|
||||
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
|
||||
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
|
||||
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
|
||||
|
||||
for (const bool is_factory : {true, false}) {
|
||||
// metadata lists "foo"
|
||||
PayloadMetadata(metadata_partition_path)
|
||||
.apex(test_apex_foo, /*public_key=*/"", /*root_digest=*/"",
|
||||
/*last_update_seconds=*/0, is_factory);
|
||||
|
||||
// call ApexFileRepository::AddBlockApex()
|
||||
ApexFileRepository instance;
|
||||
auto status = instance.AddBlockApex(metadata_partition_path);
|
||||
ASSERT_TRUE(IsOk(status))
|
||||
<< "failed to add block apex with is_factory=" << is_factory;
|
||||
ASSERT_EQ(is_factory,
|
||||
instance.HasPreInstalledVersion("com.android.apex.test_package"));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#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 "apexd_test_utils.h"
|
||||
#include "apexd_utils.h"
|
||||
|
||||
using android::base::GetExecutableDirectory;
|
||||
using android::base::Result;
|
||||
|
||||
static const std::string kTestDataDir = GetExecutableDirectory() + "/";
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
namespace {
|
||||
|
||||
struct ApexFileTestParam {
|
||||
const char* type;
|
||||
const char* prefix;
|
||||
};
|
||||
|
||||
constexpr const ApexFileTestParam kParameters[] = {
|
||||
{"ext4", "apex.apexd_test"},
|
||||
{"f2fs", "apex.apexd_test_f2fs"},
|
||||
{"erofs", "apex.apexd_test_erofs"}};
|
||||
|
||||
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());
|
||||
|
||||
uint32_t zip_image_offset;
|
||||
size_t zip_image_size;
|
||||
{
|
||||
ZipArchiveHandle 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); });
|
||||
|
||||
ZipEntry entry;
|
||||
rc = FindEntry(handle, "apex_payload.img", &entry);
|
||||
ASSERT_EQ(0, rc);
|
||||
|
||||
zip_image_offset = entry.offset;
|
||||
EXPECT_EQ(zip_image_offset % 4096, 0U);
|
||||
zip_image_size = entry.uncompressed_length;
|
||||
EXPECT_EQ(zip_image_size, entry.compressed_length);
|
||||
}
|
||||
|
||||
EXPECT_EQ(zip_image_offset, apex_file->GetImageOffset().value());
|
||||
EXPECT_EQ(zip_image_size, apex_file->GetImageSize().value());
|
||||
}
|
||||
|
||||
TEST_P(ApexFileTest, OpenBlockApex) {
|
||||
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
|
||||
Result<ApexFile> apex_file = ApexFile::Open(file_path);
|
||||
ASSERT_RESULT_OK(apex_file);
|
||||
|
||||
TemporaryFile temp_file;
|
||||
auto loop_device = WriteBlockApex(file_path, temp_file.path);
|
||||
|
||||
Result<ApexFile> apex_file_sized = ApexFile::Open(temp_file.path);
|
||||
ASSERT_RESULT_OK(apex_file_sized);
|
||||
|
||||
EXPECT_EQ(apex_file->GetImageOffset(), apex_file_sized->GetImageOffset());
|
||||
EXPECT_EQ(apex_file->GetImageSize(), apex_file_sized->GetImageSize());
|
||||
}
|
||||
|
||||
TEST(ApexFileTest, GetOffsetMissingFile) {
|
||||
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_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_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 =
|
||||
apex_file->VerifyApexVerity(apex_file->GetBundledPublicKey());
|
||||
ASSERT_RESULT_OK(verity_or);
|
||||
|
||||
const ApexVerityData& data = *verity_or;
|
||||
EXPECT_NE(nullptr, data.desc.get());
|
||||
EXPECT_EQ(std::string("368a22e64858647bc45498e92f749f85482ac468"
|
||||
"50ca7ec8071f49dfa47a243c"),
|
||||
data.salt);
|
||||
|
||||
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);
|
||||
|
||||
EXPECT_EQ(std::string(root_digest), data.root_digest);
|
||||
}
|
||||
|
||||
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 = apex_file->VerifyApexVerity("wrong-key");
|
||||
ASSERT_FALSE(verity_or.ok());
|
||||
}
|
||||
|
||||
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 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(key_content, apex_file->GetBundledPublicKey());
|
||||
}
|
||||
|
||||
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("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, DISABLED_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
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "apex_manifest.h"
|
||||
#include <android-base/file.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using android::base::Error;
|
||||
using android::base::Result;
|
||||
using ::apex::proto::ApexManifest;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
Result<ApexManifest> ParseManifest(const std::string& content) {
|
||||
ApexManifest apex_manifest;
|
||||
|
||||
if (!apex_manifest.ParseFromString(content)) {
|
||||
return Error() << "Can't parse APEX manifest.";
|
||||
}
|
||||
|
||||
// Verifying required fields.
|
||||
// name
|
||||
if (apex_manifest.name().empty()) {
|
||||
return Error() << "Missing required field \"name\" from APEX manifest.";
|
||||
}
|
||||
|
||||
// version
|
||||
if (apex_manifest.version() == 0) {
|
||||
return Error() << "Missing required field \"version\" from APEX manifest.";
|
||||
}
|
||||
return apex_manifest;
|
||||
}
|
||||
|
||||
std::string GetPackageId(const ApexManifest& apex_manifest) {
|
||||
return apex_manifest.name() + "@" + std::to_string(apex_manifest.version());
|
||||
}
|
||||
|
||||
Result<ApexManifest> ReadManifest(const std::string& path) {
|
||||
std::string content;
|
||||
if (!android::base::ReadFileToString(path, &content)) {
|
||||
return Error() << "Failed to read manifest file: " << path;
|
||||
}
|
||||
return ParseManifest(content);
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEX_MANIFEST_H_
|
||||
#define ANDROID_APEXD_APEX_MANIFEST_H_
|
||||
|
||||
#include <android-base/result.h>
|
||||
|
||||
#include "apex_manifest.pb.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
// Parses and validates APEX manifest.
|
||||
android::base::Result<::apex::proto::ApexManifest> ParseManifest(
|
||||
const std::string& content);
|
||||
// Returns package id of an ApexManifest
|
||||
std::string GetPackageId(const ::apex::proto::ApexManifest& apex_manifest);
|
||||
// Reads and parses APEX manifest from the file on disk.
|
||||
android::base::Result<::apex::proto::ApexManifest> ReadManifest(
|
||||
const std::string& path);
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEX_MANIFEST_H_
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "apex_manifest.h"
|
||||
|
||||
using ::apex::proto::ApexManifest;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ToString(const ApexManifest& manifest) {
|
||||
std::string out;
|
||||
manifest.SerializeToString(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ApexManifestTest, SimpleTest) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
manifest.set_version(1);
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_RESULT_OK(apex_manifest);
|
||||
EXPECT_EQ("com.android.example.apex", std::string(apex_manifest->name()));
|
||||
EXPECT_EQ(1u, apex_manifest->version());
|
||||
EXPECT_FALSE(apex_manifest->nocode());
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, NameMissing) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_version(1);
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_FALSE(apex_manifest.ok());
|
||||
EXPECT_EQ(apex_manifest.error().message(),
|
||||
std::string("Missing required field \"name\" from APEX manifest."))
|
||||
<< apex_manifest.error();
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, VersionMissing) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_FALSE(apex_manifest.ok());
|
||||
EXPECT_EQ(
|
||||
apex_manifest.error().message(),
|
||||
std::string("Missing required field \"version\" from APEX manifest."))
|
||||
<< apex_manifest.error();
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, NoPreInstallHook) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
manifest.set_version(1);
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_RESULT_OK(apex_manifest);
|
||||
EXPECT_EQ("", std::string(apex_manifest->preinstallhook()));
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, PreInstallHook) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
manifest.set_version(1);
|
||||
manifest.set_preinstallhook("bin/preInstallHook");
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_RESULT_OK(apex_manifest);
|
||||
EXPECT_EQ("bin/preInstallHook", std::string(apex_manifest->preinstallhook()));
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, NoPostInstallHook) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
manifest.set_version(1);
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_RESULT_OK(apex_manifest);
|
||||
EXPECT_EQ("", std::string(apex_manifest->postinstallhook()));
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, PostInstallHook) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
manifest.set_version(1);
|
||||
manifest.set_postinstallhook("bin/postInstallHook");
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_RESULT_OK(apex_manifest);
|
||||
EXPECT_EQ("bin/postInstallHook",
|
||||
std::string(apex_manifest->postinstallhook()));
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, UnparsableManifest) {
|
||||
auto apex_manifest = ParseManifest("This is an invalid pony");
|
||||
ASSERT_FALSE(apex_manifest.ok());
|
||||
EXPECT_EQ(apex_manifest.error().message(),
|
||||
std::string("Can't parse APEX manifest."))
|
||||
<< apex_manifest.error();
|
||||
}
|
||||
|
||||
TEST(ApexManifestTest, NoCode) {
|
||||
ApexManifest manifest;
|
||||
manifest.set_name("com.android.example.apex");
|
||||
manifest.set_version(1);
|
||||
manifest.set_nocode(true);
|
||||
auto apex_manifest = ParseManifest(ToString(manifest));
|
||||
ASSERT_RESULT_OK(apex_manifest);
|
||||
EXPECT_TRUE(apex_manifest->nocode());
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "apex_shim.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "apex_constants.h"
|
||||
#include "apex_file.h"
|
||||
#include "string_log.h"
|
||||
|
||||
using android::base::ErrnoError;
|
||||
using android::base::Error;
|
||||
using android::base::Result;
|
||||
using ::apex::proto::ApexManifest;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
namespace shim {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr const char* kApexCtsShimPackage = "com.android.apex.cts.shim";
|
||||
static constexpr const char* kHashFilePath = "etc/hash.txt";
|
||||
static constexpr const int kBufSize = 1024;
|
||||
static constexpr const fs::perms kForbiddenFilePermissions =
|
||||
fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec;
|
||||
static constexpr const char* kExpectedCtsShimFiles[] = {
|
||||
"apex_manifest.json",
|
||||
"apex_manifest.pb",
|
||||
"etc/hash.txt",
|
||||
"app/CtsShim/CtsShim.apk",
|
||||
"app/CtsShim@1/CtsShim.apk",
|
||||
"app/CtsShim@2/CtsShim.apk",
|
||||
"app/CtsShim@3/CtsShim.apk",
|
||||
"app/CtsShimTargetPSdk/CtsShimTargetPSdk.apk",
|
||||
"app/CtsShimTargetPSdk@1/CtsShimTargetPSdk.apk",
|
||||
"app/CtsShimTargetPSdk@2/CtsShimTargetPSdk.apk",
|
||||
"app/CtsShimTargetPSdk@3/CtsShimTargetPSdk.apk",
|
||||
"priv-app/CtsShimPriv/CtsShimPriv.apk",
|
||||
"priv-app/CtsShimPriv@1/CtsShimPriv.apk",
|
||||
"priv-app/CtsShimPriv@2/CtsShimPriv.apk",
|
||||
"priv-app/CtsShimPriv@3/CtsShimPriv.apk",
|
||||
};
|
||||
|
||||
Result<std::string> CalculateSha512(const std::string& path) {
|
||||
LOG(DEBUG) << "Calculating SHA512 of " << path;
|
||||
SHA512_CTX ctx;
|
||||
SHA512_Init(&ctx);
|
||||
std::ifstream apex(path, std::ios::binary);
|
||||
if (apex.bad()) {
|
||||
return Error() << "Failed to open " << path;
|
||||
}
|
||||
char buf[kBufSize];
|
||||
while (!apex.eof()) {
|
||||
apex.read(buf, kBufSize);
|
||||
if (apex.bad()) {
|
||||
return Error() << "Failed to read " << path;
|
||||
}
|
||||
int bytes_read = apex.gcount();
|
||||
SHA512_Update(&ctx, buf, bytes_read);
|
||||
}
|
||||
uint8_t hash[SHA512_DIGEST_LENGTH];
|
||||
SHA512_Final(hash, &ctx);
|
||||
std::stringstream ss;
|
||||
ss << std::hex;
|
||||
for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) {
|
||||
ss << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Result<std::vector<std::string>> GetAllowedHashes(const std::string& path) {
|
||||
using android::base::ReadFileToString;
|
||||
using android::base::StringPrintf;
|
||||
const std::string& file_path =
|
||||
StringPrintf("%s/%s", path.c_str(), kHashFilePath);
|
||||
LOG(DEBUG) << "Reading SHA512 from " << file_path;
|
||||
std::string hash;
|
||||
if (!ReadFileToString(file_path, &hash, false /* follows symlinks */)) {
|
||||
return ErrnoError() << "Failed to read " << file_path;
|
||||
}
|
||||
std::vector<std::string> allowed_hashes = android::base::Split(hash, "\n");
|
||||
auto system_shim_hash = CalculateSha512(
|
||||
StringPrintf("%s/%s", kApexPackageSystemDir, shim::kSystemShimApexName));
|
||||
if (!system_shim_hash.ok()) {
|
||||
return system_shim_hash.error();
|
||||
}
|
||||
allowed_hashes.push_back(std::move(*system_shim_hash));
|
||||
return allowed_hashes;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool IsShimApex(const ApexFile& apex_file) {
|
||||
return apex_file.GetManifest().name() == kApexCtsShimPackage;
|
||||
}
|
||||
|
||||
Result<void> ValidateShimApex(const std::string& mount_point,
|
||||
const ApexFile& apex_file) {
|
||||
LOG(DEBUG) << "Validating shim apex " << mount_point;
|
||||
const ApexManifest& manifest = apex_file.GetManifest();
|
||||
if (!manifest.preinstallhook().empty() ||
|
||||
!manifest.postinstallhook().empty()) {
|
||||
return Errorf("Shim apex is not allowed to have pre or post install hooks");
|
||||
}
|
||||
std::error_code ec;
|
||||
std::unordered_set<std::string> expected_files;
|
||||
for (auto file : kExpectedCtsShimFiles) {
|
||||
expected_files.insert(file);
|
||||
}
|
||||
|
||||
auto iter = fs::recursive_directory_iterator(mount_point, ec);
|
||||
// Unfortunately fs::recursive_directory_iterator::operator++ can throw an
|
||||
// exception, which means that it's impossible to use range-based for loop
|
||||
// here.
|
||||
while (iter != fs::end(iter)) {
|
||||
auto path = iter->path();
|
||||
// Resolve the mount point to ensure any trailing slash is removed.
|
||||
auto resolved_mount_point = fs::path(mount_point).string();
|
||||
auto local_path = path.string().substr(resolved_mount_point.length() + 1);
|
||||
fs::file_status status = iter->status(ec);
|
||||
|
||||
if (fs::is_symlink(status)) {
|
||||
return Error()
|
||||
<< "Shim apex is not allowed to contain symbolic links, found "
|
||||
<< path;
|
||||
} else if (fs::is_regular_file(status)) {
|
||||
if ((status.permissions() & kForbiddenFilePermissions) !=
|
||||
fs::perms::none) {
|
||||
return Error() << path << " has illegal permissions";
|
||||
}
|
||||
auto ex = expected_files.find(local_path);
|
||||
if (ex != expected_files.end()) {
|
||||
expected_files.erase(local_path);
|
||||
} else {
|
||||
return Error() << path << " is an unexpected file inside the shim apex";
|
||||
}
|
||||
} else if (!fs::is_directory(status)) {
|
||||
// If this is not a symlink, a file or a directory, fail.
|
||||
return Error() << "Unexpected file entry in shim apex: " << iter->path();
|
||||
}
|
||||
iter = iter.increment(ec);
|
||||
if (ec) {
|
||||
return Error() << "Failed to scan " << mount_point << " : "
|
||||
<< ec.message();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> ValidateUpdate(const std::string& system_apex_path,
|
||||
const std::string& new_apex_path) {
|
||||
LOG(DEBUG) << "Validating update of shim apex to " << new_apex_path
|
||||
<< " using system shim apex " << system_apex_path;
|
||||
auto allowed = GetAllowedHashes(system_apex_path);
|
||||
if (!allowed.ok()) {
|
||||
return allowed.error();
|
||||
}
|
||||
auto actual = CalculateSha512(new_apex_path);
|
||||
if (!actual.ok()) {
|
||||
return actual.error();
|
||||
}
|
||||
auto it = std::find(allowed->begin(), allowed->end(), *actual);
|
||||
if (it == allowed->end()) {
|
||||
return Error() << new_apex_path << " has unexpected SHA512 hash "
|
||||
<< *actual;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace shim
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "apex_file.h"
|
||||
|
||||
#include <android-base/result.h>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
namespace shim {
|
||||
|
||||
constexpr const char* kSystemShimApexName = "com.android.apex.cts.shim.apex";
|
||||
|
||||
bool IsShimApex(const ApexFile& apex_file);
|
||||
|
||||
android::base::Result<void> ValidateShimApex(const std::string& mount_point,
|
||||
const ApexFile& apex_file);
|
||||
|
||||
android::base::Result<void> ValidateUpdate(const std::string& system_apex_path,
|
||||
const std::string& new_apex_path);
|
||||
|
||||
} // namespace shim
|
||||
} // namespace apex
|
||||
} // namespace android
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEXD_H_
|
||||
#define ANDROID_APEXD_APEXD_H_
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/result.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "apex_classpath.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;
|
||||
const char* metadata_sepolicy_staged_dir;
|
||||
// Overrides the path to the "metadata" partition which is by default
|
||||
// /dev/block/by-name/payload-metadata It should be a path pointing the first
|
||||
// partition of the VM payload disk. So, realpath() of this path is checked if
|
||||
// it has the suffix "1". For example, /test-dir/test-metadata-1 can be valid
|
||||
// and the subsequent numbers should point APEX files.
|
||||
const char* vm_payload_metadata_partition_prop;
|
||||
const char* active_apex_selinux_ctx;
|
||||
};
|
||||
|
||||
static const ApexdConfig kDefaultConfig = {
|
||||
kApexStatusSysprop,
|
||||
kApexPackageBuiltinDirs,
|
||||
kActiveApexPackagesDataDir,
|
||||
kApexDecompressedDir,
|
||||
kOtaReservedDir,
|
||||
kApexHashTreeDir,
|
||||
kStagedSessionsDir,
|
||||
kMetadataSepolicyStagedDir,
|
||||
kVmPayloadMetadataPartitionProp,
|
||||
"u:object_r:staging_data_file",
|
||||
};
|
||||
|
||||
class CheckpointInterface;
|
||||
|
||||
void SetConfig(const ApexdConfig& config);
|
||||
|
||||
// Exposed only for testing.
|
||||
android::base::Result<void> Unmount(
|
||||
const MountedApexDatabase::MountedApexData& data, bool deferred);
|
||||
|
||||
android::base::Result<void> ResumeRevertIfNeeded();
|
||||
|
||||
android::base::Result<void> PreinstallPackages(
|
||||
const std::vector<std::string>& paths) WARN_UNUSED;
|
||||
|
||||
android::base::Result<void> StagePackages(
|
||||
const std::vector<std::string>& tmpPaths) WARN_UNUSED;
|
||||
android::base::Result<void> UnstagePackages(
|
||||
const std::vector<std::string>& paths) WARN_UNUSED;
|
||||
|
||||
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<std::vector<ApexFile>> GetStagedApexFiles(
|
||||
const int session_id,
|
||||
const std::vector<int>& child_session_ids) WARN_UNUSED;
|
||||
android::base::Result<ClassPath> MountAndDeriveClassPath(
|
||||
const std::vector<ApexFile>&) WARN_UNUSED;
|
||||
android::base::Result<void> MarkStagedSessionReady(const int session_id)
|
||||
WARN_UNUSED;
|
||||
android::base::Result<void> MarkStagedSessionSuccessful(const int session_id)
|
||||
WARN_UNUSED;
|
||||
// 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)
|
||||
WARN_UNUSED;
|
||||
|
||||
std::vector<ApexFile> GetActivePackages();
|
||||
android::base::Result<ApexFile> GetActivePackage(
|
||||
const std::string& package_name);
|
||||
|
||||
std::vector<ApexFile> GetFactoryPackages();
|
||||
|
||||
android::base::Result<void> AbortStagedSession(const int session_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> 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();
|
||||
// Sets the values of gVoldService and gInFsCheckpointMode.
|
||||
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);
|
||||
// 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();
|
||||
// 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();
|
||||
// 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(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 OnBootCompleted();
|
||||
// Exposed for testing
|
||||
void RemoveInactiveDataApex();
|
||||
void BootCompletedCleanup();
|
||||
int SnapshotOrRestoreDeUserData();
|
||||
|
||||
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();
|
||||
|
||||
// Exposed for unit tests
|
||||
bool ShouldAllocateSpaceForDecompression(const std::string& new_apex_name,
|
||||
int64_t new_apex_version,
|
||||
const ApexFileRepository& instance);
|
||||
|
||||
int64_t CalculateSizeForCompressedApex(
|
||||
const std::vector<std::tuple<std::string, int64_t, int64_t>>&
|
||||
compressed_apexes,
|
||||
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);
|
||||
|
||||
// Entry point when running in the VM mode (with --vm arg)
|
||||
int OnStartInVmMode();
|
||||
|
||||
// Activates apexes in otapreot_chroot environment.
|
||||
// TODO(b/172911822): support compressed apexes.
|
||||
int OnOtaChrootBootstrap();
|
||||
|
||||
// Activates flattened apexes
|
||||
int ActivateFlattenedApex();
|
||||
|
||||
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);
|
||||
|
||||
// Exposed for testing.
|
||||
android::base::Result<int> AddBlockApex(ApexFileRepository& instance);
|
||||
|
||||
bool IsActiveApexChanged(const ApexFile& apex);
|
||||
|
||||
// Shouldn't be used outside of apexd_test.cpp
|
||||
std::set<std::string>& GetChangedActiveApexesForTesting();
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEXD_H_
|
|
@ -0,0 +1,21 @@
|
|||
service apexd /system/bin/apexd
|
||||
interface aidl apexservice
|
||||
class core
|
||||
user root
|
||||
group system
|
||||
oneshot
|
||||
disabled # does not start with the core class
|
||||
reboot_on_failure reboot,apexd-failed
|
||||
|
||||
service apexd-bootstrap /system/bin/apexd --bootstrap
|
||||
user root
|
||||
group system
|
||||
oneshot
|
||||
disabled
|
||||
reboot_on_failure reboot,bootloader,bootstrap-apexd-failed
|
||||
|
||||
service apexd-snapshotde /system/bin/apexd --snapshotde
|
||||
user root
|
||||
group system
|
||||
oneshot
|
||||
disabled
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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_CHECKPOINT_H_
|
||||
#define ANDROID_APEXD_APEXD_CHECKPOINT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <android-base/result.h>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
class CheckpointInterface {
|
||||
public:
|
||||
virtual ~CheckpointInterface() {}
|
||||
|
||||
virtual android::base::Result<bool> SupportsFsCheckpoints() = 0;
|
||||
|
||||
virtual android::base::Result<bool> NeedsCheckpoint() = 0;
|
||||
virtual android::base::Result<bool> NeedsRollback() = 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;
|
||||
};
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEXD_CHECKPOINT_H_
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "apexd"
|
||||
|
||||
#include "apexd_checkpoint_vold.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android/os/IVold.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
|
||||
using android::sp;
|
||||
using android::base::Error;
|
||||
using android::base::Result;
|
||||
using android::os::IVold;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
Result<VoldCheckpointInterface> VoldCheckpointInterface::Create() {
|
||||
auto vold_service =
|
||||
defaultServiceManager()->getService(android::String16("vold"));
|
||||
if (vold_service != nullptr) {
|
||||
return VoldCheckpointInterface(
|
||||
android::interface_cast<android::os::IVold>(vold_service));
|
||||
}
|
||||
return Errorf("Failed to retrieve vold service.");
|
||||
}
|
||||
|
||||
VoldCheckpointInterface::VoldCheckpointInterface(sp<IVold>&& vold_service) {
|
||||
vold_service_ = vold_service;
|
||||
supports_fs_checkpoints_ = false;
|
||||
android::binder::Status status =
|
||||
vold_service_->supportsCheckpoint(&supports_fs_checkpoints_);
|
||||
if (!status.isOk()) {
|
||||
LOG(ERROR) << "Failed to check if filesystem checkpoints are supported: "
|
||||
<< status.toString8().c_str();
|
||||
}
|
||||
}
|
||||
|
||||
VoldCheckpointInterface::VoldCheckpointInterface(
|
||||
VoldCheckpointInterface&& other) noexcept {
|
||||
vold_service_ = std::move(other.vold_service_);
|
||||
supports_fs_checkpoints_ = other.supports_fs_checkpoints_;
|
||||
}
|
||||
|
||||
VoldCheckpointInterface::~VoldCheckpointInterface() {
|
||||
// Just here to be able to forward-declare IVold.
|
||||
}
|
||||
|
||||
Result<bool> VoldCheckpointInterface::SupportsFsCheckpoints() {
|
||||
return supports_fs_checkpoints_;
|
||||
}
|
||||
|
||||
Result<bool> VoldCheckpointInterface::NeedsCheckpoint() {
|
||||
if (supports_fs_checkpoints_) {
|
||||
bool needs_checkpoint = false;
|
||||
android::binder::Status status =
|
||||
vold_service_->needsCheckpoint(&needs_checkpoint);
|
||||
if (!status.isOk()) {
|
||||
return Error() << status.toString8().c_str();
|
||||
}
|
||||
return needs_checkpoint;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Result<bool> VoldCheckpointInterface::NeedsRollback() {
|
||||
if (supports_fs_checkpoints_) {
|
||||
bool needs_rollback = false;
|
||||
android::binder::Status status =
|
||||
vold_service_->needsRollback(&needs_rollback);
|
||||
if (!status.isOk()) {
|
||||
return Error() << status.toString8().c_str();
|
||||
}
|
||||
return needs_rollback;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Result<void> VoldCheckpointInterface::StartCheckpoint(int32_t num_retries) {
|
||||
if (supports_fs_checkpoints_) {
|
||||
android::binder::Status status =
|
||||
vold_service_->startCheckpoint(num_retries);
|
||||
if (!status.isOk()) {
|
||||
return Error() << status.toString8().c_str();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return Errorf("Device does not support filesystem checkpointing");
|
||||
}
|
||||
|
||||
Result<void> VoldCheckpointInterface::AbortChanges(const std::string& msg,
|
||||
bool retry) {
|
||||
vold_service_->abortChanges(msg, retry);
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEXD_CHECKPOINT_VOLD_H_
|
||||
#define ANDROID_APEXD_APEXD_CHECKPOINT_VOLD_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include "apexd_checkpoint.h"
|
||||
|
||||
#include <android-base/result.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
namespace os {
|
||||
class IVold;
|
||||
}
|
||||
|
||||
namespace apex {
|
||||
|
||||
class VoldCheckpointInterface : public CheckpointInterface {
|
||||
public:
|
||||
~VoldCheckpointInterface();
|
||||
|
||||
android::base::Result<bool> SupportsFsCheckpoints() override;
|
||||
|
||||
android::base::Result<bool> NeedsCheckpoint() override;
|
||||
android::base::Result<bool> NeedsRollback() override;
|
||||
android::base::Result<void> StartCheckpoint(int32_t retry) override;
|
||||
|
||||
android::base::Result<void> AbortChanges(const std::string& msg,
|
||||
bool num_retries) override;
|
||||
|
||||
static android::base::Result<VoldCheckpointInterface> Create();
|
||||
|
||||
VoldCheckpointInterface(VoldCheckpointInterface&& other) noexcept;
|
||||
|
||||
private:
|
||||
explicit VoldCheckpointInterface(sp<os::IVold>&& vold_service);
|
||||
|
||||
sp<os::IVold> vold_service_;
|
||||
bool supports_fs_checkpoints_;
|
||||
};
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEXD_CHECKPOINT_VOLD_H_
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "apexd_lifecycle.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/properties.h>
|
||||
|
||||
#include "apexd_utils.h"
|
||||
|
||||
#define LOG_TAG "apexd"
|
||||
|
||||
using android::base::GetProperty;
|
||||
using android::base::Result;
|
||||
using android::base::WaitForProperty;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
|
||||
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 10s), briefly check if boot has completed successfully,
|
||||
// if not continue waiting for updatable_crashing.
|
||||
// We use this strategy so that we can quickly detect if an updatable
|
||||
// process is crashing.
|
||||
if (WaitForProperty("sys.init.updatable_crashing", "1",
|
||||
std::chrono::seconds(10))) {
|
||||
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, "");
|
||||
if (!result.ok()) {
|
||||
LOG(ERROR) << "Revert failed : " << result.error();
|
||||
return WaitForBootStatus();
|
||||
} else {
|
||||
// This should never be reached, since revert_fn should've rebooted
|
||||
// the device.
|
||||
LOG(FATAL) << "Active sessions were reverted, but reboot wasn't "
|
||||
"triggered.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApexdLifecycle::WaitForBootStatus() {
|
||||
while (!boot_completed_) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void ApexdLifecycle::MarkBootCompleted() { boot_completed_ = true; }
|
||||
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
void WaitForBootStatus();
|
||||
|
||||
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
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "apexd"
|
||||
#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
|
||||
|
||||
#include "apexd_loop.h"
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <libdm/dm.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/loop.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/sysmacros.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 <utils/Trace.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::ReadFileToString;
|
||||
using android::base::Result;
|
||||
using android::base::StartsWith;
|
||||
using android::base::StringPrintf;
|
||||
using android::base::unique_fd;
|
||||
using android::dm::DeviceMapper;
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
namespace loop {
|
||||
|
||||
static constexpr const char* kApexLoopIdPrefix = "apex:";
|
||||
|
||||
// 128 kB read-ahead, which we currently use for /system as well
|
||||
static constexpr const char* kReadAheadKb = "128";
|
||||
|
||||
// TODO(b/122059364): Even though the kernel has created the loop
|
||||
// device, we still depend on ueventd to run to actually create the
|
||||
// device node in userspace. To solve this properly we should listen on
|
||||
// the netlink socket for uevents, or use inotify. For now, this will
|
||||
// have to do.
|
||||
static constexpr size_t kLoopDeviceRetryAttempts = 3u;
|
||||
|
||||
void LoopbackDeviceUniqueFd::MaybeCloseBad() {
|
||||
if (device_fd.get() != -1) {
|
||||
// Disassociate any files.
|
||||
if (ioctl(device_fd.get(), LOOP_CLR_FD) == -1) {
|
||||
PLOG(ERROR) << "Unable to clear fd for loopback device";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<void> ConfigureScheduler(const std::string& device_path) {
|
||||
if (!StartsWith(device_path, "/dev/")) {
|
||||
return Error() << "Invalid argument " << device_path;
|
||||
}
|
||||
|
||||
const std::string device_name = Basename(device_path);
|
||||
|
||||
const std::string sysfs_path =
|
||||
StringPrintf("/sys/block/%s/queue/scheduler", device_name.c_str());
|
||||
unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
|
||||
if (sysfs_fd.get() == -1) {
|
||||
return ErrnoError() << "Failed to open " << sysfs_path;
|
||||
}
|
||||
|
||||
// Kernels before v4.1 only support 'noop'. Kernels [v4.1, v5.0) support
|
||||
// 'noop' and 'none'. Kernels v5.0 and later only support 'none'.
|
||||
static constexpr const std::array<std::string_view, 2> kNoScheduler = {
|
||||
"none", "noop"};
|
||||
|
||||
int ret = 0;
|
||||
|
||||
for (const std::string_view& scheduler : kNoScheduler) {
|
||||
ret = write(sysfs_fd.get(), scheduler.data(), scheduler.size());
|
||||
if (ret > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
return ErrnoError() << "Failed to write to " << sysfs_path;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Return the parent device of a partition. Converts e.g. "sda26" into "sda".
|
||||
static Result<std::string> PartitionParent(const std::string& blockdev) {
|
||||
if (blockdev.find('/') != std::string::npos) {
|
||||
return Error() << "Invalid argument " << blockdev;
|
||||
}
|
||||
std::error_code ec;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator("/sys/class/block", ec)) {
|
||||
const std::string path = entry.path().string();
|
||||
if (std::filesystem::exists(
|
||||
StringPrintf("%s/%s", path.c_str(), blockdev.c_str()))) {
|
||||
return Basename(path);
|
||||
}
|
||||
}
|
||||
return blockdev;
|
||||
}
|
||||
|
||||
// Convert a major:minor pair into a block device name.
|
||||
static std::string BlockdevName(dev_t dev) {
|
||||
std::error_code ec;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator("/dev/block", ec)) {
|
||||
struct stat statbuf;
|
||||
if (stat(entry.path().string().c_str(), &statbuf) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (dev == statbuf.st_rdev) {
|
||||
return Basename(entry.path().string());
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// For file `file_path`, retrieve the block device backing the filesystem on
|
||||
// which the file exists and return the queue depth of the block device. The
|
||||
// loop in this function may e.g. traverse the following hierarchy:
|
||||
// /dev/block/dm-9 (system-verity; dm-verity)
|
||||
// -> /dev/block/dm-1 (system_b; dm-linear)
|
||||
// -> /dev/sda26
|
||||
Result<uint32_t> BlockDeviceQueueDepth(const std::string& file_path) {
|
||||
struct stat statbuf;
|
||||
int res = stat(file_path.c_str(), &statbuf);
|
||||
if (res < 0) {
|
||||
return ErrnoErrorf("stat({})", file_path.c_str());
|
||||
}
|
||||
std::string blockdev = "/dev/block/" + BlockdevName(statbuf.st_dev);
|
||||
LOG(VERBOSE) << file_path << " -> " << blockdev;
|
||||
if (blockdev.empty()) {
|
||||
return Errorf("Failed to convert {}:{} (path {})", major(statbuf.st_dev),
|
||||
minor(statbuf.st_dev), file_path.c_str());
|
||||
}
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
for (;;) {
|
||||
std::optional<std::string> child = dm.GetParentBlockDeviceByPath(blockdev);
|
||||
if (!child) {
|
||||
break;
|
||||
}
|
||||
LOG(VERBOSE) << blockdev << " -> " << *child;
|
||||
blockdev = *child;
|
||||
}
|
||||
std::optional<std::string> maybe_blockdev =
|
||||
android::dm::ExtractBlockDeviceName(blockdev);
|
||||
if (!maybe_blockdev) {
|
||||
return Error() << "Failed to remove /dev/block/ prefix from " << blockdev;
|
||||
}
|
||||
Result<std::string> maybe_parent = PartitionParent(*maybe_blockdev);
|
||||
if (!maybe_parent.ok()) {
|
||||
return Error() << "Failed to determine parent of " << *maybe_blockdev;
|
||||
}
|
||||
blockdev = *maybe_parent;
|
||||
LOG(VERBOSE) << "Partition parent: " << blockdev;
|
||||
const std::string nr_tags_path =
|
||||
StringPrintf("/sys/class/block/%s/mq/0/nr_tags", blockdev.c_str());
|
||||
std::string nr_tags;
|
||||
if (!ReadFileToString(nr_tags_path, &nr_tags)) {
|
||||
return Error() << "Failed to read " << nr_tags_path;
|
||||
}
|
||||
nr_tags = android::base::Trim(nr_tags);
|
||||
LOG(VERBOSE) << file_path << " is backed by /dev/" << blockdev
|
||||
<< " and that block device supports queue depth " << nr_tags;
|
||||
return strtol(nr_tags.c_str(), NULL, 0);
|
||||
}
|
||||
|
||||
// Set 'nr_requests' of `loop_device_path` equal to the queue depth of
|
||||
// the block device backing `file_path`.
|
||||
Result<void> ConfigureQueueDepth(const std::string& loop_device_path,
|
||||
const std::string& file_path) {
|
||||
if (!StartsWith(loop_device_path, "/dev/")) {
|
||||
return Error() << "Invalid argument " << loop_device_path;
|
||||
}
|
||||
|
||||
const std::string loop_device_name = Basename(loop_device_path);
|
||||
|
||||
const std::string sysfs_path =
|
||||
StringPrintf("/sys/block/%s/queue/nr_requests", loop_device_name.c_str());
|
||||
std::string cur_nr_requests_str;
|
||||
if (!ReadFileToString(sysfs_path, &cur_nr_requests_str)) {
|
||||
return Error() << "Failed to read " << sysfs_path;
|
||||
}
|
||||
cur_nr_requests_str = android::base::Trim(cur_nr_requests_str);
|
||||
uint32_t cur_nr_requests = 0;
|
||||
if (!ParseUint(cur_nr_requests_str.c_str(), &cur_nr_requests)) {
|
||||
return Error() << "Failed to parse " << cur_nr_requests_str;
|
||||
}
|
||||
|
||||
unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
|
||||
if (sysfs_fd.get() == -1) {
|
||||
return ErrnoErrorf("Failed to open {}", sysfs_path);
|
||||
}
|
||||
|
||||
const auto qd = BlockDeviceQueueDepth(file_path);
|
||||
if (!qd.ok()) {
|
||||
return qd.error();
|
||||
}
|
||||
if (*qd == cur_nr_requests) {
|
||||
return {};
|
||||
}
|
||||
// Only report write failures if reducing the queue depth. Attempts to
|
||||
// increase the queue depth are rejected by the kernel if no I/O scheduler
|
||||
// is associated with the request queue.
|
||||
if (!WriteStringToFd(StringPrintf("%u", *qd), sysfs_fd) &&
|
||||
*qd < cur_nr_requests) {
|
||||
return ErrnoErrorf("Failed to write {} to {}", *qd, sysfs_path);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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());
|
||||
unique_fd sysfs_fd(open(sysfs_device.c_str(), O_RDWR | O_CLOEXEC));
|
||||
if (sysfs_fd.get() == -1) {
|
||||
return ErrnoError() << "Failed to open " << sysfs_device;
|
||||
}
|
||||
|
||||
int ret = TEMP_FAILURE_RETRY(
|
||||
write(sysfs_fd.get(), kReadAheadKb, strlen(kReadAheadKb) + 1));
|
||||
if (ret < 0) {
|
||||
return ErrnoError() << "Failed to write to " << sysfs_device;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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)));
|
||||
if (ctl_fd.get() == -1) {
|
||||
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 = 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";
|
||||
}
|
||||
}
|
||||
|
||||
// Don't wait until the dev nodes are actually created, which
|
||||
// will delay the boot. By simply returing here, the creation of the dev
|
||||
// nodes will be done in parallel with other boot processes, and we
|
||||
// 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()).
|
||||
LOG(INFO) << "Pre-allocated " << num << " loopback devices";
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> ConfigureLoopDevice(const int device_fd, const std::string& target,
|
||||
const uint32_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
|
||||
* on the underlying file, which we want to do to avoid double caching.
|
||||
* Note that Direct I/O won't be enabled immediately, because the block
|
||||
* size of the underlying block device may not match the default loop
|
||||
* device block size (512); when we call LOOP_SET_BLOCK_SIZE below, the
|
||||
* kernel driver will automatically enable Direct I/O when it sees that
|
||||
* condition is now met.
|
||||
*/
|
||||
bool use_buffered_io = false;
|
||||
unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
|
||||
if (target_fd.get() == -1) {
|
||||
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;
|
||||
use_buffered_io = true;
|
||||
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));
|
||||
config.fd = target_fd.get();
|
||||
config.info = li;
|
||||
config.block_size = 4096;
|
||||
if (!use_buffered_io) {
|
||||
li.lo_flags |= LO_FLAGS_DIRECT_IO;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
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.
|
||||
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) {
|
||||
unique_fd sysfs_fd(open(device.c_str(), O_RDWR | O_CLOEXEC));
|
||||
if (sysfs_fd.get() != -1) {
|
||||
return LoopbackDeviceUniqueFd(std::move(sysfs_fd), 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;
|
||||
}
|
||||
}
|
||||
|
||||
return Error() << "Faled to open loopback device " << num;
|
||||
}
|
||||
|
||||
Result<LoopbackDeviceUniqueFd> CreateLoopDevice(const std::string& target,
|
||||
uint32_t image_offset,
|
||||
size_t image_size) {
|
||||
ATRACE_NAME("CreateLoopDevice");
|
||||
|
||||
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 mtx;
|
||||
std::lock_guard lock(mtx);
|
||||
int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
|
||||
if (num == -1) {
|
||||
return ErrnoError() << "Failed LOOP_CTL_GET_FREE";
|
||||
}
|
||||
|
||||
Result<LoopbackDeviceUniqueFd> loop_device = WaitForDevice(num);
|
||||
if (!loop_device.ok()) {
|
||||
return loop_device.error();
|
||||
}
|
||||
CHECK_NE(loop_device->device_fd.get(), -1);
|
||||
|
||||
Result<void> configure_status = ConfigureLoopDevice(
|
||||
loop_device->device_fd.get(), target, image_offset, image_size);
|
||||
if (!configure_status.ok()) {
|
||||
return configure_status.error();
|
||||
}
|
||||
|
||||
return loop_device;
|
||||
}
|
||||
|
||||
Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
|
||||
const std::string& target, uint32_t image_offset, size_t image_size) {
|
||||
ATRACE_NAME("CreateAndConfigureLoopDevice");
|
||||
// Do minimal amount of work while holding a mutex. We need it because
|
||||
// acquiring + configuring a loop device is not atomic. Ideally we should
|
||||
// pre-acquire all the loop devices in advance, so that when we run APEX
|
||||
// activation in-parallel, we can do it without holding any lock.
|
||||
// Unfortunately, this will require some refactoring of how we manage loop
|
||||
// devices, and probably some new loop-control ioctls, so for the time being
|
||||
// we just limit the scope that requires locking.
|
||||
auto loop_device = CreateLoopDevice(target, image_offset, image_size);
|
||||
if (!loop_device.ok()) {
|
||||
return loop_device.error();
|
||||
}
|
||||
|
||||
// We skip confiruing scheduler and queue depth for automotive products.
|
||||
// See: b/241473698.
|
||||
#ifndef DISABLE_LOOP_IO_CONFIG
|
||||
Result<void> sched_status = ConfigureScheduler(loop_device->name);
|
||||
if (!sched_status.ok()) {
|
||||
LOG(WARNING) << "Configuring I/O scheduler failed: "
|
||||
<< sched_status.error();
|
||||
}
|
||||
|
||||
Result<void> qd_status = ConfigureQueueDepth(loop_device->name, target);
|
||||
if (!qd_status.ok()) {
|
||||
LOG(WARNING) << qd_status.error();
|
||||
}
|
||||
#endif
|
||||
|
||||
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) {
|
||||
unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
|
||||
if (fd.get() == -1) {
|
||||
if (errno != ENOENT) {
|
||||
PLOG(WARNING) << "Failed to open " << path;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
struct loop_info64 li;
|
||||
if (ioctl(fd.get(), LOOP_GET_STATUS64, &li) < 0) {
|
||||
if (errno != ENXIO) {
|
||||
PLOG(WARNING) << "Failed to LOOP_GET_STATUS64 " << path;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto id = std::string((char*)li.lo_crypt_name);
|
||||
if (StartsWith(id, kApexLoopIdPrefix)) {
|
||||
extra(path, id);
|
||||
|
||||
if (ioctl(fd.get(), LOOP_CLR_FD, 0) < 0) {
|
||||
PLOG(WARNING) << "Failed to LOOP_CLR_FD " << path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace loop
|
||||
} // namespace apex
|
||||
} // namespace android
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_APEXD_APEXD_LOOP_H_
|
||||
#define ANDROID_APEXD_APEXD_LOOP_H_
|
||||
|
||||
#include <android-base/result.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace apex {
|
||||
namespace loop {
|
||||
|
||||
using android::base::unique_fd;
|
||||
|
||||
struct LoopbackDeviceUniqueFd {
|
||||
unique_fd device_fd;
|
||||
std::string name;
|
||||
|
||||
LoopbackDeviceUniqueFd() {}
|
||||
LoopbackDeviceUniqueFd(unique_fd&& fd, const std::string& name)
|
||||
: device_fd(std::move(fd)), name(name) {}
|
||||
|
||||
LoopbackDeviceUniqueFd(LoopbackDeviceUniqueFd&& fd) noexcept
|
||||
: 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);
|
||||
name = std::move(other.name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~LoopbackDeviceUniqueFd() { MaybeCloseBad(); }
|
||||
|
||||
void MaybeCloseBad();
|
||||
|
||||
void CloseGood() { device_fd.reset(-1); }
|
||||
|
||||
int Get() { return device_fd.get(); }
|
||||
};
|
||||
|
||||
// Exposed only for testing
|
||||
android::base::Result<uint32_t> BlockDeviceQueueDepth(
|
||||
const std::string& file_path);
|
||||
|
||||
android::base::Result<LoopbackDeviceUniqueFd> WaitForDevice(int num);
|
||||
|
||||
android::base::Result<void> ConfigureQueueDepth(
|
||||
const std::string& loop_device_path, const std::string& file_path);
|
||||
|
||||
android::base::Result<void> ConfigureReadAhead(const std::string& device_path);
|
||||
|
||||
android::base::Result<void> PreAllocateLoopDevices(size_t num);
|
||||
|
||||
android::base::Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
|
||||
const std::string& target, uint32_t image_offset, size_t image_size);
|
||||
|
||||
using DestroyLoopFn =
|
||||
std::function<void(const std::string&, const std::string&)>;
|
||||
void DestroyLoopDevice(const std::string& path, const DestroyLoopFn& extra);
|
||||
|
||||
} // namespace loop
|
||||
} // namespace apex
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_APEXD_APEXD_LOOP_H_
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue