diff options
-rw-r--r-- | .clang-format | 15 | ||||
-rw-r--r-- | Android.mk | 101 | ||||
-rw-r--r-- | README.md | 20 | ||||
-rw-r--r-- | adb_install.cpp | 37 | ||||
-rw-r--r-- | applypatch/Android.mk | 176 | ||||
-rw-r--r-- | applypatch/Makefile | 33 | ||||
-rw-r--r-- | applypatch/applypatch.cpp | 1288 | ||||
-rw-r--r-- | applypatch/applypatch_main.cpp (renamed from applypatch/utils.h) | 24 | ||||
-rw-r--r-- | applypatch/applypatch_modes.cpp (renamed from applypatch/main.cpp) | 104 | ||||
-rw-r--r-- | applypatch/applypatch_modes.h | 22 | ||||
-rw-r--r-- | applypatch/bsdiff.cpp | 410 | ||||
-rw-r--r-- | applypatch/bspatch.cpp | 205 | ||||
-rw-r--r-- | applypatch/freecache.cpp | 2 | ||||
-rw-r--r-- | applypatch/imgdiff.cpp | 1329 | ||||
-rw-r--r-- | applypatch/imgdiff_main.cpp | 21 | ||||
-rw-r--r-- | applypatch/imgpatch.cpp | 415 | ||||
-rw-r--r-- | applypatch/include/applypatch/applypatch.h (renamed from applypatch/applypatch.h) | 27 | ||||
-rw-r--r-- | applypatch/include/applypatch/imgdiff.h (renamed from applypatch/imgdiff.h) | 21 | ||||
-rw-r--r-- | applypatch/include/applypatch/imgpatch.h | 10 | ||||
-rw-r--r-- | applypatch/libimgpatch.pc | 6 | ||||
-rw-r--r-- | applypatch/utils.cpp | 65 | ||||
-rw-r--r-- | asn1_decoder.cpp | 253 | ||||
-rw-r--r-- | asn1_decoder.h | 45 | ||||
-rw-r--r-- | bootloader_message/Android.mk | 1 | ||||
-rw-r--r-- | bootloader_message/bootloader_message.cpp | 134 | ||||
-rw-r--r-- | bootloader_message/include/bootloader_message/bootloader_message.h | 154 | ||||
-rw-r--r-- | common.h | 25 | ||||
-rw-r--r-- | device.cpp | 2 | ||||
-rw-r--r-- | device.h | 152 | ||||
-rw-r--r-- | edify/Android.mk | 30 | ||||
-rw-r--r-- | edify/README.md (renamed from edify/README) | 3 | ||||
-rw-r--r-- | edify/edify_parser.cpp | 79 | ||||
-rw-r--r-- | edify/expr.cpp | 585 | ||||
-rw-r--r-- | edify/expr.h | 172 | ||||
-rw-r--r-- | edify/main.cpp | 219 | ||||
-rw-r--r-- | edify/parser.yy | 74 | ||||
-rw-r--r-- | error_code.h | 84 | ||||
-rw-r--r-- | etc/META-INF/com/google/android/update-script | 8 | ||||
-rw-r--r-- | etc/init.rc | 13 | ||||
-rw-r--r-- | fuse_sdcard_provider.cpp | 13 | ||||
-rw-r--r-- | install.cpp | 874 | ||||
-rw-r--r-- | install.h | 11 | ||||
-rw-r--r-- | minadbd/Android.mk | 6 | ||||
-rw-r--r-- | minadbd/README.md (renamed from minadbd/README.txt) | 2 | ||||
-rw-r--r-- | minadbd/fuse_adb_provider.cpp | 2 | ||||
-rw-r--r-- | minadbd/minadbd.cpp (renamed from minadbd/adb_main.cpp) | 6 | ||||
-rw-r--r-- | minadbd/minadbd.h (renamed from minzip/inline_magic.h) | 14 | ||||
-rw-r--r-- | minadbd/minadbd_services.cpp (renamed from minadbd/services.cpp) | 7 | ||||
-rw-r--r-- | minui/Android.mk | 40 | ||||
-rw-r--r-- | minui/events.cpp | 217 | ||||
-rw-r--r-- | minui/graphics.cpp | 155 | ||||
-rw-r--r-- | minui/graphics.h | 31 | ||||
-rw-r--r-- | minui/graphics_adf.cpp | 318 | ||||
-rw-r--r-- | minui/graphics_adf.h | 58 | ||||
-rw-r--r-- | minui/graphics_drm.cpp | 671 | ||||
-rw-r--r-- | minui/graphics_drm.h | 58 | ||||
-rw-r--r-- | minui/graphics_fbdev.cpp | 286 | ||||
-rw-r--r-- | minui/graphics_fbdev.h | 44 | ||||
-rw-r--r-- | minui/include/minui/minui.h (renamed from minui/minui.h) | 20 | ||||
-rw-r--r-- | minui/resources.cpp | 85 | ||||
-rw-r--r-- | minzip/Android.mk | 23 | ||||
-rw-r--r-- | minzip/Bits.h | 357 | ||||
-rw-r--r-- | minzip/Hash.c | 379 | ||||
-rw-r--r-- | minzip/Hash.h | 194 | ||||
-rw-r--r-- | minzip/Log.h | 207 | ||||
-rw-r--r-- | minzip/SysUtil.c | 212 | ||||
-rw-r--r-- | minzip/SysUtil.h | 52 | ||||
-rw-r--r-- | minzip/Zip.c | 1016 | ||||
-rw-r--r-- | minzip/Zip.h | 172 | ||||
-rw-r--r-- | mounts.cpp | 89 | ||||
-rw-r--r-- | mounts.h (renamed from minzip/Inlines.c) | 25 | ||||
-rw-r--r-- | mtdutils/Android.mk | 20 | ||||
-rw-r--r-- | mtdutils/flash_image.c | 143 | ||||
-rw-r--r-- | mtdutils/mounts.c | 161 | ||||
-rw-r--r-- | mtdutils/mounts.h | 41 | ||||
-rw-r--r-- | mtdutils/mtdutils.c | 561 | ||||
-rw-r--r-- | mtdutils/mtdutils.h | 62 | ||||
-rw-r--r-- | otafault/Android.mk | 11 | ||||
-rw-r--r-- | otafault/config.cpp | 24 | ||||
-rw-r--r-- | otafault/config.h | 4 | ||||
-rw-r--r-- | otafault/ota_io.cpp | 31 | ||||
-rw-r--r-- | otafault/ota_io.h | 24 | ||||
-rw-r--r-- | otautil/Android.mk | 33 | ||||
-rw-r--r-- | otautil/DirUtil.cpp (renamed from minzip/DirUtil.c) | 66 | ||||
-rw-r--r-- | otautil/DirUtil.h (renamed from minzip/DirUtil.h) | 3 | ||||
-rw-r--r-- | otautil/SysUtil.cpp | 212 | ||||
-rw-r--r-- | otautil/SysUtil.h | 55 | ||||
-rw-r--r-- | otautil/ThermalUtil.cpp | 80 | ||||
-rw-r--r-- | otautil/ThermalUtil.h | 24 | ||||
-rw-r--r-- | otautil/ZipUtil.cpp | 121 | ||||
-rw-r--r-- | otautil/ZipUtil.h | 57 | ||||
-rw-r--r-- | print_sha1.h | 4 | ||||
-rw-r--r-- | private/install.h | 27 | ||||
-rw-r--r-- | recovery-persist.cpp | 55 | ||||
-rw-r--r-- | recovery-refresh.cpp | 59 | ||||
-rw-r--r-- | recovery.cpp | 1551 | ||||
-rw-r--r-- | res-hdpi/images/erasing_text.png | bin | 55181 -> 50184 bytes | |||
-rw-r--r-- | res-hdpi/images/error_text.png | bin | 39052 -> 35856 bytes | |||
-rw-r--r-- | res-hdpi/images/installing_security_text.png | bin | 122186 -> 113078 bytes | |||
-rw-r--r-- | res-hdpi/images/installing_text.png | bin | 113078 -> 104002 bytes | |||
-rw-r--r-- | res-hdpi/images/no_command_text.png | bin | 67427 -> 61417 bytes | |||
-rw-r--r-- | res-mdpi/images/erasing_text.png | bin | 31323 -> 29660 bytes | |||
-rw-r--r-- | res-mdpi/images/error_text.png | bin | 22467 -> 21123 bytes | |||
-rw-r--r-- | res-mdpi/images/installing_security_text.png | bin | 74376 -> 69603 bytes | |||
-rw-r--r-- | res-mdpi/images/installing_text.png | bin | 66242 -> 61281 bytes | |||
-rw-r--r-- | res-mdpi/images/no_command_text.png | bin | 36416 -> 34366 bytes | |||
-rw-r--r-- | res-xhdpi/images/erasing_text.png | bin | 80047 -> 73097 bytes | |||
-rw-r--r-- | res-xhdpi/images/error_text.png | bin | 57974 -> 52180 bytes | |||
-rw-r--r-- | res-xhdpi/images/installing_security_text.png | bin | 212335 -> 197146 bytes | |||
-rw-r--r-- | res-xhdpi/images/installing_text.png | bin | 191771 -> 175660 bytes | |||
-rw-r--r-- | res-xhdpi/images/no_command_text.png | bin | 94944 -> 86619 bytes | |||
-rw-r--r-- | res-xxhdpi/images/erasing_text.png | bin | 136033 -> 121637 bytes | |||
-rw-r--r-- | res-xxhdpi/images/error_text.png | bin | 96714 -> 84961 bytes | |||
-rw-r--r-- | res-xxhdpi/images/installing_security_text.png | bin | 484913 -> 447228 bytes | |||
-rw-r--r-- | res-xxhdpi/images/installing_text.png | bin | 455797 -> 416000 bytes | |||
-rw-r--r-- | res-xxhdpi/images/no_command_text.png | bin | 266239 -> 243226 bytes | |||
-rw-r--r-- | res-xxxhdpi/images/erasing_text.png | bin | 297262 -> 263646 bytes | |||
-rw-r--r-- | res-xxxhdpi/images/error_text.png | bin | 199691 -> 178458 bytes | |||
-rw-r--r-- | res-xxxhdpi/images/installing_security_text.png | bin | 676381 -> 610223 bytes | |||
-rw-r--r-- | res-xxxhdpi/images/installing_text.png | bin | 637038 -> 567987 bytes | |||
-rw-r--r-- | res-xxxhdpi/images/no_command_text.png | bin | 372530 -> 331473 bytes | |||
-rw-r--r-- | roots.cpp | 142 | ||||
-rw-r--r-- | roots.h | 2 | ||||
-rw-r--r-- | rotate_logs.cpp | 115 | ||||
-rw-r--r-- | rotate_logs.h | 44 | ||||
-rw-r--r-- | screen_ui.cpp | 264 | ||||
-rw-r--r-- | screen_ui.h | 20 | ||||
-rw-r--r-- | stub_ui.h | 65 | ||||
-rw-r--r-- | tests/Android.mk | 169 | ||||
-rw-r--r-- | tests/common/component_test_util.h | 43 | ||||
-rw-r--r-- | tests/common/test_constants.h | 26 | ||||
-rw-r--r-- | tests/component/applypatch_test.cpp | 609 | ||||
-rw-r--r-- | tests/component/bootloader_message_test.cpp | 207 | ||||
-rw-r--r-- | tests/component/edify_test.cpp | 170 | ||||
-rw-r--r-- | tests/component/imgdiff_test.cpp | 585 | ||||
-rw-r--r-- | tests/component/install_test.cpp | 228 | ||||
-rw-r--r-- | tests/component/sideload_test.cpp | 21 | ||||
-rw-r--r-- | tests/component/uncrypt_test.cpp | 192 | ||||
-rw-r--r-- | tests/component/updater_test.cpp | 609 | ||||
-rw-r--r-- | tests/component/verifier_test.cpp | 237 | ||||
-rw-r--r-- | tests/manual/recovery_test.cpp | 224 | ||||
-rw-r--r-- | tests/testdata/bonus.file | bin | 0 -> 557334 bytes | |||
-rw-r--r-- | tests/testdata/boot.img | bin | 0 -> 783655 bytes | |||
-rw-r--r-- | tests/testdata/otasigned_v1.zip (renamed from tests/testdata/otasigned.zip) | bin | 4009 -> 4009 bytes | |||
-rw-r--r-- | tests/testdata/otasigned_v2.zip (renamed from tests/testdata/otasigned_f4.zip) | bin | 5195 -> 5195 bytes | |||
-rw-r--r-- | tests/testdata/otasigned_v3.zip (renamed from tests/testdata/otasigned_sha256.zip) | bin | 5326 -> 5326 bytes | |||
-rw-r--r-- | tests/testdata/otasigned_v4.zip (renamed from tests/testdata/otasigned_f4_sha256.zip) | bin | 5319 -> 5319 bytes | |||
-rw-r--r-- | tests/testdata/otasigned_v5.zip (renamed from tests/testdata/otasigned_ecdsa_sha256.zip) | bin | 3085 -> 3085 bytes | |||
-rw-r--r-- | tests/testdata/recovery-from-boot-with-bonus.p | bin | 0 -> 381615 bytes | |||
-rw-r--r-- | tests/testdata/recovery-from-boot.p | bin | 0 -> 5404 bytes | |||
-rw-r--r-- | tests/testdata/recovery.img | bin | 0 -> 529707 bytes | |||
-rw-r--r-- | tests/testdata/testkey_v1.pk8 (renamed from tests/testdata/testkey.pk8) | bin | 1217 -> 1217 bytes | |||
-rw-r--r-- | tests/testdata/testkey_v1.txt (renamed from tests/testdata/test_key_e3.txt) | 0 | ||||
-rw-r--r-- | tests/testdata/testkey_v1.x509.pem (renamed from tests/testdata/testkey.x509.pem) | 0 | ||||
-rw-r--r-- | tests/testdata/testkey_v2.pk8 (renamed from tests/testdata/test_f4.pk8) | bin | 1217 -> 1217 bytes | |||
-rw-r--r-- | tests/testdata/testkey_v2.txt (renamed from tests/testdata/test_key_f4.txt) | 0 | ||||
-rw-r--r-- | tests/testdata/testkey_v2.x509.pem (renamed from tests/testdata/test_f4.x509.pem) | 0 | ||||
l--------- | tests/testdata/testkey_v3.pk8 | 1 | ||||
-rw-r--r-- | tests/testdata/testkey_v3.txt | 1 | ||||
-rw-r--r-- | tests/testdata/testkey_v3.x509.pem (renamed from tests/testdata/testkey_sha256.x509.pem) | 0 | ||||
l--------- | tests/testdata/testkey_v4.pk8 | 1 | ||||
-rw-r--r-- | tests/testdata/testkey_v4.txt | 1 | ||||
-rw-r--r-- | tests/testdata/testkey_v4.x509.pem (renamed from tests/testdata/test_f4_sha256.x509.pem) | 0 | ||||
-rw-r--r-- | tests/testdata/testkey_v5.pk8 (renamed from tests/testdata/testkey_ecdsa.pk8) | bin | 138 -> 138 bytes | |||
-rw-r--r-- | tests/testdata/testkey_v5.txt (renamed from tests/testdata/test_key_ec.txt) | 0 | ||||
-rw-r--r-- | tests/testdata/testkey_v5.x509.pem (renamed from tests/testdata/testkey_ecdsa.x509.pem) | 0 | ||||
-rw-r--r-- | tests/testdata/ziptest_dummy-update.zip | bin | 0 -> 1090065 bytes | |||
-rw-r--r-- | tests/testdata/ziptest_valid.zip | bin | 0 -> 758 bytes | |||
-rw-r--r-- | tests/unit/asn1_decoder_test.cpp | 397 | ||||
-rw-r--r-- | tests/unit/dirutil_test.cpp | 150 | ||||
-rw-r--r-- | tests/unit/locale_test.cpp | 18 | ||||
-rw-r--r-- | tests/unit/recovery_test.cpp | 92 | ||||
-rw-r--r-- | tests/unit/sysutil_test.cpp | 140 | ||||
-rw-r--r-- | tests/unit/zip_test.cpp | 90 | ||||
-rw-r--r-- | tests/unit/ziputil_test.cpp | 191 | ||||
-rw-r--r-- | tools/dumpkey/Android.mk (renamed from tools/ota/Android.mk) | 21 | ||||
-rw-r--r-- | tools/dumpkey/DumpPublicKey.java | 270 | ||||
-rw-r--r-- | tools/dumpkey/DumpPublicKey.mf | 1 | ||||
-rw-r--r-- | tools/ota/add-property-tag.c | 141 | ||||
-rw-r--r-- | tools/ota/check-lost+found.c | 145 | ||||
-rw-r--r-- | tools/ota/convert-to-bmp.py | 79 | ||||
-rw-r--r-- | tools/recovery_l10n/README.md | 36 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-az/strings.xml (renamed from tools/recovery_l10n/res/values-az-rAZ/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-be/strings.xml (renamed from tools/recovery_l10n/res/values-be-rBY/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-bn/strings.xml (renamed from tools/recovery_l10n/res/values-bn-rBD/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-bs/strings.xml (renamed from tools/recovery_l10n/res/values-bs-rBA/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-et/strings.xml (renamed from tools/recovery_l10n/res/values-et-rEE/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-eu/strings.xml (renamed from tools/recovery_l10n/res/values-eu-rES/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-gl/strings.xml (renamed from tools/recovery_l10n/res/values-gl-rES/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-gu/strings.xml (renamed from tools/recovery_l10n/res/values-gu-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-hy/strings.xml (renamed from tools/recovery_l10n/res/values-hy-rAM/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-is/strings.xml (renamed from tools/recovery_l10n/res/values-is-rIS/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ka/strings.xml (renamed from tools/recovery_l10n/res/values-ka-rGE/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-kk/strings.xml (renamed from tools/recovery_l10n/res/values-kk-rKZ/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-km/strings.xml (renamed from tools/recovery_l10n/res/values-km-rKH/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-kn/strings.xml (renamed from tools/recovery_l10n/res/values-kn-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ky/strings.xml (renamed from tools/recovery_l10n/res/values-ky-rKG/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-lo/strings.xml (renamed from tools/recovery_l10n/res/values-lo-rLA/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-mk/strings.xml (renamed from tools/recovery_l10n/res/values-mk-rMK/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ml/strings.xml (renamed from tools/recovery_l10n/res/values-ml-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-mn/strings.xml (renamed from tools/recovery_l10n/res/values-mn-rMN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-mr/strings.xml (renamed from tools/recovery_l10n/res/values-mr-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ms/strings.xml (renamed from tools/recovery_l10n/res/values-ms-rMY/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-my/strings.xml (renamed from tools/recovery_l10n/res/values-my-rMM/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ne/strings.xml (renamed from tools/recovery_l10n/res/values-ne-rNP/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-pa/strings.xml (renamed from tools/recovery_l10n/res/values-pa-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-si/strings.xml (renamed from tools/recovery_l10n/res/values-si-rLK/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-sq/strings.xml (renamed from tools/recovery_l10n/res/values-sq-rAL/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ta/strings.xml (renamed from tools/recovery_l10n/res/values-ta-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-te/strings.xml (renamed from tools/recovery_l10n/res/values-te-rIN/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-ur/strings.xml (renamed from tools/recovery_l10n/res/values-ur-rPK/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values-uz/strings.xml (renamed from tools/recovery_l10n/res/values-uz-rUZ/strings.xml) | 0 | ||||
-rw-r--r-- | tools/recovery_l10n/res/values/strings.xml | 1 | ||||
-rw-r--r-- | tools/recovery_l10n/src/com/android/recovery_l10n/Main.java | 23 | ||||
-rw-r--r-- | ui.cpp | 206 | ||||
-rw-r--r-- | ui.h | 38 | ||||
-rw-r--r-- | uncrypt/Android.mk | 14 | ||||
-rw-r--r-- | uncrypt/uncrypt.cpp | 258 | ||||
-rw-r--r-- | unique_fd.h | 62 | ||||
-rw-r--r-- | update_verifier/Android.mk | 16 | ||||
-rw-r--r-- | update_verifier/update_verifier.cpp | 249 | ||||
-rw-r--r-- | update_verifier/update_verifier.rc | 11 | ||||
-rw-r--r-- | updater/Android.mk | 136 | ||||
-rw-r--r-- | updater/blockimg.cpp | 1655 | ||||
-rw-r--r-- | updater/include/updater/blockimg.h (renamed from updater/blockimg.h) | 0 | ||||
-rw-r--r-- | updater/include/updater/install.h (renamed from updater/install.h) | 7 | ||||
-rw-r--r-- | updater/include/updater/updater.h (renamed from updater/updater.h) | 8 | ||||
-rw-r--r-- | updater/install.cpp | 2306 | ||||
-rw-r--r-- | updater/updater.cpp | 324 | ||||
-rw-r--r-- | verifier.cpp | 452 | ||||
-rw-r--r-- | verifier.h | 18 | ||||
-rw-r--r-- | wear_touch.cpp | 24 | ||||
-rw-r--r-- | wear_ui.cpp | 96 | ||||
-rw-r--r-- | wear_ui.h | 6 |
234 files changed, 13018 insertions, 14076 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..532278864 --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +BasedOnStyle: Google +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true + +ColumnLimit: 100 +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +IndentWidth: 2 +PointerAlignment: Left +TabWidth: 2 +UseTab: Never +PenaltyExcessCharacter: 32 + +Cpp11BracedListStyle: false diff --git a/Android.mk b/Android.mk index 589bff41f..408b146a9 100644 --- a/Android.mk +++ b/Android.mk @@ -14,18 +14,54 @@ LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) +# Needed by build/make/core/Makefile. +RECOVERY_API_VERSION := 3 +RECOVERY_FSTAB_VERSION := 2 +# libfusesideload (static library) +# =============================== +include $(CLEAR_VARS) LOCAL_SRC_FILES := fuse_sideload.cpp LOCAL_CLANG := true -LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter -Werror LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE - LOCAL_MODULE := libfusesideload +LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto +include $(BUILD_STATIC_LIBRARY) -LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto_static +# libmounts (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := mounts.cpp +LOCAL_CLANG := true +LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror +LOCAL_MODULE := libmounts include $(BUILD_STATIC_LIBRARY) +# librecovery (static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + install.cpp +LOCAL_CFLAGS := -Wno-unused-parameter -Werror +LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) + +ifeq ($(AB_OTA_UPDATER),true) + LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 +endif + +LOCAL_MODULE := librecovery +LOCAL_STATIC_LIBRARIES := \ + libminui \ + libvintf_recovery \ + libcrypto_utils \ + libcrypto \ + libbase + +include $(BUILD_STATIC_LIBRARY) + +# recovery (static executable) +# =============================== include $(CLEAR_VARS) LOCAL_SRC_FILES := \ @@ -33,9 +69,9 @@ LOCAL_SRC_FILES := \ asn1_decoder.cpp \ device.cpp \ fuse_sdcard_provider.cpp \ - install.cpp \ recovery.cpp \ roots.cpp \ + rotate_logs.cpp \ screen_ui.cpp \ ui.cpp \ verifier.cpp \ @@ -52,31 +88,34 @@ LOCAL_REQUIRED_MODULES := mkfs.f2fs endif endif -RECOVERY_API_VERSION := 3 -RECOVERY_FSTAB_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) -LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CFLAGS += -Wno-unused-parameter -Werror LOCAL_CLANG := true LOCAL_C_INCLUDES += \ system/vold \ - system/extras/ext4_utils \ system/core/adb \ LOCAL_STATIC_LIBRARIES := \ + librecovery \ libbatterymonitor \ libbootloader_message \ - libext4_utils_static \ - libsparse_static \ - libminzip \ + libext4_utils \ + libsparse \ + libziparchive \ + libotautil \ + libmounts \ libz \ - libmtdutils \ libminadbd \ libfusesideload \ libminui \ libpng \ libfs_mgr \ - libcrypto_static \ + libcrypto_utils \ + libcrypto \ + libvintf_recovery \ + libvintf \ + libtinyxml2 \ libbase \ libcutils \ libutils \ @@ -87,12 +126,6 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_HAL_STATIC_LIBRARIES := libhealthd -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) - LOCAL_CFLAGS += -DUSE_EXT4 - LOCAL_C_INCLUDES += system/extras/ext4_utils - LOCAL_STATIC_LIBRARIES += libext4_utils_static libz -endif - ifeq ($(AB_OTA_UPDATER),true) LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 endif @@ -114,7 +147,9 @@ include $(BUILD_EXECUTABLE) # recovery-persist (system partition dynamic executable run after /data mounts) # =============================== include $(CLEAR_VARS) -LOCAL_SRC_FILES := recovery-persist.cpp +LOCAL_SRC_FILES := \ + recovery-persist.cpp \ + rotate_logs.cpp LOCAL_MODULE := recovery-persist LOCAL_SHARED_LIBRARIES := liblog libbase LOCAL_CFLAGS := -Werror @@ -124,34 +159,38 @@ include $(BUILD_EXECUTABLE) # recovery-refresh (system partition dynamic executable run at init) # =============================== include $(CLEAR_VARS) -LOCAL_SRC_FILES := recovery-refresh.cpp +LOCAL_SRC_FILES := \ + recovery-refresh.cpp \ + rotate_logs.cpp LOCAL_MODULE := recovery-refresh -LOCAL_SHARED_LIBRARIES := liblog +LOCAL_SHARED_LIBRARIES := liblog libbase LOCAL_CFLAGS := -Werror LOCAL_INIT_RC := recovery-refresh.rc include $(BUILD_EXECUTABLE) -# All the APIs for testing +# libverifier (static library) +# =============================== include $(CLEAR_VARS) -LOCAL_CLANG := true LOCAL_MODULE := libverifier LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ asn1_decoder.cpp \ - verifier.cpp \ - ui.cpp -LOCAL_STATIC_LIBRARIES := libcrypto_static + verifier.cpp +LOCAL_STATIC_LIBRARIES := \ + libcrypto_utils \ + libcrypto \ + libbase +LOCAL_CFLAGS := -Werror include $(BUILD_STATIC_LIBRARY) include \ $(LOCAL_PATH)/applypatch/Android.mk \ $(LOCAL_PATH)/bootloader_message/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ - $(LOCAL_PATH)/minui/Android.mk \ - $(LOCAL_PATH)/minzip/Android.mk \ $(LOCAL_PATH)/minadbd/Android.mk \ - $(LOCAL_PATH)/mtdutils/Android.mk \ + $(LOCAL_PATH)/minui/Android.mk \ $(LOCAL_PATH)/otafault/Android.mk \ + $(LOCAL_PATH)/otautil/Android.mk \ $(LOCAL_PATH)/tests/Android.mk \ $(LOCAL_PATH)/tools/Android.mk \ $(LOCAL_PATH)/uncrypt/Android.mk \ @@ -27,3 +27,23 @@ Running the tests # Or 64-bit device adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test adb shell /data/nativetest64/recovery_component_test/recovery_component_test + +Running the manual tests +------------------------ + +`recovery-refresh` and `recovery-persist` executables exist only on systems without +/cache partition. And we need to follow special steps to run tests for them. + +- Execute the test on an A/B device first. The test should fail but it will log + some contents to pmsg. + +- Reboot the device immediately and run the test again. The test should save the + contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if + this file has expected contents. + +`ResourceTest` validates whether the png files are qualified as background text +image under recovery. + + 1. `adb sync data` to make sure the test-dir has the images to test. + 2. The test will automatically pickup and verify all `_text.png` files in + the test dir. diff --git a/adb_install.cpp b/adb_install.cpp index 4aed9d4b1..79b8df91b 100644 --- a/adb_install.cpp +++ b/adb_install.cpp @@ -26,17 +26,15 @@ #include <fcntl.h> #include "ui.h" -#include "cutils/properties.h" #include "install.h" #include "common.h" #include "adb_install.h" #include "minadbd/fuse_adb_provider.h" #include "fuse_sideload.h" -static RecoveryUI* ui = NULL; +#include <android-base/properties.h> -static void -set_usb_driver(bool enabled) { +static void set_usb_driver(RecoveryUI* ui, bool enabled) { int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY); if (fd < 0) { ui->Print("failed to open driver control: %s\n", strerror(errno)); @@ -50,19 +48,17 @@ set_usb_driver(bool enabled) { } } -static void -stop_adbd() { - property_set("ctl.stop", "adbd"); - set_usb_driver(false); +static void stop_adbd(RecoveryUI* ui) { + ui->Print("Stopping adbd...\n"); + android::base::SetProperty("ctl.stop", "adbd"); + set_usb_driver(ui, false); } - -static void -maybe_restart_adbd() { +static void maybe_restart_adbd(RecoveryUI* ui) { if (is_ro_debuggable()) { ui->Print("Restarting adbd...\n"); - set_usb_driver(true); - property_set("ctl.start", "adbd"); + set_usb_driver(ui, true); + android::base::SetProperty("ctl.start", "adbd"); } } @@ -70,14 +66,11 @@ maybe_restart_adbd() { // package, before timing out. #define ADB_INSTALL_TIMEOUT 300 -int -apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { +int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) { modified_flash = true; - ui = ui_; - - stop_adbd(); - set_usb_driver(true); + stop_adbd(ui); + set_usb_driver(ui, true); ui->Print("\n\nNow send the package you want to apply\n" "to the device with \"adb sideload <filename>\"...\n"); @@ -85,7 +78,7 @@ apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { pid_t child; if ((child = fork()) == 0) { execl("/sbin/recovery", "recovery", "--adbd", NULL); - _exit(-1); + _exit(EXIT_FAILURE); } // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host @@ -137,8 +130,8 @@ apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) { } } - set_usb_driver(false); - maybe_restart_adbd(); + set_usb_driver(ui, false); + maybe_restart_adbd(ui); return result; } diff --git a/applypatch/Android.mk b/applypatch/Android.mk index 887a570db..a7412d238 100644 --- a/applypatch/Android.mk +++ b/applypatch/Android.mk @@ -14,61 +14,169 @@ LOCAL_PATH := $(call my-dir) +# libapplypatch (static library) +# =============================== include $(CLEAR_VARS) - -LOCAL_CLANG := true -LOCAL_SRC_FILES := applypatch.cpp bspatch.cpp freecache.cpp imgpatch.cpp utils.cpp +LOCAL_SRC_FILES := \ + applypatch.cpp \ + bspatch.cpp \ + freecache.cpp \ + imgpatch.cpp LOCAL_MODULE := libapplypatch LOCAL_MODULE_TAGS := eng -LOCAL_C_INCLUDES += bootable/recovery -LOCAL_STATIC_LIBRARIES += libbase libotafault libmtdutils libcrypto_static libbz libz - +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + bootable/recovery +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_STATIC_LIBRARIES := \ + libotafault \ + libbase \ + libcrypto \ + libbspatch \ + libbz \ + libz +LOCAL_CFLAGS := \ + -DZLIB_CONST \ + -Werror include $(BUILD_STATIC_LIBRARY) +# libimgpatch (static library) +# =============================== include $(CLEAR_VARS) - -LOCAL_CLANG := true -LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp +LOCAL_SRC_FILES := \ + bspatch.cpp \ + imgpatch.cpp LOCAL_MODULE := libimgpatch -LOCAL_C_INCLUDES += bootable/recovery +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + bootable/recovery LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include -LOCAL_STATIC_LIBRARIES += libcrypto_static libbz libz - +LOCAL_STATIC_LIBRARIES := \ + libcrypto \ + libbspatch \ + libbase \ + libbz \ + libz +LOCAL_CFLAGS := \ + -DZLIB_CONST \ + -Werror include $(BUILD_STATIC_LIBRARY) -ifeq ($(HOST_OS),linux) +# libimgpatch (host static library) +# =============================== include $(CLEAR_VARS) - -LOCAL_CLANG := true -LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp +LOCAL_SRC_FILES := \ + bspatch.cpp \ + imgpatch.cpp LOCAL_MODULE := libimgpatch -LOCAL_C_INCLUDES += bootable/recovery +LOCAL_MODULE_HOST_OS := linux +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + bootable/recovery LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include -LOCAL_STATIC_LIBRARIES += libcrypto_static libbz libz - +LOCAL_STATIC_LIBRARIES := \ + libcrypto \ + libbspatch \ + libbase \ + libbz \ + libz +LOCAL_CFLAGS := \ + -DZLIB_CONST \ + -Werror include $(BUILD_HOST_STATIC_LIBRARY) -endif # HOST_OS == linux +# libapplypatch_modes (static library) +# =============================== include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + applypatch_modes.cpp +LOCAL_MODULE := libapplypatch_modes +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_STATIC_LIBRARIES := \ + libapplypatch \ + libbase \ + libedify \ + libcrypto +LOCAL_CFLAGS := -Werror +include $(BUILD_STATIC_LIBRARY) -LOCAL_CLANG := true -LOCAL_SRC_FILES := main.cpp +# applypatch (target executable) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := applypatch_main.cpp LOCAL_MODULE := applypatch -LOCAL_C_INCLUDES += bootable/recovery -LOCAL_STATIC_LIBRARIES += libapplypatch libbase libotafault libmtdutils libcrypto_static libbz \ - libedify \ - -LOCAL_SHARED_LIBRARIES += libz libcutils libc - +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_STATIC_LIBRARIES := \ + libapplypatch_modes \ + libapplypatch \ + libbase \ + libedify \ + libotafault \ + libcrypto \ + libbspatch \ + libbz +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libz \ + libcutils +LOCAL_CFLAGS := -Werror include $(BUILD_EXECUTABLE) +libimgdiff_src_files := imgdiff.cpp + +# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64. +libimgdiff_cflags := \ + -Werror \ + -D_FILE_OFFSET_BITS=64 + +libimgdiff_static_libraries := \ + libbsdiff \ + libdivsufsort \ + libdivsufsort64 \ + libziparchive \ + libutils \ + liblog \ + libbase \ + libz + +# libimgdiff (static library) +# =============================== include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + $(libimgdiff_src_files) +LOCAL_MODULE := libimgdiff +LOCAL_CFLAGS := \ + $(libimgdiff_cflags) +LOCAL_STATIC_LIBRARIES := \ + $(libimgdiff_static_libraries) +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +include $(BUILD_STATIC_LIBRARY) -LOCAL_CLANG := true -LOCAL_SRC_FILES := imgdiff.cpp utils.cpp bsdiff.cpp -LOCAL_MODULE := imgdiff -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_C_INCLUDES += external/zlib external/bzip2 -LOCAL_STATIC_LIBRARIES += libz libbz +# libimgdiff (host static library) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + $(libimgdiff_src_files) +LOCAL_MODULE := libimgdiff +LOCAL_CFLAGS := \ + $(libimgdiff_cflags) +LOCAL_STATIC_LIBRARIES := \ + $(libimgdiff_static_libraries) +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +include $(BUILD_HOST_STATIC_LIBRARY) +# imgdiff (host static executable) +# =============================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES := imgdiff_main.cpp +LOCAL_MODULE := imgdiff +LOCAL_CFLAGS := -Werror +LOCAL_STATIC_LIBRARIES := \ + libimgdiff \ + $(libimgdiff_static_libraries) \ + libbz include $(BUILD_HOST_EXECUTABLE) diff --git a/applypatch/Makefile b/applypatch/Makefile new file mode 100644 index 000000000..fb4984303 --- /dev/null +++ b/applypatch/Makefile @@ -0,0 +1,33 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file is for building imgdiff in Chrome OS. + +CPPFLAGS += -iquote.. -Iinclude +CXXFLAGS += -std=c++11 -O3 -Wall -Werror +LDLIBS += -lbz2 -lz + +.PHONY: all clean + +all: imgdiff libimgpatch.a + +clean: + rm -f *.o imgdiff libimgpatch.a + +imgdiff: imgdiff.o bsdiff.o utils.o + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDLIBS) -o $@ $^ + +libimgpatch.a utils.o: CXXFLAGS += -fPIC +libimgpatch.a: imgpatch.o bspatch.o utils.o + ${AR} rcs $@ $^ diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 7985fc0c6..7be3fdbde 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "applypatch/applypatch.h" + #include <errno.h> #include <fcntl.h> #include <libgen.h> @@ -27,70 +29,57 @@ #include <memory> #include <string> +#include <utility> +#include <vector> +#include <android-base/logging.h> +#include <android-base/parseint.h> #include <android-base/strings.h> +#include <openssl/sha.h> -#include "openssl/sha.h" -#include "applypatch.h" -#include "mtdutils/mtdutils.h" #include "edify/expr.h" #include "ota_io.h" #include "print_sha1.h" -static int LoadPartitionContents(const char* filename, FileContents* file); +static int LoadPartitionContents(const std::string& filename, FileContents* file); static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); -static int GenerateTarget(FileContents* source_file, - const Value* source_patch_value, - FileContents* copy_file, - const Value* copy_patch_value, - const char* source_filename, - const char* target_filename, - const uint8_t target_sha1[SHA_DIGEST_LENGTH], - size_t target_size, - const Value* bonus_data); - -static bool mtd_partitions_scanned = false; - -// Read a file into memory; store the file contents and associated -// metadata in *file. -// +static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch, + const std::string& target_filename, + const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data); + +// Read a file into memory; store the file contents and associated metadata in *file. // Return 0 on success. int LoadFileContents(const char* filename, FileContents* file) { - // A special 'filename' beginning with "MTD:" or "EMMC:" means to - // load the contents of a partition. - if (strncmp(filename, "MTD:", 4) == 0 || - strncmp(filename, "EMMC:", 5) == 0) { - return LoadPartitionContents(filename, file); - } + // A special 'filename' beginning with "EMMC:" means to load the contents of a partition. + if (strncmp(filename, "EMMC:", 5) == 0) { + return LoadPartitionContents(filename, file); + } - if (stat(filename, &file->st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return -1; - } + if (stat(filename, &file->st) == -1) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } - std::vector<unsigned char> data(file->st.st_size); - FILE* f = ota_fopen(filename, "rb"); - if (f == NULL) { - printf("failed to open \"%s\": %s\n", filename, strerror(errno)); - return -1; - } + std::vector<unsigned char> data(file->st.st_size); + unique_file f(ota_fopen(filename, "rb")); + if (!f) { + printf("failed to open \"%s\": %s\n", filename, strerror(errno)); + return -1; + } - size_t bytes_read = ota_fread(data.data(), 1, data.size(), f); - if (bytes_read != data.size()) { - printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, data.size()); - ota_fclose(f); - return -1; - } - ota_fclose(f); - file->data = std::move(data); - SHA1(file->data.data(), file->data.size(), file->sha1); - return 0; + size_t bytes_read = ota_fread(data.data(), 1, data.size(), f.get()); + if (bytes_read != data.size()) { + printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size()); + return -1; + } + file->data = std::move(data); + SHA1(file->data.data(), file->data.size(), file->sha1); + return 0; } -// Load the contents of an MTD or EMMC partition into the provided +// Load the contents of an EMMC partition into the provided // FileContents. filename should be a string of the form -// "MTD:<partition_name>:<size_1>:<sha1_1>:<size_2>:<sha1_2>:..." (or -// "EMMC:<partition_device>:..."). The smallest size_n bytes for +// "EMMC:<partition_device>:...". The smallest size_n bytes for // which that prefix of the partition contents has the corresponding // sha1 hash will be loaded. It is acceptable for a size value to be // repeated with different sha1s. Will return 0 on success. @@ -102,380 +91,267 @@ int LoadFileContents(const char* filename, FileContents* file) { // "end-of-file" marker), so the caller must specify the possible // lengths and the hash of the data, and we'll do the load expecting // to find one of those hashes. -enum PartitionType { MTD, EMMC }; - -static int LoadPartitionContents(const char* filename, FileContents* file) { - std::string copy(filename); - std::vector<std::string> pieces = android::base::Split(copy, ":"); - if (pieces.size() < 4 || pieces.size() % 2 != 0) { - printf("LoadPartitionContents called with bad filename (%s)\n", filename); - return -1; - } - - enum PartitionType type; - if (pieces[0] == "MTD") { - type = MTD; - } else if (pieces[0] == "EMMC") { - type = EMMC; - } else { - printf("LoadPartitionContents called with bad filename (%s)\n", filename); +static int LoadPartitionContents(const std::string& filename, FileContents* file) { + std::vector<std::string> pieces = android::base::Split(filename, ":"); + if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") { + printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str()); + return -1; + } + + size_t pair_count = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename + std::vector<std::pair<size_t, std::string>> pairs; + for (size_t i = 0; i < pair_count; ++i) { + size_t size; + if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) { + printf("LoadPartitionContents called with bad size \"%s\"\n", pieces[i * 2 + 2].c_str()); + return -1; + } + pairs.push_back({ size, pieces[i * 2 + 3] }); + } + + // Sort the pairs array so that they are in order of increasing size. + std::sort(pairs.begin(), pairs.end()); + + const char* partition = pieces[1].c_str(); + unique_file dev(ota_fopen(partition, "rb")); + if (!dev) { + printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno)); + return -1; + } + + SHA_CTX sha_ctx; + SHA1_Init(&sha_ctx); + + // Allocate enough memory to hold the largest size. + std::vector<unsigned char> buffer(pairs[pair_count - 1].first); + unsigned char* buffer_ptr = buffer.data(); + size_t buffer_size = 0; // # bytes read so far + bool found = false; + + for (const auto& pair : pairs) { + size_t current_size = pair.first; + const std::string& current_sha1 = pair.second; + + // Read enough additional bytes to get us up to the next size. (Again, + // we're trying the possibilities in order of increasing size). + size_t next = current_size - buffer_size; + if (next > 0) { + size_t read = ota_fread(buffer_ptr, 1, next, dev.get()); + if (next != read) { + printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition); return -1; + } + SHA1_Update(&sha_ctx, buffer_ptr, read); + buffer_size += read; + buffer_ptr += read; } - const char* partition = pieces[1].c_str(); - size_t pairs = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename - std::vector<size_t> index(pairs); - std::vector<size_t> size(pairs); - std::vector<std::string> sha1sum(pairs); + // Duplicate the SHA context and finalize the duplicate so we can + // check it against this pair's expected hash. + SHA_CTX temp_ctx; + memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); + uint8_t sha_so_far[SHA_DIGEST_LENGTH]; + SHA1_Final(sha_so_far, &temp_ctx); - for (size_t i = 0; i < pairs; ++i) { - size[i] = strtol(pieces[i*2+2].c_str(), NULL, 10); - if (size[i] == 0) { - printf("LoadPartitionContents called with bad size (%s)\n", filename); - return -1; - } - sha1sum[i] = pieces[i*2+3].c_str(); - index[i] = i; + uint8_t parsed_sha[SHA_DIGEST_LENGTH]; + if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) { + printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str()); + return -1; } - // Sort the index[] array so it indexes the pairs in order of increasing size. - sort(index.begin(), index.end(), - [&](const size_t& i, const size_t& j) { - return (size[i] < size[j]); - } - ); - - MtdReadContext* ctx = NULL; - FILE* dev = NULL; - - switch (type) { - case MTD: { - if (!mtd_partitions_scanned) { - mtd_scan_partitions(); - mtd_partitions_scanned = true; - } - - const MtdPartition* mtd = mtd_find_partition_by_name(partition); - if (mtd == NULL) { - printf("mtd partition \"%s\" not found (loading %s)\n", partition, filename); - return -1; - } - - ctx = mtd_read_partition(mtd); - if (ctx == NULL) { - printf("failed to initialize read of mtd partition \"%s\"\n", partition); - return -1; - } - break; - } - - case EMMC: - dev = ota_fopen(partition, "rb"); - if (dev == NULL) { - printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno)); - return -1; - } + if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) { + // We have a match. Stop reading the partition; we'll return the data we've read so far. + printf("partition read matched size %zu SHA-1 %s\n", current_size, current_sha1.c_str()); + found = true; + break; } + } - SHA_CTX sha_ctx; - SHA1_Init(&sha_ctx); - uint8_t parsed_sha[SHA_DIGEST_LENGTH]; - - // Allocate enough memory to hold the largest size. - std::vector<unsigned char> data(size[index[pairs-1]]); - char* p = reinterpret_cast<char*>(data.data()); - size_t data_size = 0; // # bytes read so far - bool found = false; - - for (size_t i = 0; i < pairs; ++i) { - // Read enough additional bytes to get us up to the next size. (Again, - // we're trying the possibilities in order of increasing size). - size_t next = size[index[i]] - data_size; - if (next > 0) { - size_t read = 0; - switch (type) { - case MTD: - read = mtd_read_data(ctx, p, next); - break; - - case EMMC: - read = ota_fread(p, 1, next, dev); - break; - } - if (next != read) { - printf("short read (%zu bytes of %zu) for partition \"%s\"\n", - read, next, partition); - return -1; - } - SHA1_Update(&sha_ctx, p, read); - data_size += read; - p += read; - } - - // Duplicate the SHA context and finalize the duplicate so we can - // check it against this pair's expected hash. - SHA_CTX temp_ctx; - memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); - uint8_t sha_so_far[SHA_DIGEST_LENGTH]; - SHA1_Final(sha_so_far, &temp_ctx); - - if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) { - printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename); - return -1; - } - - if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) { - // we have a match. stop reading the partition; we'll return - // the data we've read so far. - printf("partition read matched size %zu sha %s\n", - size[index[i]], sha1sum[index[i]].c_str()); - found = true; - break; - } - } + if (!found) { + // Ran off the end of the list of (size, sha1) pairs without finding a match. + printf("contents of partition \"%s\" didn't match %s\n", partition, filename.c_str()); + return -1; + } - switch (type) { - case MTD: - mtd_read_close(ctx); - break; + SHA1_Final(file->sha1, &sha_ctx); - case EMMC: - ota_fclose(dev); - break; - } + buffer.resize(buffer_size); + file->data = std::move(buffer); + // Fake some stat() info. + file->st.st_mode = 0644; + file->st.st_uid = 0; + file->st.st_gid = 0; + return 0; +} - if (!found) { - // Ran off the end of the list of (size,sha1) pairs without finding a match. - printf("contents of partition \"%s\" didn't match %s\n", partition, filename); - return -1; - } +// Save the contents of the given FileContents object under the given +// filename. Return 0 on success. +int SaveFileContents(const char* filename, const FileContents* file) { + unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR)); + if (fd == -1) { + printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); + return -1; + } - SHA1_Final(file->sha1, &sha_ctx); + ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); + if (bytes_written != static_cast<ssize_t>(file->data.size())) { + printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written, + file->data.size(), strerror(errno)); + return -1; + } + if (ota_fsync(fd) != 0) { + printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (ota_close(fd) != 0) { + printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } - data.resize(data_size); - file->data = std::move(data); - // Fake some stat() info. - file->st.st_mode = 0644; - file->st.st_uid = 0; - file->st.st_gid = 0; + if (chmod(filename, file->st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } - return 0; + return 0; } +// Write a memory buffer to 'target' partition, a string of the form +// "EMMC:<partition_device>[:...]". The target name +// might contain multiple colons, but WriteToPartition() only uses the first +// two and ignores the rest. Return 0 on success. +int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) { + std::vector<std::string> pieces = android::base::Split(target, ":"); + if (pieces.size() < 2 || pieces[0] != "EMMC") { + printf("WriteToPartition called with bad target (%s)\n", target.c_str()); + return -1; + } -// Save the contents of the given FileContents object under the given -// filename. Return 0 on success. -int SaveFileContents(const char* filename, const FileContents* file) { - int fd = ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); - if (fd < 0) { - printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); + const char* partition = pieces[1].c_str(); + unique_fd fd(ota_open(partition, O_RDWR)); + if (fd == -1) { + printf("failed to open %s: %s\n", partition, strerror(errno)); + return -1; + } + + size_t start = 0; + bool success = false; + for (size_t attempt = 0; attempt < 2; ++attempt) { + if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { + printf("failed seek on %s: %s\n", partition, strerror(errno)); + return -1; + } + while (start < len) { + size_t to_write = len - start; + if (to_write > 1 << 20) to_write = 1 << 20; + + ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data + start, to_write)); + if (written == -1) { + printf("failed write writing to %s: %s\n", partition, strerror(errno)); return -1; + } + start += written; } - ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); - if (bytes_written != static_cast<ssize_t>(file->data.size())) { - printf("short write of \"%s\" (%zd bytes of %zu) (%s)\n", - filename, bytes_written, file->data.size(), strerror(errno)); - ota_close(fd); - return -1; - } if (ota_fsync(fd) != 0) { - printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; + printf("failed to sync to %s: %s\n", partition, strerror(errno)); + return -1; } if (ota_close(fd) != 0) { - printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; + printf("failed to close %s: %s\n", partition, strerror(errno)); + return -1; } - if (chmod(filename, file->st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; + fd.reset(ota_open(partition, O_RDONLY)); + if (fd == -1) { + printf("failed to reopen %s for verify: %s\n", partition, strerror(errno)); + return -1; } - return 0; -} + // Drop caches so our subsequent verification read won't just be reading the cache. + sync(); + unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY)); + if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) { + printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); + } else { + printf(" caches dropped\n"); + } + ota_close(dc); + sleep(1); + + // Verify. + if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { + printf("failed to seek back to beginning of %s: %s\n", partition, strerror(errno)); + return -1; + } + + unsigned char buffer[4096]; + start = len; + for (size_t p = 0; p < len; p += sizeof(buffer)) { + size_t to_read = len - p; + if (to_read > sizeof(buffer)) { + to_read = sizeof(buffer); + } + + size_t so_far = 0; + while (so_far < to_read) { + ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far)); + if (read_count == -1) { + printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno)); + return -1; + } else if (read_count == 0) { + printf("verify read reached unexpected EOF, %s at %zu\n", partition, p); + return -1; + } + if (static_cast<size_t>(read_count) < to_read) { + printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read); + } + so_far += read_count; + } -// Write a memory buffer to 'target' partition, a string of the form -// "MTD:<partition>[:...]" or "EMMC:<partition_device>[:...]". The target name -// might contain multiple colons, but WriteToPartition() only uses the first -// two and ignores the rest. Return 0 on success. -int WriteToPartition(const unsigned char* data, size_t len, const char* target) { - std::string copy(target); - std::vector<std::string> pieces = android::base::Split(copy, ":"); + if (memcmp(buffer, data + p, to_read) != 0) { + printf("verification failed starting at %zu\n", p); + start = p; + break; + } + } - if (pieces.size() < 2) { - printf("WriteToPartition called with bad target (%s)\n", target); - return -1; + if (start == len) { + printf("verification read succeeded (attempt %zu)\n", attempt + 1); + success = true; + break; } - enum PartitionType type; - if (pieces[0] == "MTD") { - type = MTD; - } else if (pieces[0] == "EMMC") { - type = EMMC; - } else { - printf("WriteToPartition called with bad target (%s)\n", target); - return -1; + if (ota_close(fd) != 0) { + printf("failed to close %s: %s\n", partition, strerror(errno)); + return -1; } - const char* partition = pieces[1].c_str(); - - switch (type) { - case MTD: { - if (!mtd_partitions_scanned) { - mtd_scan_partitions(); - mtd_partitions_scanned = true; - } - - const MtdPartition* mtd = mtd_find_partition_by_name(partition); - if (mtd == NULL) { - printf("mtd partition \"%s\" not found for writing\n", partition); - return -1; - } - - MtdWriteContext* ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - printf("failed to init mtd partition \"%s\" for writing\n", partition); - return -1; - } - - size_t written = mtd_write_data(ctx, reinterpret_cast<const char*>(data), len); - if (written != len) { - printf("only wrote %zu of %zu bytes to MTD %s\n", written, len, partition); - mtd_write_close(ctx); - return -1; - } - - if (mtd_erase_blocks(ctx, -1) < 0) { - printf("error finishing mtd write of %s\n", partition); - mtd_write_close(ctx); - return -1; - } - - if (mtd_write_close(ctx)) { - printf("error closing mtd write of %s\n", partition); - return -1; - } - break; - } - case EMMC: { - size_t start = 0; - bool success = false; - int fd = ota_open(partition, O_RDWR | O_SYNC); - if (fd < 0) { - printf("failed to open %s: %s\n", partition, strerror(errno)); - return -1; - } - - for (size_t attempt = 0; attempt < 2; ++attempt) { - if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { - printf("failed seek on %s: %s\n", partition, strerror(errno)); - return -1; - } - while (start < len) { - size_t to_write = len - start; - if (to_write > 1<<20) to_write = 1<<20; - - ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data+start, to_write)); - if (written == -1) { - printf("failed write writing to %s: %s\n", partition, strerror(errno)); - return -1; - } - start += written; - } - if (ota_fsync(fd) != 0) { - printf("failed to sync to %s (%s)\n", partition, strerror(errno)); - return -1; - } - if (ota_close(fd) != 0) { - printf("failed to close %s (%s)\n", partition, strerror(errno)); - return -1; - } - fd = ota_open(partition, O_RDONLY); - if (fd < 0) { - printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno)); - return -1; - } - - // Drop caches so our subsequent verification read - // won't just be reading the cache. - sync(); - int dc = ota_open("/proc/sys/vm/drop_caches", O_WRONLY); - if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) { - printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); - } else { - printf(" caches dropped\n"); - } - ota_close(dc); - sleep(1); - - // verify - if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { - printf("failed to seek back to beginning of %s: %s\n", - partition, strerror(errno)); - return -1; - } - unsigned char buffer[4096]; - start = len; - for (size_t p = 0; p < len; p += sizeof(buffer)) { - size_t to_read = len - p; - if (to_read > sizeof(buffer)) { - to_read = sizeof(buffer); - } - - size_t so_far = 0; - while (so_far < to_read) { - ssize_t read_count = - TEMP_FAILURE_RETRY(ota_read(fd, buffer+so_far, to_read-so_far)); - if (read_count == -1) { - printf("verify read error %s at %zu: %s\n", - partition, p, strerror(errno)); - return -1; - } - if (static_cast<size_t>(read_count) < to_read) { - printf("short verify read %s at %zu: %zd %zu %s\n", - partition, p, read_count, to_read, strerror(errno)); - } - so_far += read_count; - } - - if (memcmp(buffer, data+p, to_read) != 0) { - printf("verification failed starting at %zu\n", p); - start = p; - break; - } - } - - if (start == len) { - printf("verification read succeeded (attempt %zu)\n", attempt+1); - success = true; - break; - } - } - - if (!success) { - printf("failed to verify after all attempts\n"); - return -1; - } - - if (ota_close(fd) != 0) { - printf("error closing %s (%s)\n", partition, strerror(errno)); - return -1; - } - sync(); - break; - } + fd.reset(ota_open(partition, O_RDWR)); + if (fd == -1) { + printf("failed to reopen %s for retry write && verify: %s\n", partition, strerror(errno)); + return -1; } + } - return 0; -} + if (!success) { + printf("failed to verify after all attempts\n"); + return -1; + } + + if (ota_close(fd) == -1) { + printf("error closing %s: %s\n", partition, strerror(errno)); + return -1; + } + sync(); + return 0; +} // Take a string 'str' of 40 hex digits and parse it into the 20 // byte array 'digest'. 'str' may contain only the digest or be of @@ -509,52 +385,47 @@ int ParseSha1(const char* str, uint8_t* digest) { // Search an array of sha1 strings for one matching the given sha1. // Return the index of the match on success, or -1 if no match is // found. -int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, - int num_patches) { +static int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) { + for (size_t i = 0; i < patch_sha1_str.size(); ++i) { uint8_t patch_sha1[SHA_DIGEST_LENGTH]; - for (int i = 0; i < num_patches; ++i) { - if (ParseSha1(patch_sha1_str[i], patch_sha1) == 0 && - memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) { - return i; - } + if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 && + memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) { + return i; } - return -1; + } + return -1; } // Returns 0 if the contents of the file (argv[2]) or the cached file // match any of the sha1's on the command line (argv[3:]). Returns // nonzero otherwise. -int applypatch_check(const char* filename, int num_patches, - char** const patch_sha1_str) { - FileContents file; - - // It's okay to specify no sha1s; the check will pass if the - // LoadFileContents is successful. (Useful for reading - // partitions, where the filename encodes the sha1s; no need to - // check them twice.) - if (LoadFileContents(filename, &file) != 0 || - (num_patches > 0 && - FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) { - printf("file \"%s\" doesn't have any of expected " - "sha1 sums; checking cache\n", filename); - - // If the source file is missing or corrupted, it might be because - // we were killed in the middle of patching it. A copy of it - // should have been made in CACHE_TEMP_SOURCE. If that file - // exists and matches the sha1 we're looking for, the check still - // passes. - - if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) { - printf("failed to load cache file\n"); - return 1; - } - - if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) { - printf("cache bits don't match any sha1 for \"%s\"\n", filename); - return 1; - } - } - return 0; +int applypatch_check(const char* filename, const std::vector<std::string>& patch_sha1_str) { + FileContents file; + + // It's okay to specify no sha1s; the check will pass if the + // LoadFileContents is successful. (Useful for reading + // partitions, where the filename encodes the sha1s; no need to + // check them twice.) + if (LoadFileContents(filename, &file) != 0 || + (!patch_sha1_str.empty() && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) { + printf("file \"%s\" doesn't have any of expected sha1 sums; checking cache\n", filename); + + // If the source file is missing or corrupted, it might be because + // we were killed in the middle of patching it. A copy of it + // should have been made in CACHE_TEMP_SOURCE. If that file + // exists and matches the sha1 we're looking for, the check still + // passes. + if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) { + printf("failed to load cache file\n"); + return 1; + } + + if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) { + printf("cache bits don't match any sha1 for \"%s\"\n", filename); + return 1; + } + } + return 0; } int ShowLicenses() { @@ -596,116 +467,97 @@ size_t FreeSpaceForFile(const char* filename) { int CacheSizeCheck(size_t bytes) { if (MakeFreeSpaceOnCache(bytes) < 0) { - printf("unable to make %ld bytes available on /cache\n", (long)bytes); + printf("unable to make %zu bytes available on /cache\n", bytes); return 1; } else { return 0; } } -// This function applies binary patches to files in a way that is safe -// (the original file is not touched until we have the desired -// replacement for it) and idempotent (it's okay to run this program -// multiple times). +// This function applies binary patches to EMMC target files in a way that is safe (the original +// file is not touched until we have the desired replacement for it) and idempotent (it's okay to +// run this program multiple times). // -// - if the sha1 hash of <target_filename> is <target_sha1_string>, -// does nothing and exits successfully. +// - If the SHA-1 hash of <target_filename> is <target_sha1_string>, does nothing and exits +// successfully. // -// - otherwise, if the sha1 hash of <source_filename> is one of the -// entries in <patch_sha1_str>, the corresponding patch from -// <patch_data> (which must be a VAL_BLOB) is applied to produce a -// new file (the type of patch is automatically detected from the -// blob data). If that new file has sha1 hash <target_sha1_str>, -// moves it to replace <target_filename>, and exits successfully. -// Note that if <source_filename> and <target_filename> are not the -// same, <source_filename> is NOT deleted on success. -// <target_filename> may be the string "-" to mean "the same as -// source_filename". +// - Otherwise, if the SHA-1 hash of <source_filename> is one of the entries in <patch_sha1_str>, +// the corresponding patch from <patch_data> (which must be a VAL_BLOB) is applied to produce a +// new file (the type of patch is automatically detected from the blob data). If that new file +// has SHA-1 hash <target_sha1_str>, moves it to replace <target_filename>, and exits +// successfully. Note that if <source_filename> and <target_filename> are not the same, +// <source_filename> is NOT deleted on success. <target_filename> may be the string "-" to mean +// "the same as <source_filename>". // -// - otherwise, or if any error is encountered, exits with non-zero -// status. +// - Otherwise, or if any error is encountered, exits with non-zero status. // -// <source_filename> may refer to a partition to read the source data. -// See the comments for the LoadPartitionContents() function above -// for the format of such a filename. - -int applypatch(const char* source_filename, - const char* target_filename, - const char* target_sha1_str, - size_t target_size, - int num_patches, - char** const patch_sha1_str, - Value** patch_data, - Value* bonus_data) { - printf("patch %s: ", source_filename); - - if (target_filename[0] == '-' && target_filename[1] == '\0') { - target_filename = source_filename; - } - - uint8_t target_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(target_sha1_str, target_sha1) != 0) { - printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); - return 1; - } - - FileContents copy_file; - FileContents source_file; - const Value* source_patch_value = NULL; - const Value* copy_patch_value = NULL; - - // We try to load the target file into the source_file object. - if (LoadFileContents(target_filename, &source_file) == 0) { - if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { - // The early-exit case: the patch was already applied, this file - // has the desired hash, nothing for us to do. - printf("already %s\n", short_sha1(target_sha1).c_str()); - return 0; - } - } - - if (source_file.data.empty() || - (target_filename != source_filename && - strcmp(target_filename, source_filename) != 0)) { - // Need to load the source file: either we failed to load the - // target file, or we did but it's different from the source file. - source_file.data.clear(); - LoadFileContents(source_filename, &source_file); - } - - if (!source_file.data.empty()) { - int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str, num_patches); - if (to_use >= 0) { - source_patch_value = patch_data[to_use]; - } - } - - if (source_patch_value == NULL) { - source_file.data.clear(); - printf("source file is bad; trying copy\n"); - - if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { - // fail. - printf("failed to read copy file\n"); - return 1; - } - - int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str, num_patches); - if (to_use >= 0) { - copy_patch_value = patch_data[to_use]; - } - - if (copy_patch_value == NULL) { - // fail. - printf("copy file doesn't match source SHA-1s either\n"); - return 1; - } - } - - return GenerateTarget(&source_file, source_patch_value, - ©_file, copy_patch_value, - source_filename, target_filename, - target_sha1, target_size, bonus_data); +// <source_filename> must refer to an EMMC partition to read the source data. See the comments for +// the LoadPartitionContents() function above for the format of such a filename. <target_size> has +// become obsolete since we have dropped the support for patching non-EMMC targets (EMMC targets +// have the size embedded in the filename). +int applypatch(const char* source_filename, const char* target_filename, + const char* target_sha1_str, size_t target_size __unused, + const std::vector<std::string>& patch_sha1_str, + const std::vector<std::unique_ptr<Value>>& patch_data, const Value* bonus_data) { + printf("patch %s: ", source_filename); + + if (target_filename[0] == '-' && target_filename[1] == '\0') { + target_filename = source_filename; + } + + if (strncmp(target_filename, "EMMC:", 5) != 0) { + printf("Supporting patching EMMC targets only.\n"); + return 1; + } + + uint8_t target_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + // We try to load the target file into the source_file object. + FileContents source_file; + if (LoadFileContents(target_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { + // The early-exit case: the patch was already applied, this file has the desired hash, nothing + // for us to do. + printf("already %s\n", short_sha1(target_sha1).c_str()); + return 0; + } + } + + if (source_file.data.empty() || + (target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) { + // Need to load the source file: either we failed to load the target file, or we did but it's + // different from the expected. + source_file.data.clear(); + LoadFileContents(source_filename, &source_file); + } + + if (!source_file.data.empty()) { + int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str); + if (to_use != -1) { + return GenerateTarget(source_file, patch_data[to_use], target_filename, target_sha1, + bonus_data); + } + } + + printf("source file is bad; trying copy\n"); + + FileContents copy_file; + if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { + printf("failed to read copy file\n"); + return 1; + } + + int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str); + if (to_use == -1) { + printf("copy file doesn't match source SHA-1s either\n"); + return 1; + } + + return GenerateTarget(copy_file, patch_data[to_use], target_filename, target_sha1, bonus_data); } /* @@ -717,278 +569,124 @@ int applypatch(const char* source_filename, */ int applypatch_flash(const char* source_filename, const char* target_filename, const char* target_sha1_str, size_t target_size) { - printf("flash %s: ", target_filename); - - uint8_t target_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(target_sha1_str, target_sha1) != 0) { - printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); - return 1; - } - - FileContents source_file; - std::string target_str(target_filename); - - std::vector<std::string> pieces = android::base::Split(target_str, ":"); - if (pieces.size() != 2 || (pieces[0] != "MTD" && pieces[0] != "EMMC")) { - printf("invalid target name \"%s\"", target_filename); - return 1; - } - - // Load the target into the source_file object to see if already applied. - pieces.push_back(std::to_string(target_size)); - pieces.push_back(target_sha1_str); - std::string fullname = android::base::Join(pieces, ':'); - if (LoadPartitionContents(fullname.c_str(), &source_file) == 0 && - memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { - // The early-exit case: the image was already applied, this partition - // has the desired hash, nothing for us to do. - printf("already %s\n", short_sha1(target_sha1).c_str()); - return 0; - } - - if (LoadFileContents(source_filename, &source_file) == 0) { - if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { - // The source doesn't have desired checksum. - printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); - printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), - short_sha1(source_file.sha1).c_str()); - return 1; - } - } - - if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { - printf("write of copied data to %s failed\n", target_filename); - return 1; - } + printf("flash %s: ", target_filename); + + uint8_t target_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + std::string target_str(target_filename); + std::vector<std::string> pieces = android::base::Split(target_str, ":"); + if (pieces.size() != 2 || pieces[0] != "EMMC") { + printf("invalid target name \"%s\"", target_filename); + return 1; + } + + // Load the target into the source_file object to see if already applied. + pieces.push_back(std::to_string(target_size)); + pieces.push_back(target_sha1_str); + std::string fullname = android::base::Join(pieces, ':'); + FileContents source_file; + if (LoadPartitionContents(fullname, &source_file) == 0 && + memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { + // The early-exit case: the image was already applied, this partition + // has the desired hash, nothing for us to do. + printf("already %s\n", short_sha1(target_sha1).c_str()); return 0; + } + + if (LoadFileContents(source_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { + // The source doesn't have desired checksum. + printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); + printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), + short_sha1(source_file.sha1).c_str()); + return 1; + } + } + + if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { + printf("write of copied data to %s failed\n", target_filename); + return 1; + } + return 0; } -static int GenerateTarget(FileContents* source_file, - const Value* source_patch_value, - FileContents* copy_file, - const Value* copy_patch_value, - const char* source_filename, - const char* target_filename, - const uint8_t target_sha1[SHA_DIGEST_LENGTH], - size_t target_size, - const Value* bonus_data) { - int retry = 1; - SHA_CTX ctx; - std::string memory_sink_str; - FileContents* source_to_use; - int made_copy = 0; - - bool target_is_partition = (strncmp(target_filename, "MTD:", 4) == 0 || - strncmp(target_filename, "EMMC:", 5) == 0); - const std::string tmp_target_filename = std::string(target_filename) + ".patch"; - - // assume that target_filename (eg "/system/app/Foo.apk") is located - // on the same filesystem as its top-level directory ("/system"). - // We need something that exists for calling statfs(). - std::string target_fs = target_filename; - auto slash_pos = target_fs.find('/', 1); - if (slash_pos != std::string::npos) { - target_fs.resize(slash_pos); - } - - const Value* patch; - if (source_patch_value != NULL) { - source_to_use = source_file; - patch = source_patch_value; - } else { - source_to_use = copy_file; - patch = copy_patch_value; - } - if (patch->type != VAL_BLOB) { - printf("patch is not a blob\n"); - return 1; - } - char* header = patch->data; - ssize_t header_bytes_read = patch->size; - bool use_bsdiff = false; - if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { - use_bsdiff = true; - } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { - use_bsdiff = false; - } else { - printf("Unknown patch file format\n"); - return 1; - } - - do { - // Is there enough room in the target filesystem to hold the patched - // file? - - if (target_is_partition) { - // If the target is a partition, we're actually going to - // write the output to /tmp and then copy it to the - // partition. statfs() always returns 0 blocks free for - // /tmp, so instead we'll just assume that /tmp has enough - // space to hold the file. - - // We still write the original source to cache, in case - // the partition write is interrupted. - if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { - printf("not enough free space on /cache\n"); - return 1; - } - if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { - printf("failed to back up source file\n"); - return 1; - } - made_copy = 1; - retry = 0; - } else { - int enough_space = 0; - if (retry > 0) { - size_t free_space = FreeSpaceForFile(target_fs.c_str()); - enough_space = - (free_space > (256 << 10)) && // 256k (two-block) minimum - (free_space > (target_size * 3 / 2)); // 50% margin of error - if (!enough_space) { - printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n", - target_size, free_space, retry, enough_space); - } - } - - if (!enough_space) { - retry = 0; - } - - if (!enough_space && source_patch_value != NULL) { - // Using the original source, but not enough free space. First - // copy the source file to cache, then delete it from the original - // location. - - if (strncmp(source_filename, "MTD:", 4) == 0 || - strncmp(source_filename, "EMMC:", 5) == 0) { - // It's impossible to free space on the target filesystem by - // deleting the source if the source is a partition. If - // we're ever in a state where we need to do this, fail. - printf("not enough free space for target but source is partition\n"); - return 1; - } - - if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { - printf("not enough free space on /cache\n"); - return 1; - } - - if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { - printf("failed to back up source file\n"); - return 1; - } - made_copy = 1; - unlink(source_filename); - - size_t free_space = FreeSpaceForFile(target_fs.c_str()); - printf("(now %zu bytes free for target) ", free_space); - } - } - - - SinkFn sink = NULL; - void* token = NULL; - int output_fd = -1; - if (target_is_partition) { - // We store the decoded output in memory. - sink = MemorySink; - token = &memory_sink_str; - } else { - // We write the decoded output to "<tgt-file>.patch". - output_fd = ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, - S_IRUSR | S_IWUSR); - if (output_fd < 0) { - printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(), - strerror(errno)); - return 1; - } - sink = FileSink; - token = &output_fd; - } - - - SHA1_Init(&ctx); - - int result; - if (use_bsdiff) { - result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(), - patch, 0, sink, token, &ctx); - } else { - result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(), - patch, sink, token, &ctx, bonus_data); - } - - if (!target_is_partition) { - if (ota_fsync(output_fd) != 0) { - printf("failed to fsync file \"%s\" (%s)\n", tmp_target_filename.c_str(), - strerror(errno)); - result = 1; - } - if (ota_close(output_fd) != 0) { - printf("failed to close file \"%s\" (%s)\n", tmp_target_filename.c_str(), - strerror(errno)); - result = 1; - } - } - - if (result != 0) { - if (retry == 0) { - printf("applying patch failed\n"); - return result != 0; - } else { - printf("applying patch failed; retrying\n"); - } - if (!target_is_partition) { - unlink(tmp_target_filename.c_str()); - } - } else { - // succeeded; no need to retry - break; - } - } while (retry-- > 0); - - uint8_t current_target_sha1[SHA_DIGEST_LENGTH]; - SHA1_Final(current_target_sha1, &ctx); - if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { - printf("patch did not produce expected sha1\n"); - return 1; - } else { - printf("now %s\n", short_sha1(target_sha1).c_str()); - } - - if (target_is_partition) { - // Copy the temp file to the partition. - if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()), - memory_sink_str.size(), target_filename) != 0) { - printf("write of patched data to %s failed\n", target_filename); - return 1; - } - } else { - // Give the .patch file the same owner, group, and mode of the - // original source file. - if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); - return 1; - } - if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid, source_to_use->st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); - return 1; - } - - // Finally, rename the .patch file to replace the target file. - if (rename(tmp_target_filename.c_str(), target_filename) != 0) { - printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno)); - return 1; - } - } - - // If this run of applypatch created the copy, and we're here, we - // can delete it. - if (made_copy) { - unlink(CACHE_TEMP_SOURCE); - } - - // Success! - return 0; +static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch, + const std::string& target_filename, + const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data) { + if (patch->type != VAL_BLOB) { + printf("patch is not a blob\n"); + return 1; + } + + const char* header = &patch->data[0]; + size_t header_bytes_read = patch->data.size(); + bool use_bsdiff = false; + if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { + use_bsdiff = true; + } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { + use_bsdiff = false; + } else { + printf("Unknown patch file format\n"); + return 1; + } + + CHECK(android::base::StartsWith(target_filename, "EMMC:")); + + // We still write the original source to cache, in case the partition write is interrupted. + if (MakeFreeSpaceOnCache(source_file.data.size()) < 0) { + printf("not enough free space on /cache\n"); + return 1; + } + if (SaveFileContents(CACHE_TEMP_SOURCE, &source_file) < 0) { + printf("failed to back up source file\n"); + return 1; + } + + // We store the decoded output in memory. + SinkFn sink = MemorySink; + std::string memory_sink_str; // Don't need to reserve space. + void* token = &memory_sink_str; + + SHA_CTX ctx; + SHA1_Init(&ctx); + + int result; + if (use_bsdiff) { + result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0, + sink, token, &ctx); + } else { + result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink, + token, &ctx, bonus_data); + } + + if (result != 0) { + printf("applying patch failed\n"); + return 1; + } + + uint8_t current_target_sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(current_target_sha1, &ctx); + if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { + printf("patch did not produce expected sha1\n"); + return 1; + } else { + printf("now %s\n", short_sha1(target_sha1).c_str()); + } + + // Write back the temp file to the partition. + if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()), + memory_sink_str.size(), target_filename) != 0) { + printf("write of patched data to %s failed\n", target_filename.c_str()); + return 1; + } + + // Delete the backup copy of the source. + unlink(CACHE_TEMP_SOURCE); + + // Success! + return 0; } diff --git a/applypatch/utils.h b/applypatch/applypatch_main.cpp index bc97f1720..197077c93 100644 --- a/applypatch/utils.h +++ b/applypatch/applypatch_main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,15 @@ * limitations under the License. */ -#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H -#define _BUILD_TOOLS_APPLYPATCH_UTILS_H +#include "applypatch_modes.h" -#include <stdio.h> +// This program (applypatch) applies binary patches to files in a way that +// is safe (the original file is not touched until we have the desired +// replacement for it) and idempotent (it's okay to run this program +// multiple times). +// +// See the comments to applypatch_modes() function. -// Read and write little-endian values of various sizes. - -void Write4(int value, FILE* f); -void Write8(long long value, FILE* f); -int Read2(void* p); -int Read4(void* p); -long long Read8(void* p); - -#endif // _BUILD_TOOLS_APPLYPATCH_UTILS_H +int main(int argc, char** argv) { + return applypatch_modes(argc, const_cast<const char**>(argv)); +} diff --git a/applypatch/main.cpp b/applypatch/applypatch_modes.cpp index 9013760c4..7b191a801 100644 --- a/applypatch/main.cpp +++ b/applypatch/applypatch_modes.cpp @@ -14,61 +14,72 @@ * limitations under the License. */ +#include "applypatch_modes.h" + #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <memory> +#include <string> #include <vector> -#include "applypatch.h" +#include <android-base/parseint.h> +#include <android-base/strings.h> +#include <openssl/sha.h> + +#include "applypatch/applypatch.h" #include "edify/expr.h" -#include "openssl/sha.h" -static int CheckMode(int argc, char** argv) { +static int CheckMode(int argc, const char** argv) { if (argc < 3) { return 2; } - return applypatch_check(argv[2], argc-3, argv+3); + std::vector<std::string> sha1; + for (int i = 3; i < argc; i++) { + sha1.push_back(argv[i]); + } + + return applypatch_check(argv[2], sha1); } -static int SpaceMode(int argc, char** argv) { +static int SpaceMode(int argc, const char** argv) { if (argc != 3) { return 2; } - char* endptr; - size_t bytes = strtol(argv[2], &endptr, 10); - if (bytes == 0 && endptr == argv[2]) { + + size_t bytes; + if (!android::base::ParseUint(argv[2], &bytes) || bytes == 0) { printf("can't parse \"%s\" as byte count\n\n", argv[2]); return 1; } return CacheSizeCheck(bytes); } -// Parse arguments (which should be of the form "<sha1>:<filename>" -// into the new parallel arrays *sha1s and *files.Returns true on -// success. -static bool ParsePatchArgs(int argc, char** argv, std::vector<char*>* sha1s, +// Parse arguments (which should be of the form "<sha1>:<filename>" into the +// new parallel arrays *sha1s and *files. Returns true on success. +static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s, std::vector<FileContents>* files) { - uint8_t digest[SHA_DIGEST_LENGTH]; - + if (sha1s == nullptr) { + return false; + } for (int i = 0; i < argc; ++i) { - char* colon = strchr(argv[i], ':'); - if (colon == nullptr) { - printf("no ':' in patch argument \"%s\"\n", argv[i]); + std::vector<std::string> pieces = android::base::Split(argv[i], ":"); + if (pieces.size() != 2) { + printf("failed to parse patch argument \"%s\"\n", argv[i]); return false; } - *colon = '\0'; - ++colon; - if (ParseSha1(argv[i], digest) != 0) { + + uint8_t digest[SHA_DIGEST_LENGTH]; + if (ParseSha1(pieces[0].c_str(), digest) != 0) { printf("failed to parse sha1 \"%s\"\n", argv[i]); return false; } - sha1s->push_back(argv[i]); + sha1s->push_back(pieces[0]); FileContents fc; - if (LoadFileContents(colon, &fc) != 0) { + if (LoadFileContents(pieces[1].c_str(), &fc) != 0) { return false; } files->push_back(std::move(fc)); @@ -81,20 +92,17 @@ static int FlashMode(const char* src_filename, const char* tgt_filename, return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size); } -static int PatchMode(int argc, char** argv) { +static int PatchMode(int argc, const char** argv) { FileContents bonusFc; - Value bonusValue; - Value* bonus = nullptr; + Value bonus(VAL_INVALID, ""); if (argc >= 3 && strcmp(argv[1], "-b") == 0) { if (LoadFileContents(argv[2], &bonusFc) != 0) { printf("failed to load bonus file %s\n", argv[2]); return 1; } - bonus = &bonusValue; - bonus->type = VAL_BLOB; - bonus->size = bonusFc.data.size(); - bonus->data = reinterpret_cast<char*>(bonusFc.data.data()); + bonus.type = VAL_BLOB; + bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend()); argc -= 2; argv += 2; } @@ -103,42 +111,38 @@ static int PatchMode(int argc, char** argv) { return 2; } - char* endptr; - size_t target_size = strtol(argv[4], &endptr, 10); - if (target_size == 0 && endptr == argv[4]) { + size_t target_size; + if (!android::base::ParseUint(argv[4], &target_size) || target_size == 0) { printf("can't parse \"%s\" as byte count\n\n", argv[4]); return 1; } // If no <src-sha1>:<patch> is provided, it is in flash mode. if (argc == 5) { - if (bonus != nullptr) { + if (bonus.type != VAL_INVALID) { printf("bonus file not supported in flash mode\n"); return 1; } return FlashMode(argv[1], argv[2], argv[3], target_size); } - std::vector<char*> sha1s; + + std::vector<std::string> sha1s; std::vector<FileContents> files; if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) { printf("failed to parse patch args\n"); return 1; } - std::vector<Value> patches(files.size()); - std::vector<Value*> patch_ptrs(files.size()); + + std::vector<std::unique_ptr<Value>> patches; for (size_t i = 0; i < files.size(); ++i) { - patches[i].type = VAL_BLOB; - patches[i].size = files[i].data.size(); - patches[i].data = reinterpret_cast<char*>(files[i].data.data()); - patch_ptrs[i] = &patches[i]; + patches.push_back(std::make_unique<Value>( + VAL_BLOB, std::string(files[i].data.cbegin(), files[i].data.cend()))); } - return applypatch(argv[1], argv[2], argv[3], target_size, - patch_ptrs.size(), sha1s.data(), - patch_ptrs.data(), bonus); + return applypatch(argv[1], argv[2], argv[3], target_size, sha1s, patches, &bonus); } -// This program applies binary patches to files in a way that is safe -// (the original file is not touched until we have the desired +// This program (applypatch) applies binary patches to files in a way that +// is safe (the original file is not touched until we have the desired // replacement for it) and idempotent (it's okay to run this program // multiple times). // @@ -160,11 +164,11 @@ static int PatchMode(int argc, char** argv) { // - otherwise, or if any error is encountered, exits with non-zero // status. // -// <src-file> (or <file> in check mode) may refer to an MTD partition +// <src-file> (or <file> in check mode) may refer to an EMMC partition // to read the source data. See the comments for the -// LoadMTDContents() function above for the format of such a filename. +// LoadPartitionContents() function for the format of such a filename. -int main(int argc, char** argv) { +int applypatch_modes(int argc, const char** argv) { if (argc < 2) { usage: printf( @@ -175,8 +179,8 @@ int main(int argc, char** argv) { " or %s -l\n" "\n" "Filenames may be of the form\n" - " MTD:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n" - "to specify reading from or writing to an MTD partition.\n\n", + " EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n" + "to specify reading from or writing to an EMMC partition.\n\n", argv[0], argv[0], argv[0], argv[0]); return 2; } diff --git a/applypatch/applypatch_modes.h b/applypatch/applypatch_modes.h new file mode 100644 index 000000000..3d9d08df5 --- /dev/null +++ b/applypatch/applypatch_modes.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016 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 _APPLYPATCH_MODES_H +#define _APPLYPATCH_MODES_H + +int applypatch_modes(int argc, const char** argv); + +#endif // _APPLYPATCH_MODES_H diff --git a/applypatch/bsdiff.cpp b/applypatch/bsdiff.cpp deleted file mode 100644 index 55dbe5cf1..000000000 --- a/applypatch/bsdiff.cpp +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2009 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. - */ - -/* - * Most of this code comes from bsdiff.c from the bsdiff-4.3 - * distribution, which is: - */ - -/*- - * Copyright 2003-2005 Colin Percival - * All rights reserved - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted providing that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include <sys/types.h> - -#include <bzlib.h> -#include <err.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#define MIN(x,y) (((x)<(y)) ? (x) : (y)) - -static void split(off_t *I,off_t *V,off_t start,off_t len,off_t h) -{ - off_t i,j,k,x,tmp,jj,kk; - - if(len<16) { - for(k=start;k<start+len;k+=j) { - j=1;x=V[I[k]+h]; - for(i=1;k+i<start+len;i++) { - if(V[I[k+i]+h]<x) { - x=V[I[k+i]+h]; - j=0; - }; - if(V[I[k+i]+h]==x) { - tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp; - j++; - }; - }; - for(i=0;i<j;i++) V[I[k+i]]=k+j-1; - if(j==1) I[k]=-1; - }; - return; - }; - - x=V[I[start+len/2]+h]; - jj=0;kk=0; - for(i=start;i<start+len;i++) { - if(V[I[i]+h]<x) jj++; - if(V[I[i]+h]==x) kk++; - }; - jj+=start;kk+=jj; - - i=start;j=0;k=0; - while(i<jj) { - if(V[I[i]+h]<x) { - i++; - } else if(V[I[i]+h]==x) { - tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp; - j++; - } else { - tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp; - k++; - }; - }; - - while(jj+j<kk) { - if(V[I[jj+j]+h]==x) { - j++; - } else { - tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp; - k++; - }; - }; - - if(jj>start) split(I,V,start,jj-start,h); - - for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1; - if(jj==kk-1) I[jj]=-1; - - if(start+len>kk) split(I,V,kk,start+len-kk,h); -} - -static void qsufsort(off_t *I,off_t *V,u_char *old,off_t oldsize) -{ - off_t buckets[256]; - off_t i,h,len; - - for(i=0;i<256;i++) buckets[i]=0; - for(i=0;i<oldsize;i++) buckets[old[i]]++; - for(i=1;i<256;i++) buckets[i]+=buckets[i-1]; - for(i=255;i>0;i--) buckets[i]=buckets[i-1]; - buckets[0]=0; - - for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i; - I[0]=oldsize; - for(i=0;i<oldsize;i++) V[i]=buckets[old[i]]; - V[oldsize]=0; - for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1; - I[0]=-1; - - for(h=1;I[0]!=-(oldsize+1);h+=h) { - len=0; - for(i=0;i<oldsize+1;) { - if(I[i]<0) { - len-=I[i]; - i-=I[i]; - } else { - if(len) I[i-len]=-len; - len=V[I[i]]+1-i; - split(I,V,i,len,h); - i+=len; - len=0; - }; - }; - if(len) I[i-len]=-len; - }; - - for(i=0;i<oldsize+1;i++) I[V[i]]=i; -} - -static off_t matchlen(u_char *olddata,off_t oldsize,u_char *newdata,off_t newsize) -{ - off_t i; - - for(i=0;(i<oldsize)&&(i<newsize);i++) - if(olddata[i]!=newdata[i]) break; - - return i; -} - -static off_t search(off_t *I,u_char *old,off_t oldsize, - u_char *newdata,off_t newsize,off_t st,off_t en,off_t *pos) -{ - off_t x,y; - - if(en-st<2) { - x=matchlen(old+I[st],oldsize-I[st],newdata,newsize); - y=matchlen(old+I[en],oldsize-I[en],newdata,newsize); - - if(x>y) { - *pos=I[st]; - return x; - } else { - *pos=I[en]; - return y; - } - }; - - x=st+(en-st)/2; - if(memcmp(old+I[x],newdata,MIN(oldsize-I[x],newsize))<0) { - return search(I,old,oldsize,newdata,newsize,x,en,pos); - } else { - return search(I,old,oldsize,newdata,newsize,st,x,pos); - }; -} - -static void offtout(off_t x,u_char *buf) -{ - off_t y; - - if(x<0) y=-x; else y=x; - - buf[0]=y%256;y-=buf[0]; - y=y/256;buf[1]=y%256;y-=buf[1]; - y=y/256;buf[2]=y%256;y-=buf[2]; - y=y/256;buf[3]=y%256;y-=buf[3]; - y=y/256;buf[4]=y%256;y-=buf[4]; - y=y/256;buf[5]=y%256;y-=buf[5]; - y=y/256;buf[6]=y%256;y-=buf[6]; - y=y/256;buf[7]=y%256; - - if(x<0) buf[7]|=0x80; -} - -// This is main() from bsdiff.c, with the following changes: -// -// - old, oldsize, newdata, newsize are arguments; we don't load this -// data from files. old and newdata are owned by the caller; we -// don't free them at the end. -// -// - the "I" block of memory is owned by the caller, who passes a -// pointer to *I, which can be NULL. This way if we call -// bsdiff() multiple times with the same 'old' data, we only do -// the qsufsort() step the first time. -// -int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* newdata, off_t newsize, - const char* patch_filename) -{ - int fd; - off_t *I; - off_t scan,pos,len; - off_t lastscan,lastpos,lastoffset; - off_t oldscore,scsc; - off_t s,Sf,lenf,Sb,lenb; - off_t overlap,Ss,lens; - off_t i; - off_t dblen,eblen; - u_char *db,*eb; - u_char buf[8]; - u_char header[32]; - FILE * pf; - BZFILE * pfbz2; - int bz2err; - - if (*IP == NULL) { - off_t* V; - *IP = reinterpret_cast<off_t*>(malloc((oldsize+1) * sizeof(off_t))); - V = reinterpret_cast<off_t*>(malloc((oldsize+1) * sizeof(off_t))); - qsufsort(*IP, V, old, oldsize); - free(V); - } - I = *IP; - - if(((db=reinterpret_cast<u_char*>(malloc(newsize+1)))==NULL) || - ((eb=reinterpret_cast<u_char*>(malloc(newsize+1)))==NULL)) err(1,NULL); - dblen=0; - eblen=0; - - /* Create the patch file */ - if ((pf = fopen(patch_filename, "w")) == NULL) - err(1, "%s", patch_filename); - - /* Header is - 0 8 "BSDIFF40" - 8 8 length of bzip2ed ctrl block - 16 8 length of bzip2ed diff block - 24 8 length of new file */ - /* File is - 0 32 Header - 32 ?? Bzip2ed ctrl block - ?? ?? Bzip2ed diff block - ?? ?? Bzip2ed extra block */ - memcpy(header,"BSDIFF40",8); - offtout(0, header + 8); - offtout(0, header + 16); - offtout(newsize, header + 24); - if (fwrite(header, 32, 1, pf) != 1) - err(1, "fwrite(%s)", patch_filename); - - /* Compute the differences, writing ctrl as we go */ - if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) - errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); - scan=0;len=0; - lastscan=0;lastpos=0;lastoffset=0; - while(scan<newsize) { - oldscore=0; - - for(scsc=scan+=len;scan<newsize;scan++) { - len=search(I,old,oldsize,newdata+scan,newsize-scan, - 0,oldsize,&pos); - - for(;scsc<scan+len;scsc++) - if((scsc+lastoffset<oldsize) && - (old[scsc+lastoffset] == newdata[scsc])) - oldscore++; - - if(((len==oldscore) && (len!=0)) || - (len>oldscore+8)) break; - - if((scan+lastoffset<oldsize) && - (old[scan+lastoffset] == newdata[scan])) - oldscore--; - }; - - if((len!=oldscore) || (scan==newsize)) { - s=0;Sf=0;lenf=0; - for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) { - if(old[lastpos+i]==newdata[lastscan+i]) s++; - i++; - if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; }; - }; - - lenb=0; - if(scan<newsize) { - s=0;Sb=0; - for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) { - if(old[pos-i]==newdata[scan-i]) s++; - if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; }; - }; - }; - - if(lastscan+lenf>scan-lenb) { - overlap=(lastscan+lenf)-(scan-lenb); - s=0;Ss=0;lens=0; - for(i=0;i<overlap;i++) { - if(newdata[lastscan+lenf-overlap+i]== - old[lastpos+lenf-overlap+i]) s++; - if(newdata[scan-lenb+i]== - old[pos-lenb+i]) s--; - if(s>Ss) { Ss=s; lens=i+1; }; - }; - - lenf+=lens-overlap; - lenb-=lens; - }; - - for(i=0;i<lenf;i++) - db[dblen+i]=newdata[lastscan+i]-old[lastpos+i]; - for(i=0;i<(scan-lenb)-(lastscan+lenf);i++) - eb[eblen+i]=newdata[lastscan+lenf+i]; - - dblen+=lenf; - eblen+=(scan-lenb)-(lastscan+lenf); - - offtout(lenf,buf); - BZ2_bzWrite(&bz2err, pfbz2, buf, 8); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); - - offtout((scan-lenb)-(lastscan+lenf),buf); - BZ2_bzWrite(&bz2err, pfbz2, buf, 8); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); - - offtout((pos-lenb)-(lastpos+lenf),buf); - BZ2_bzWrite(&bz2err, pfbz2, buf, 8); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); - - lastscan=scan-lenb; - lastpos=pos-lenb; - lastoffset=pos-scan; - }; - }; - BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); - - /* Compute size of compressed ctrl data */ - if ((len = ftello(pf)) == -1) - err(1, "ftello"); - offtout(len-32, header + 8); - - /* Write compressed diff data */ - if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) - errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); - BZ2_bzWrite(&bz2err, pfbz2, db, dblen); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); - BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); - - /* Compute size of compressed diff data */ - if ((newsize = ftello(pf)) == -1) - err(1, "ftello"); - offtout(newsize - len, header + 16); - - /* Write compressed extra data */ - if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) - errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); - BZ2_bzWrite(&bz2err, pfbz2, eb, eblen); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); - BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); - if (bz2err != BZ_OK) - errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); - - /* Seek to the beginning, write the header, and close the file */ - if (fseeko(pf, 0, SEEK_SET)) - err(1, "fseeko"); - if (fwrite(header, 32, 1, pf) != 1) - err(1, "fwrite(%s)", patch_filename); - if (fclose(pf)) - err(1, "fclose"); - - /* Free the memory we used */ - free(db); - free(eb); - - return 0; -} diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp index ebb55f1d1..9920c2be1 100644 --- a/applypatch/bspatch.cpp +++ b/applypatch/bspatch.cpp @@ -21,16 +21,12 @@ // notice. #include <stdio.h> -#include <sys/stat.h> #include <sys/types.h> -#include <errno.h> -#include <unistd.h> -#include <string.h> -#include <bzlib.h> +#include <bspatch.h> +#include "applypatch/applypatch.h" #include "openssl/sha.h" -#include "applypatch.h" void ShowBSDiffLicense() { puts("The bsdiff library used herein is:\n" @@ -64,184 +60,25 @@ void ShowBSDiffLicense() { ); } -static off_t offtin(u_char *buf) -{ - off_t y; - - y=buf[7]&0x7F; - y=y*256;y+=buf[6]; - y=y*256;y+=buf[5]; - y=y*256;y+=buf[4]; - y=y*256;y+=buf[3]; - y=y*256;y+=buf[2]; - y=y*256;y+=buf[1]; - y=y*256;y+=buf[0]; - - if(buf[7]&0x80) y=-y; - - return y; -} - -int FillBuffer(unsigned char* buffer, int size, bz_stream* stream) { - stream->next_out = (char*)buffer; - stream->avail_out = size; - while (stream->avail_out > 0) { - int bzerr = BZ2_bzDecompress(stream); - if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) { - printf("bz error %d decompressing\n", bzerr); - return -1; - } - if (stream->avail_out > 0) { - printf("need %d more bytes\n", stream->avail_out); - } - } - return 0; -} - -int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - SinkFn sink, void* token, SHA_CTX* ctx) { - - std::vector<unsigned char> new_data; - if (ApplyBSDiffPatchMem(old_data, old_size, patch, patch_offset, &new_data) != 0) { - return -1; - } - - if (sink(new_data.data(), new_data.size(), token) < static_cast<ssize_t>(new_data.size())) { - printf("short write of output: %d (%s)\n", errno, strerror(errno)); - return 1; - } - if (ctx) SHA1_Update(ctx, new_data.data(), new_data.size()); - return 0; +int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, + ssize_t patch_offset, SinkFn sink, void* token, SHA_CTX* ctx) { + auto sha_sink = [&](const uint8_t* data, size_t len) { + len = sink(data, len, token); + if (ctx) SHA1_Update(ctx, data, len); + return len; + }; + return bsdiff::bspatch(old_data, old_size, + reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), + patch->data.size(), sha_sink); } -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - std::vector<unsigned char>* new_data) { - // Patch data format: - // 0 8 "BSDIFF40" - // 8 8 X - // 16 8 Y - // 24 8 sizeof(newfile) - // 32 X bzip2(control block) - // 32+X Y bzip2(diff block) - // 32+X+Y ??? bzip2(extra block) - // with control block a set of triples (x,y,z) meaning "add x bytes - // from oldfile to x bytes from the diff block; copy y bytes from the - // extra block; seek forwards in oldfile by z bytes". - - unsigned char* header = (unsigned char*) patch->data + patch_offset; - if (memcmp(header, "BSDIFF40", 8) != 0) { - printf("corrupt bsdiff patch file header (magic number)\n"); - return 1; - } - - ssize_t ctrl_len, data_len, new_size; - ctrl_len = offtin(header+8); - data_len = offtin(header+16); - new_size = offtin(header+24); - - if (ctrl_len < 0 || data_len < 0 || new_size < 0) { - printf("corrupt patch file header (data lengths)\n"); - return 1; - } - - int bzerr; - - bz_stream cstream; - cstream.next_in = patch->data + patch_offset + 32; - cstream.avail_in = ctrl_len; - cstream.bzalloc = NULL; - cstream.bzfree = NULL; - cstream.opaque = NULL; - if ((bzerr = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) { - printf("failed to bzinit control stream (%d)\n", bzerr); - } - - bz_stream dstream; - dstream.next_in = patch->data + patch_offset + 32 + ctrl_len; - dstream.avail_in = data_len; - dstream.bzalloc = NULL; - dstream.bzfree = NULL; - dstream.opaque = NULL; - if ((bzerr = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) { - printf("failed to bzinit diff stream (%d)\n", bzerr); - } - - bz_stream estream; - estream.next_in = patch->data + patch_offset + 32 + ctrl_len + data_len; - estream.avail_in = patch->size - (patch_offset + 32 + ctrl_len + data_len); - estream.bzalloc = NULL; - estream.bzfree = NULL; - estream.opaque = NULL; - if ((bzerr = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) { - printf("failed to bzinit extra stream (%d)\n", bzerr); - } - - new_data->resize(new_size); - - off_t oldpos = 0, newpos = 0; - off_t ctrl[3]; - off_t len_read; - int i; - unsigned char buf[24]; - while (newpos < new_size) { - // Read control data - if (FillBuffer(buf, 24, &cstream) != 0) { - printf("error while reading control stream\n"); - return 1; - } - ctrl[0] = offtin(buf); - ctrl[1] = offtin(buf+8); - ctrl[2] = offtin(buf+16); - - if (ctrl[0] < 0 || ctrl[1] < 0) { - printf("corrupt patch (negative byte counts)\n"); - return 1; - } - - // Sanity check - if (newpos + ctrl[0] > new_size) { - printf("corrupt patch (new file overrun)\n"); - return 1; - } - - // Read diff string - if (FillBuffer(new_data->data() + newpos, ctrl[0], &dstream) != 0) { - printf("error while reading diff stream\n"); - return 1; - } - - // Add old data to diff string - for (i = 0; i < ctrl[0]; ++i) { - if ((oldpos+i >= 0) && (oldpos+i < old_size)) { - (*new_data)[newpos+i] += old_data[oldpos+i]; - } - } - - // Adjust pointers - newpos += ctrl[0]; - oldpos += ctrl[0]; - - // Sanity check - if (newpos + ctrl[1] > new_size) { - printf("corrupt patch (new file overrun)\n"); - return 1; - } - - // Read extra string - if (FillBuffer(new_data->data() + newpos, ctrl[1], &estream) != 0) { - printf("error while reading extra stream\n"); - return 1; - } - - // Adjust pointers - newpos += ctrl[1]; - oldpos += ctrl[2]; - } - - BZ2_bzDecompressEnd(&cstream); - BZ2_bzDecompressEnd(&dstream); - BZ2_bzDecompressEnd(&estream); - return 0; +int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, const Value* patch, + ssize_t patch_offset, std::vector<unsigned char>* new_data) { + auto vector_sink = [new_data](const uint8_t* data, size_t len) { + new_data->insert(new_data->end(), data, data + len); + return len; + }; + return bsdiff::bspatch(old_data, old_size, + reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), + patch->data.size(), vector_sink); } diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp index c84f42797..331cae265 100644 --- a/applypatch/freecache.cpp +++ b/applypatch/freecache.cpp @@ -32,7 +32,7 @@ #include <android-base/parseint.h> #include <android-base/stringprintf.h> -#include "applypatch.h" +#include "applypatch/applypatch.h" static int EliminateOpenFiles(std::set<std::string>* files) { std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir); diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp index f22502e38..41d73ab98 100644 --- a/applypatch/imgdiff.cpp +++ b/applypatch/imgdiff.cpp @@ -121,618 +121,687 @@ * information that is stored on the system partition. */ +#include "applypatch/imgdiff.h" + #include <errno.h> -#include <inttypes.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> -#include <unistd.h> #include <sys/types.h> +#include <unistd.h> -#include "zlib.h" -#include "imgdiff.h" -#include "utils.h" +#include <algorithm> +#include <string> +#include <vector> -typedef struct { - int type; // CHUNK_NORMAL, CHUNK_DEFLATE - size_t start; // offset of chunk in original image file +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/memory.h> +#include <android-base/unique_fd.h> +#include <ziparchive/zip_archive.h> - size_t len; - unsigned char* data; // data to be patched (uncompressed, for deflate chunks) +#include <bsdiff.h> +#include <zlib.h> - size_t source_start; - size_t source_len; +using android::base::get_unaligned; - off_t* I; // used by bsdiff +static constexpr auto BUFFER_SIZE = 0x8000; - // --- for CHUNK_DEFLATE chunks only: --- +// If we use this function to write the offset and length (type size_t), their values should not +// exceed 2^63; because the signed bit will be casted away. +static inline bool Write8(int fd, int64_t value) { + return android::base::WriteFully(fd, &value, sizeof(int64_t)); +} + +// Similarly, the value should not exceed 2^31 if we are casting from size_t (e.g. target chunk +// size). +static inline bool Write4(int fd, int32_t value) { + return android::base::WriteFully(fd, &value, sizeof(int32_t)); +} + +class ImageChunk { + public: + static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream. + static constexpr auto MEMLEVEL = 8; // the default value. + static constexpr auto METHOD = Z_DEFLATED; + static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY; + + ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len) + : type_(type), + start_(start), + input_file_ptr_(file_content), + raw_data_len_(raw_data_len), + compress_level_(6), + source_start_(0), + source_len_(0), + source_uncompressed_len_(0) { + CHECK(file_content != nullptr) << "input file container can't be nullptr"; + } + + int GetType() const { + return type_; + } + size_t GetRawDataLength() const { + return raw_data_len_; + } + const std::string& GetEntryName() const { + return entry_name_; + } + + // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return + // the raw data. + const uint8_t * DataForPatch() const; + size_t DataLengthForPatch() const; + + void Dump() const { + printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch()); + } - // original (compressed) deflate data - size_t deflate_len; - unsigned char* deflate_data; + void SetSourceInfo(const ImageChunk& other); + void SetEntryName(std::string entryname); + void SetUncompressedData(std::vector<uint8_t> data); + bool SetBonusData(const std::vector<uint8_t>& bonus_data); - char* filename; // used for zip entries + bool operator==(const ImageChunk& other) const; + bool operator!=(const ImageChunk& other) const { + return !(*this == other); + } + + size_t GetHeaderSize(size_t patch_size) const; + // Return the offset of the next patch into the patch data. + size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset); + + /* + * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob + * of uninterpreted data). The resulting patch will likely be about + * as big as the target file, but it lets us handle the case of images + * where some gzip chunks are reconstructible but others aren't (by + * treating the ones that aren't as normal chunks). + */ + void ChangeDeflateChunkToNormal(); + bool ChangeChunkToRaw(size_t patch_size); + + /* + * Verify that we can reproduce exactly the same compressed data that + * we started with. Sets the level, method, windowBits, memLevel, and + * strategy fields in the chunk to the encoding parameters needed to + * produce the right output. + */ + bool ReconstructDeflateChunk(); + bool IsAdjacentNormal(const ImageChunk& other) const; + void MergeAdjacentNormal(const ImageChunk& other); + + private: + int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW + size_t start_; // offset of chunk in the original input file + const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file + size_t raw_data_len_; + + // --- for CHUNK_DEFLATE chunks only: --- + std::vector<uint8_t> uncompressed_data_; + std::string entry_name_; // used for zip entries // deflate encoder parameters - int level, method, windowBits, memLevel, strategy; - - size_t source_uncompressed_len; -} ImageChunk; - -typedef struct { - int data_offset; - int deflate_len; - int uncomp_len; - char* filename; -} ZipFileEntry; - -static int fileentry_compare(const void* a, const void* b) { - int ao = ((ZipFileEntry*)a)->data_offset; - int bo = ((ZipFileEntry*)b)->data_offset; - if (ao < bo) { - return -1; - } else if (ao > bo) { - return 1; - } else { - return 0; + int compress_level_; + + size_t source_start_; + size_t source_len_; + size_t source_uncompressed_len_; + + const uint8_t* GetRawData() const; + bool TryReconstruction(int level); +}; + +const uint8_t* ImageChunk::GetRawData() const { + CHECK_LE(start_ + raw_data_len_, input_file_ptr_->size()); + return input_file_ptr_->data() + start_; +} + +const uint8_t * ImageChunk::DataForPatch() const { + if (type_ == CHUNK_DEFLATE) { + return uncompressed_data_.data(); } + return GetRawData(); } -// from bsdiff.c -int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* newdata, off_t newsize, - const char* patch_filename); +size_t ImageChunk::DataLengthForPatch() const { + if (type_ == CHUNK_DEFLATE) { + return uncompressed_data_.size(); + } + return raw_data_len_; +} -unsigned char* ReadZip(const char* filename, - int* num_chunks, ImageChunk** chunks, - int include_pseudo_chunk) { - struct stat st; - if (stat(filename, &st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return NULL; +bool ImageChunk::operator==(const ImageChunk& other) const { + if (type_ != other.type_) { + return false; } + return (raw_data_len_ == other.raw_data_len_ && + memcmp(GetRawData(), other.GetRawData(), raw_data_len_) == 0); +} - size_t sz = static_cast<size_t>(st.st_size); - unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz)); - FILE* f = fopen(filename, "rb"); - if (fread(img, 1, sz, f) != sz) { - printf("failed to read \"%s\" %s\n", filename, strerror(errno)); - fclose(f); - return NULL; +void ImageChunk::SetSourceInfo(const ImageChunk& src) { + source_start_ = src.start_; + if (type_ == CHUNK_NORMAL) { + source_len_ = src.raw_data_len_; + } else if (type_ == CHUNK_DEFLATE) { + source_len_ = src.raw_data_len_; + source_uncompressed_len_ = src.uncompressed_data_.size(); } - fclose(f); +} + +void ImageChunk::SetEntryName(std::string entryname) { + entry_name_ = std::move(entryname); +} - // look for the end-of-central-directory record. +void ImageChunk::SetUncompressedData(std::vector<uint8_t> data) { + uncompressed_data_ = std::move(data); +} - int i; - for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) { - if (img[i] == 0x50 && img[i+1] == 0x4b && - img[i+2] == 0x05 && img[i+3] == 0x06) { - break; - } - } - // double-check: this archive consists of a single "disk" - if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) { - printf("can't process multi-disk archive\n"); - return NULL; +bool ImageChunk::SetBonusData(const std::vector<uint8_t>& bonus_data) { + if (type_ != CHUNK_DEFLATE) { + return false; } + uncompressed_data_.insert(uncompressed_data_.end(), bonus_data.begin(), bonus_data.end()); + return true; +} - int cdcount = Read2(img+i+8); - int cdoffset = Read4(img+i+16); +// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the target size is +// smaller. Also take the header size into account during size comparison. +bool ImageChunk::ChangeChunkToRaw(size_t patch_size) { + if (type_ == CHUNK_RAW) { + return true; + } else if (type_ == CHUNK_NORMAL && (raw_data_len_ <= 160 || raw_data_len_ < patch_size)) { + type_ = CHUNK_RAW; + return true; + } + return false; +} - ZipFileEntry* temp_entries = reinterpret_cast<ZipFileEntry*>(malloc( - cdcount * sizeof(ZipFileEntry))); - int entrycount = 0; +void ImageChunk::ChangeDeflateChunkToNormal() { + if (type_ != CHUNK_DEFLATE) return; + type_ = CHUNK_NORMAL; + entry_name_.clear(); + uncompressed_data_.clear(); +} - unsigned char* cd = img+cdoffset; - for (i = 0; i < cdcount; ++i) { - if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) { - printf("bad central directory entry %d\n", i); - return NULL; - } +// Header size: +// header_type 4 bytes +// CHUNK_NORMAL 8*3 = 24 bytes +// CHUNK_DEFLATE 8*5 + 4*5 = 60 bytes +// CHUNK_RAW 4 bytes + patch_size +size_t ImageChunk::GetHeaderSize(size_t patch_size) const { + switch (type_) { + case CHUNK_NORMAL: + return 4 + 8 * 3; + case CHUNK_DEFLATE: + return 4 + 8 * 5 + 4 * 5; + case CHUNK_RAW: + return 4 + 4 + patch_size; + default: + CHECK(false) << "unexpected chunk type: " << type_; // Should not reach here. + return 0; + } +} - int clen = Read4(cd+20); // compressed len - int ulen = Read4(cd+24); // uncompressed len - int nlen = Read2(cd+28); // filename len - int xlen = Read2(cd+30); // extra field len - int mlen = Read2(cd+32); // file comment len - int hoffset = Read4(cd+42); // local header offset +size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) { + Write4(fd, type_); + switch (type_) { + case CHUNK_NORMAL: + printf("normal (%10zu, %10zu) %10zu\n", start_, raw_data_len_, patch.size()); + Write8(fd, static_cast<int64_t>(source_start_)); + Write8(fd, static_cast<int64_t>(source_len_)); + Write8(fd, static_cast<int64_t>(offset)); + return offset + patch.size(); + case CHUNK_DEFLATE: + printf("deflate (%10zu, %10zu) %10zu %s\n", start_, raw_data_len_, patch.size(), + entry_name_.c_str()); + Write8(fd, static_cast<int64_t>(source_start_)); + Write8(fd, static_cast<int64_t>(source_len_)); + Write8(fd, static_cast<int64_t>(offset)); + Write8(fd, static_cast<int64_t>(source_uncompressed_len_)); + Write8(fd, static_cast<int64_t>(uncompressed_data_.size())); + Write4(fd, compress_level_); + Write4(fd, METHOD); + Write4(fd, WINDOWBITS); + Write4(fd, MEMLEVEL); + Write4(fd, STRATEGY); + return offset + patch.size(); + case CHUNK_RAW: + printf("raw (%10zu, %10zu)\n", start_, raw_data_len_); + Write4(fd, static_cast<int32_t>(patch.size())); + if (!android::base::WriteFully(fd, patch.data(), patch.size())) { + CHECK(false) << "failed to write " << patch.size() <<" bytes patch"; + } + return offset; + default: + CHECK(false) << "unexpected chunk type: " << type_; + return offset; + } +} - char* filename = reinterpret_cast<char*>(malloc(nlen+1)); - memcpy(filename, cd+46, nlen); - filename[nlen] = '\0'; +bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const { + if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) { + return false; + } + return (other.start_ == start_ + raw_data_len_); +} - int method = Read2(cd+10); +void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) { + CHECK(IsAdjacentNormal(other)); + raw_data_len_ = raw_data_len_ + other.raw_data_len_; +} - cd += 46 + nlen + xlen + mlen; +bool ImageChunk::ReconstructDeflateChunk() { + if (type_ != CHUNK_DEFLATE) { + printf("attempt to reconstruct non-deflate chunk\n"); + return false; + } - if (method != 8) { // 8 == deflate - free(filename); - continue; + // We only check two combinations of encoder parameters: level 6 + // (the default) and level 9 (the maximum). + for (int level = 6; level <= 9; level += 3) { + if (TryReconstruction(level)) { + compress_level_ = level; + return true; } + } + + return false; +} - unsigned char* lh = img + hoffset; +/* + * Takes the uncompressed data stored in the chunk, compresses it + * using the zlib parameters stored in the chunk, and checks that it + * matches exactly the compressed data we started with (also stored in + * the chunk). + */ +bool ImageChunk::TryReconstruction(int level) { + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = uncompressed_data_.size(); + strm.next_in = uncompressed_data_.data(); + int ret = deflateInit2(&strm, level, METHOD, WINDOWBITS, MEMLEVEL, STRATEGY); + if (ret < 0) { + printf("failed to initialize deflate: %d\n", ret); + return false; + } - if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) { - printf("bad local file header entry %d\n", i); - return NULL; + std::vector<uint8_t> buffer(BUFFER_SIZE); + size_t offset = 0; + do { + strm.avail_out = buffer.size(); + strm.next_out = buffer.data(); + ret = deflate(&strm, Z_FINISH); + if (ret < 0) { + printf("failed to deflate: %d\n", ret); + return false; } - if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) { - printf("central dir filename doesn't match local header\n"); - return NULL; + size_t compressed_size = buffer.size() - strm.avail_out; + if (memcmp(buffer.data(), input_file_ptr_->data() + start_ + offset, compressed_size) != 0) { + // mismatch; data isn't the same. + deflateEnd(&strm); + return false; } + offset += compressed_size; + } while (ret != Z_STREAM_END); + deflateEnd(&strm); - xlen = Read2(lh+28); // extra field len; might be different from CD entry? + if (offset != raw_data_len_) { + // mismatch; ran out of data before we should have. + return false; + } + return true; +} - temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen; - temp_entries[entrycount].deflate_len = clen; - temp_entries[entrycount].uncomp_len = ulen; - temp_entries[entrycount].filename = filename; - ++entrycount; +// EOCD record +// offset 0: signature 0x06054b50, 4 bytes +// offset 4: number of this disk, 2 bytes +// ... +// offset 20: comment length, 2 bytes +// offset 22: comment, n bytes +static bool GetZipFileSize(const std::vector<uint8_t>& zip_file, size_t* input_file_size) { + if (zip_file.size() < 22) { + printf("file is too small to be a zip file\n"); + return false; } - qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare); - -#if 0 - printf("found %d deflated entries\n", entrycount); - for (i = 0; i < entrycount; ++i) { - printf("off %10d len %10d unlen %10d %p %s\n", - temp_entries[i].data_offset, - temp_entries[i].deflate_len, - temp_entries[i].uncomp_len, - temp_entries[i].filename, - temp_entries[i].filename); + // Look for End of central directory record of the zip file, and calculate the actual + // zip_file size. + for (int i = zip_file.size() - 22; i >= 0; i--) { + if (zip_file[i] == 0x50) { + if (get_unaligned<uint32_t>(&zip_file[i]) == 0x06054b50) { + // double-check: this archive consists of a single "disk". + CHECK_EQ(get_unaligned<uint16_t>(&zip_file[i + 4]), 0); + + uint16_t comment_length = get_unaligned<uint16_t>(&zip_file[i + 20]); + size_t file_size = i + 22 + comment_length; + CHECK_LE(file_size, zip_file.size()); + *input_file_size = file_size; + return true; + } + } } -#endif - *num_chunks = 0; - *chunks = reinterpret_cast<ImageChunk*>(malloc((entrycount*2+2) * sizeof(ImageChunk))); - ImageChunk* curr = *chunks; + // EOCD not found, this file is likely not a valid zip file. + return false; +} + +static bool ReadZip(const char* filename, std::vector<ImageChunk>* chunks, + std::vector<uint8_t>* zip_file, bool include_pseudo_chunk) { + CHECK(chunks != nullptr && zip_file != nullptr); - if (include_pseudo_chunk) { - curr->type = CHUNK_NORMAL; - curr->start = 0; - curr->len = st.st_size; - curr->data = img; - curr->filename = NULL; - curr->I = NULL; - ++curr; - ++*num_chunks; + android::base::unique_fd fd(open(filename, O_RDONLY)); + if (fd == -1) { + printf("failed to open \"%s\" %s\n", filename, strerror(errno)); + return false; + } + struct stat st; + if (fstat(fd, &st) != 0) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return false; } - int pos = 0; - int nextentry = 0; + size_t sz = static_cast<size_t>(st.st_size); + zip_file->resize(sz); + if (!android::base::ReadFully(fd, zip_file->data(), sz)) { + printf("failed to read \"%s\" %s\n", filename, strerror(errno)); + return false; + } + fd.reset(); - while (pos < st.st_size) { - if (nextentry < entrycount && pos == temp_entries[nextentry].data_offset) { - curr->type = CHUNK_DEFLATE; - curr->start = pos; - curr->deflate_len = temp_entries[nextentry].deflate_len; - curr->deflate_data = img + pos; - curr->filename = temp_entries[nextentry].filename; - curr->I = NULL; + // Trim the trailing zeros before we pass the file to ziparchive handler. + size_t zipfile_size; + if (!GetZipFileSize(*zip_file, &zipfile_size)) { + printf("failed to parse the actual size of %s\n", filename); + return false; + } + ZipArchiveHandle handle; + int err = OpenArchiveFromMemory(zip_file->data(), zipfile_size, filename, &handle); + if (err != 0) { + printf("failed to open zip file %s: %s\n", filename, ErrorCodeString(err)); + CloseArchive(handle); + return false; + } - curr->len = temp_entries[nextentry].uncomp_len; - curr->data = reinterpret_cast<unsigned char*>(malloc(curr->len)); + // Create a list of deflated zip entries, sorted by offset. + std::vector<std::pair<std::string, ZipEntry>> temp_entries; + void* cookie; + int ret = StartIteration(handle, &cookie, nullptr, nullptr); + if (ret != 0) { + printf("failed to iterate over entries in %s: %s\n", filename, ErrorCodeString(ret)); + CloseArchive(handle); + return false; + } - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = curr->deflate_len; - strm.next_in = curr->deflate_data; + ZipString name; + ZipEntry entry; + while ((ret = Next(cookie, &entry, &name)) == 0) { + if (entry.method == kCompressDeflated) { + std::string entryname(name.name, name.name + name.name_length); + temp_entries.push_back(std::make_pair(entryname, entry)); + } + } - // -15 means we are decoding a 'raw' deflate stream; zlib will - // not expect zlib headers. - int ret = inflateInit2(&strm, -15); + if (ret != -1) { + printf("Error while iterating over zip entries: %s\n", ErrorCodeString(ret)); + CloseArchive(handle); + return false; + } + std::sort(temp_entries.begin(), temp_entries.end(), + [](auto& entry1, auto& entry2) { + return entry1.second.offset < entry2.second.offset; + }); - strm.avail_out = curr->len; - strm.next_out = curr->data; - ret = inflate(&strm, Z_NO_FLUSH); - if (ret != Z_STREAM_END) { - printf("failed to inflate \"%s\"; %d\n", curr->filename, ret); - return NULL; + EndIteration(cookie); + + if (include_pseudo_chunk) { + chunks->emplace_back(CHUNK_NORMAL, 0, zip_file, zip_file->size()); + } + + size_t pos = 0; + size_t nextentry = 0; + while (pos < zip_file->size()) { + if (nextentry < temp_entries.size() && + static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) { + // compose the next deflate chunk. + std::string entryname = temp_entries[nextentry].first; + size_t uncompressed_len = temp_entries[nextentry].second.uncompressed_length; + std::vector<uint8_t> uncompressed_data(uncompressed_len); + if ((ret = ExtractToMemory(handle, &temp_entries[nextentry].second, uncompressed_data.data(), + uncompressed_len)) != 0) { + printf("failed to extract %s with size %zu: %s\n", entryname.c_str(), uncompressed_len, + ErrorCodeString(ret)); + CloseArchive(handle); + return false; } - inflateEnd(&strm); + size_t compressed_len = temp_entries[nextentry].second.compressed_length; + ImageChunk curr(CHUNK_DEFLATE, pos, zip_file, compressed_len); + curr.SetEntryName(std::move(entryname)); + curr.SetUncompressedData(std::move(uncompressed_data)); + chunks->push_back(curr); - pos += curr->deflate_len; + pos += compressed_len; ++nextentry; - ++*num_chunks; - ++curr; continue; } - // use a normal chunk to take all the data up to the start of the - // next deflate section. - - curr->type = CHUNK_NORMAL; - curr->start = pos; - if (nextentry < entrycount) { - curr->len = temp_entries[nextentry].data_offset - pos; + // Use a normal chunk to take all the data up to the start of the next deflate section. + size_t raw_data_len; + if (nextentry < temp_entries.size()) { + raw_data_len = temp_entries[nextentry].second.offset - pos; } else { - curr->len = st.st_size - pos; + raw_data_len = zip_file->size() - pos; } - curr->data = img + pos; - curr->filename = NULL; - curr->I = NULL; - pos += curr->len; + chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len); - ++*num_chunks; - ++curr; + pos += raw_data_len; } - free(temp_entries); - return img; + CloseArchive(handle); + return true; } -/* - * Read the given file and break it up into chunks, putting the number - * of chunks and their info in *num_chunks and **chunks, - * respectively. Returns a malloc'd block of memory containing the - * contents of the file; various pointers in the output chunk array - * will point into this block of memory. The caller should free the - * return value when done with all the chunks. Returns NULL on - * failure. - */ -unsigned char* ReadImage(const char* filename, - int* num_chunks, ImageChunk** chunks) { +// Read the given file and break it up into chunks, and putting the data in to a vector. +static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, + std::vector<uint8_t>* img) { + CHECK(chunks != nullptr && img != nullptr); + + android::base::unique_fd fd(open(filename, O_RDONLY)); + if (fd == -1) { + printf("failed to open \"%s\" %s\n", filename, strerror(errno)); + return false; + } struct stat st; - if (stat(filename, &st) != 0) { + if (fstat(fd, &st) != 0) { printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return NULL; + return false; } size_t sz = static_cast<size_t>(st.st_size); - unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz + 4)); - FILE* f = fopen(filename, "rb"); - if (fread(img, 1, sz, f) != sz) { + img->resize(sz); + if (!android::base::ReadFully(fd, img->data(), sz)) { printf("failed to read \"%s\" %s\n", filename, strerror(errno)); - fclose(f); - return NULL; + return false; } - fclose(f); - - // append 4 zero bytes to the data so we can always search for the - // four-byte string 1f8b0800 starting at any point in the actual - // file data, without special-casing the end of the data. - memset(img+sz, 0, 4); size_t pos = 0; - *num_chunks = 0; - *chunks = NULL; - while (pos < sz) { - unsigned char* p = img+pos; - - bool processed_deflate = false; - if (sz - pos >= 4 && - p[0] == 0x1f && p[1] == 0x8b && - p[2] == 0x08 && // deflate compression - p[3] == 0x00) { // no header flags + // 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number + if (sz - pos >= 4 && get_unaligned<uint32_t>(img->data() + pos) == 0x00088b1f) { // 'pos' is the offset of the start of a gzip chunk. size_t chunk_offset = pos; - *num_chunks += 3; - *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks, - *num_chunks * sizeof(ImageChunk))); - ImageChunk* curr = *chunks + (*num_chunks-3); - - // create a normal chunk for the header. - curr->start = pos; - curr->type = CHUNK_NORMAL; - curr->len = GZIP_HEADER_LEN; - curr->data = p; - curr->I = NULL; - - pos += curr->len; - p += curr->len; - ++curr; - - curr->type = CHUNK_DEFLATE; - curr->filename = NULL; - curr->I = NULL; + // The remaining data is too small to be a gzip chunk; treat them as a normal chunk. + if (sz - pos < GZIP_HEADER_LEN + GZIP_FOOTER_LEN) { + chunks->emplace_back(CHUNK_NORMAL, pos, img, sz - pos); + break; + } - // We must decompress this chunk in order to discover where it - // ends, and so we can put the uncompressed data and its length - // into curr->data and curr->len. + // We need three chunks for the deflated image in total, one normal chunk for the header, + // one deflated chunk for the body, and another normal chunk for the footer. + chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_HEADER_LEN); + pos += GZIP_HEADER_LEN; - size_t allocated = 32768; - curr->len = 0; - curr->data = reinterpret_cast<unsigned char*>(malloc(allocated)); - curr->start = pos; - curr->deflate_data = p; + // We must decompress this chunk in order to discover where it ends, and so we can update + // the uncompressed_data of the image body and its length. z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = sz - pos; - strm.next_in = p; + strm.next_in = img->data() + pos; // -15 means we are decoding a 'raw' deflate stream; zlib will // not expect zlib headers. int ret = inflateInit2(&strm, -15); + if (ret < 0) { + printf("failed to initialize inflate: %d\n", ret); + return false; + } + size_t allocated = BUFFER_SIZE; + std::vector<uint8_t> uncompressed_data(allocated); + size_t uncompressed_len = 0, raw_data_len = 0; do { - strm.avail_out = allocated - curr->len; - strm.next_out = curr->data + curr->len; + strm.avail_out = allocated - uncompressed_len; + strm.next_out = uncompressed_data.data() + uncompressed_len; ret = inflate(&strm, Z_NO_FLUSH); if (ret < 0) { - if (!processed_deflate) { - // This is the first chunk, assume that it's just a spurious - // gzip header instead of a real one. - break; - } - printf("Error: inflate failed [%s] at file offset [%zu]\n" - "imgdiff only supports gzip kernel compression," - " did you try CONFIG_KERNEL_LZO?\n", + printf("Warning: inflate failed [%s] at offset [%zu], treating as a normal chunk\n", strm.msg, chunk_offset); - free(img); - return NULL; + break; } - curr->len = allocated - strm.avail_out; + uncompressed_len = allocated - strm.avail_out; if (strm.avail_out == 0) { allocated *= 2; - curr->data = reinterpret_cast<unsigned char*>(realloc(curr->data, allocated)); + uncompressed_data.resize(allocated); } - processed_deflate = true; } while (ret != Z_STREAM_END); - curr->deflate_len = sz - strm.avail_in - pos; + raw_data_len = sz - strm.avail_in - pos; inflateEnd(&strm); - pos += curr->deflate_len; - p += curr->deflate_len; - ++curr; - // create a normal chunk for the footer + if (ret < 0) { + continue; + } + + ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len); + uncompressed_data.resize(uncompressed_len); + body.SetUncompressedData(std::move(uncompressed_data)); + chunks->push_back(body); - curr->type = CHUNK_NORMAL; - curr->start = pos; - curr->len = GZIP_FOOTER_LEN; - curr->data = img+pos; - curr->I = NULL; + pos += raw_data_len; - pos += curr->len; - p += curr->len; - ++curr; + // create a normal chunk for the footer + chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN); + + pos += GZIP_FOOTER_LEN; // The footer (that we just skipped over) contains the size of // the uncompressed data. Double-check to make sure that it // matches the size of the data we got when we actually did // the decompression. - size_t footer_size = Read4(p-4); - if (footer_size != curr[-2].len) { - printf("Error: footer size %zu != decompressed size %zu\n", - footer_size, curr[-2].len); - free(img); - return NULL; + size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4); + if (footer_size != body.DataLengthForPatch()) { + printf("Error: footer size %zu != decompressed size %zu\n", footer_size, + body.GetRawDataLength()); + return false; } } else { - // Reallocate the list for every chunk; we expect the number of - // chunks to be small (5 for typical boot and recovery images). - ++*num_chunks; - *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk))); - ImageChunk* curr = *chunks + (*num_chunks-1); - curr->start = pos; - curr->I = NULL; - - // 'pos' is not the offset of the start of a gzip chunk, so scan - // forward until we find a gzip header. - curr->type = CHUNK_NORMAL; - curr->data = p; - - for (curr->len = 0; curr->len < (sz - pos); ++curr->len) { - if (p[curr->len] == 0x1f && - p[curr->len+1] == 0x8b && - p[curr->len+2] == 0x08 && - p[curr->len+3] == 0x00) { + // Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect + // the number of chunks to be small (5 for typical boot and recovery images). + + // Scan forward until we find a gzip header. + size_t data_len = 0; + while (data_len + pos < sz) { + if (data_len + pos + 4 <= sz && + get_unaligned<uint32_t>(img->data() + pos + data_len) == 0x00088b1f) { break; } + data_len++; } - pos += curr->len; - } - } + chunks->emplace_back(CHUNK_NORMAL, pos, img, data_len); - return img; -} - -#define BUFFER_SIZE 32768 - -/* - * Takes the uncompressed data stored in the chunk, compresses it - * using the zlib parameters stored in the chunk, and checks that it - * matches exactly the compressed data we started with (also stored in - * the chunk). Return 0 on success. - */ -int TryReconstruction(ImageChunk* chunk, unsigned char* out) { - size_t p = 0; - -#if 0 - printf("trying %d %d %d %d %d\n", - chunk->level, chunk->method, chunk->windowBits, - chunk->memLevel, chunk->strategy); -#endif - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = chunk->len; - strm.next_in = chunk->data; - int ret; - ret = deflateInit2(&strm, chunk->level, chunk->method, chunk->windowBits, - chunk->memLevel, chunk->strategy); - do { - strm.avail_out = BUFFER_SIZE; - strm.next_out = out; - ret = deflate(&strm, Z_FINISH); - size_t have = BUFFER_SIZE - strm.avail_out; - - if (memcmp(out, chunk->deflate_data+p, have) != 0) { - // mismatch; data isn't the same. - deflateEnd(&strm); - return -1; + pos += data_len; } - p += have; - } while (ret != Z_STREAM_END); - deflateEnd(&strm); - if (p != chunk->deflate_len) { - // mismatch; ran out of data before we should have. - return -1; - } - return 0; -} - -/* - * Verify that we can reproduce exactly the same compressed data that - * we started with. Sets the level, method, windowBits, memLevel, and - * strategy fields in the chunk to the encoding parameters needed to - * produce the right output. Returns 0 on success. - */ -int ReconstructDeflateChunk(ImageChunk* chunk) { - if (chunk->type != CHUNK_DEFLATE) { - printf("attempt to reconstruct non-deflate chunk\n"); - return -1; } - size_t p = 0; - unsigned char* out = reinterpret_cast<unsigned char*>(malloc(BUFFER_SIZE)); - - // We only check two combinations of encoder parameters: level 6 - // (the default) and level 9 (the maximum). - for (chunk->level = 6; chunk->level <= 9; chunk->level += 3) { - chunk->windowBits = -15; // 32kb window; negative to indicate a raw stream. - chunk->memLevel = 8; // the default value. - chunk->method = Z_DEFLATED; - chunk->strategy = Z_DEFAULT_STRATEGY; - - if (TryReconstruction(chunk, out) == 0) { - free(out); - return 0; - } - } - - free(out); - return -1; + return true; } /* - * Given source and target chunks, compute a bsdiff patch between them - * by running bsdiff in a subprocess. Return the patch data, placing - * its length in *size. Return NULL on failure. We expect the bsdiff - * program to be in the path. + * Given source and target chunks, compute a bsdiff patch between them. + * Store the result in the patch_data. + * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk + * is used repeatedly, pass nullptr if not needed. */ -unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { - if (tgt->type == CHUNK_NORMAL) { - if (tgt->len <= 160) { - tgt->type = CHUNK_RAW; - *size = tgt->len; - return tgt->data; - } +static bool MakePatch(const ImageChunk* src, ImageChunk* tgt, std::vector<uint8_t>* patch_data, + saidx_t** bsdiff_cache) { + if (tgt->ChangeChunkToRaw(0)) { + size_t patch_size = tgt->DataLengthForPatch(); + patch_data->resize(patch_size); + std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin()); + return true; } +#if defined(__ANDROID__) + char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX"; +#else char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; - int fd = mkstemp(ptemp); +#endif + int fd = mkstemp(ptemp); if (fd == -1) { - printf("MakePatch failed to create a temporary file: %s\n", - strerror(errno)); - return NULL; + printf("MakePatch failed to create a temporary file: %s\n", strerror(errno)); + return false; } - close(fd); // temporary file is created and we don't need its file - // descriptor + close(fd); - int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp); + int r = bsdiff::bsdiff(src->DataForPatch(), src->DataLengthForPatch(), tgt->DataForPatch(), + tgt->DataLengthForPatch(), ptemp, bsdiff_cache); if (r != 0) { printf("bsdiff() failed: %d\n", r); - return NULL; + return false; } + android::base::unique_fd patch_fd(open(ptemp, O_RDONLY)); + if (patch_fd == -1) { + printf("failed to open %s: %s\n", ptemp, strerror(errno)); + return false; + } struct stat st; - if (stat(ptemp, &st) != 0) { - printf("failed to stat patch file %s: %s\n", - ptemp, strerror(errno)); - return NULL; + if (fstat(patch_fd, &st) != 0) { + printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno)); + return false; } size_t sz = static_cast<size_t>(st.st_size); - // TODO: Memory leak on error return. - unsigned char* data = reinterpret_cast<unsigned char*>(malloc(sz)); - - if (tgt->type == CHUNK_NORMAL && tgt->len <= sz) { + // Change the chunk type to raw if the patch takes less space that way. + if (tgt->ChangeChunkToRaw(sz)) { unlink(ptemp); - - tgt->type = CHUNK_RAW; - *size = tgt->len; - return tgt->data; - } - - *size = sz; - - FILE* f = fopen(ptemp, "rb"); - if (f == NULL) { - printf("failed to open patch %s: %s\n", ptemp, strerror(errno)); - return NULL; + size_t patch_size = tgt->DataLengthForPatch(); + patch_data->resize(patch_size); + std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin()); + return true; } - if (fread(data, 1, sz, f) != sz) { - printf("failed to read patch %s: %s\n", ptemp, strerror(errno)); - return NULL; + patch_data->resize(sz); + if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) { + printf("failed to read \"%s\" %s\n", ptemp, strerror(errno)); + return false; } - fclose(f); unlink(ptemp); + tgt->SetSourceInfo(*src); - tgt->source_start = src->start; - switch (tgt->type) { - case CHUNK_NORMAL: - tgt->source_len = src->len; - break; - case CHUNK_DEFLATE: - tgt->source_len = src->deflate_len; - tgt->source_uncompressed_len = src->len; - break; - } - - return data; -} - -/* - * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob - * of uninterpreted data). The resulting patch will likely be about - * as big as the target file, but it lets us handle the case of images - * where some gzip chunks are reconstructible but others aren't (by - * treating the ones that aren't as normal chunks). - */ -void ChangeDeflateChunkToNormal(ImageChunk* ch) { - if (ch->type != CHUNK_DEFLATE) return; - ch->type = CHUNK_NORMAL; - free(ch->data); - ch->data = ch->deflate_data; - ch->len = ch->deflate_len; -} - -/* - * Return true if the data in the chunk is identical (including the - * compressed representation, for gzip chunks). - */ -int AreChunksEqual(ImageChunk* a, ImageChunk* b) { - if (a->type != b->type) return 0; - - switch (a->type) { - case CHUNK_NORMAL: - return a->len == b->len && memcmp(a->data, b->data, a->len) == 0; - - case CHUNK_DEFLATE: - return a->deflate_len == b->deflate_len && - memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0; - - default: - printf("unknown chunk type %d\n", a->type); - return 0; - } + return true; } /* @@ -740,137 +809,103 @@ int AreChunksEqual(ImageChunk* a, ImageChunk* b) { * a single chunk. (Such runs can be produced when deflate chunks are * changed to normal chunks.) */ -void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) { - int out = 0; - int in_start = 0, in_end; - while (in_start < *num_chunks) { - if (chunks[in_start].type != CHUNK_NORMAL) { - in_end = in_start+1; - } else { - // in_start is a normal chunk. Look for a run of normal chunks - // that constitute a solid block of data (ie, each chunk begins - // where the previous one ended). - for (in_end = in_start+1; - in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL && - (chunks[in_end].start == - chunks[in_end-1].start + chunks[in_end-1].len && - chunks[in_end].data == - chunks[in_end-1].data + chunks[in_end-1].len); - ++in_end); +static void MergeAdjacentNormalChunks(std::vector<ImageChunk>* chunks) { + size_t merged_last = 0, cur = 0; + while (cur < chunks->size()) { + // Look for normal chunks adjacent to the current one. If such chunk exists, extend the + // length of the current normal chunk. + size_t to_check = cur + 1; + while (to_check < chunks->size() && chunks->at(cur).IsAdjacentNormal(chunks->at(to_check))) { + chunks->at(cur).MergeAdjacentNormal(chunks->at(to_check)); + to_check++; } - if (in_end == in_start+1) { -#if 0 - printf("chunk %d is now %d\n", in_start, out); -#endif - if (out != in_start) { - memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk)); - } - } else { -#if 0 - printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out); -#endif - - // Merge chunks [in_start, in_end-1] into one chunk. Since the - // data member of each chunk is just a pointer into an in-memory - // copy of the file, this can be done without recopying (the - // output chunk has the first chunk's start location and data - // pointer, and length equal to the sum of the input chunk - // lengths). - chunks[out].type = CHUNK_NORMAL; - chunks[out].start = chunks[in_start].start; - chunks[out].data = chunks[in_start].data; - chunks[out].len = chunks[in_end-1].len + - (chunks[in_end-1].start - chunks[in_start].start); + if (merged_last != cur) { + chunks->at(merged_last) = std::move(chunks->at(cur)); } - - ++out; - in_start = in_end; + merged_last++; + cur = to_check; + } + if (merged_last < chunks->size()) { + chunks->erase(chunks->begin() + merged_last, chunks->end()); } - *num_chunks = out; } -ImageChunk* FindChunkByName(const char* name, - ImageChunk* chunks, int num_chunks) { - int i; - for (i = 0; i < num_chunks; ++i) { - if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename && - strcmp(name, chunks[i].filename) == 0) { - return chunks+i; +static ImageChunk* FindChunkByName(const std::string& name, std::vector<ImageChunk>& chunks) { + for (size_t i = 0; i < chunks.size(); ++i) { + if (chunks[i].GetType() == CHUNK_DEFLATE && chunks[i].GetEntryName() == name) { + return &chunks[i]; } } - return NULL; + return nullptr; } -void DumpChunks(ImageChunk* chunks, int num_chunks) { - for (int i = 0; i < num_chunks; ++i) { - printf("chunk %d: type %d start %zu len %zu\n", - i, chunks[i].type, chunks[i].start, chunks[i].len); - } +static void DumpChunks(const std::vector<ImageChunk>& chunks) { + for (size_t i = 0; i < chunks.size(); ++i) { + printf("chunk %zu: ", i); + chunks[i].Dump(); + } } -int main(int argc, char** argv) { - int zip_mode = 0; +int imgdiff(int argc, const char** argv) { + bool zip_mode = false; if (argc >= 2 && strcmp(argv[1], "-z") == 0) { - zip_mode = 1; + zip_mode = true; --argc; ++argv; } - size_t bonus_size = 0; - unsigned char* bonus_data = NULL; + std::vector<uint8_t> bonus_data; if (argc >= 3 && strcmp(argv[1], "-b") == 0) { - struct stat st; - if (stat(argv[2], &st) != 0) { - printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); + android::base::unique_fd fd(open(argv[2], O_RDONLY)); + if (fd == -1) { + printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno)); return 1; } - bonus_size = st.st_size; - bonus_data = reinterpret_cast<unsigned char*>(malloc(bonus_size)); - FILE* f = fopen(argv[2], "rb"); - if (f == NULL) { - printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno)); + struct stat st; + if (fstat(fd, &st) != 0) { + printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno)); return 1; } - if (fread(bonus_data, 1, bonus_size, f) != bonus_size) { + + size_t bonus_size = st.st_size; + bonus_data.resize(bonus_size); + if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) { printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno)); return 1; } - fclose(f); argc -= 2; argv += 2; } if (argc != 4) { - usage: printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n", argv[0]); return 2; } - int num_src_chunks; - ImageChunk* src_chunks; - int num_tgt_chunks; - ImageChunk* tgt_chunks; - int i; + std::vector<ImageChunk> src_chunks; + std::vector<ImageChunk> tgt_chunks; + std::vector<uint8_t> src_file; + std::vector<uint8_t> tgt_file; if (zip_mode) { - if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) { + if (!ReadZip(argv[1], &src_chunks, &src_file, true)) { printf("failed to break apart source zip file\n"); return 1; } - if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) { + if (!ReadZip(argv[2], &tgt_chunks, &tgt_file, false)) { printf("failed to break apart target zip file\n"); return 1; } } else { - if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) { + if (!ReadImage(argv[1], &src_chunks, &src_file)) { printf("failed to break apart source image\n"); return 1; } - if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) { + if (!ReadImage(argv[2], &tgt_chunks, &tgt_file)) { printf("failed to break apart target image\n"); return 1; } @@ -878,51 +913,47 @@ int main(int argc, char** argv) { // Verify that the source and target images have the same chunk // structure (ie, the same sequence of deflate and normal chunks). - if (!zip_mode) { - // Merge the gzip header and footer in with any adjacent - // normal chunks. - MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); - MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); - } + // Merge the gzip header and footer in with any adjacent normal chunks. + MergeAdjacentNormalChunks(&tgt_chunks); + MergeAdjacentNormalChunks(&src_chunks); - if (num_src_chunks != num_tgt_chunks) { + if (src_chunks.size() != tgt_chunks.size()) { printf("source and target don't have same number of chunks!\n"); printf("source chunks:\n"); - DumpChunks(src_chunks, num_src_chunks); + DumpChunks(src_chunks); printf("target chunks:\n"); - DumpChunks(tgt_chunks, num_tgt_chunks); + DumpChunks(tgt_chunks); return 1; } - for (i = 0; i < num_src_chunks; ++i) { - if (src_chunks[i].type != tgt_chunks[i].type) { - printf("source and target don't have same chunk " - "structure! (chunk %d)\n", i); + for (size_t i = 0; i < src_chunks.size(); ++i) { + if (src_chunks[i].GetType() != tgt_chunks[i].GetType()) { + printf("source and target don't have same chunk structure! (chunk %zu)\n", i); printf("source chunks:\n"); - DumpChunks(src_chunks, num_src_chunks); + DumpChunks(src_chunks); printf("target chunks:\n"); - DumpChunks(tgt_chunks, num_tgt_chunks); + DumpChunks(tgt_chunks); return 1; } } } - for (i = 0; i < num_tgt_chunks; ++i) { - if (tgt_chunks[i].type == CHUNK_DEFLATE) { + for (size_t i = 0; i < tgt_chunks.size(); ++i) { + if (tgt_chunks[i].GetType() == CHUNK_DEFLATE) { // Confirm that given the uncompressed chunk data in the target, we // can recompress it and get exactly the same bits as are in the // input target image. If this fails, treat the chunk as a normal // non-deflated chunk. - if (ReconstructDeflateChunk(tgt_chunks+i) < 0) { - printf("failed to reconstruct target deflate chunk %d [%s]; " - "treating as normal\n", i, tgt_chunks[i].filename); - ChangeDeflateChunkToNormal(tgt_chunks+i); + if (!tgt_chunks[i].ReconstructDeflateChunk()) { + printf("failed to reconstruct target deflate chunk %zu [%s]; treating as normal\n", i, + tgt_chunks[i].GetEntryName().c_str()); + tgt_chunks[i].ChangeDeflateChunkToNormal(); if (zip_mode) { - ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); - if (src) { - ChangeDeflateChunkToNormal(src); + ImageChunk* src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks); + if (src != nullptr) { + src->ChangeDeflateChunkToNormal(); } } else { - ChangeDeflateChunkToNormal(src_chunks+i); + src_chunks[i].ChangeDeflateChunkToNormal(); } continue; } @@ -935,16 +966,16 @@ int main(int argc, char** argv) { // data. ImageChunk* src; if (zip_mode) { - src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks); + src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks); } else { - src = src_chunks+i; + src = &src_chunks[i]; } - if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) { - ChangeDeflateChunkToNormal(tgt_chunks+i); - if (src) { - ChangeDeflateChunkToNormal(src); - } + if (src == nullptr) { + tgt_chunks[i].ChangeDeflateChunkToNormal(); + } else if (tgt_chunks[i] == *src) { + tgt_chunks[i].ChangeDeflateChunkToNormal(); + src->ChangeDeflateChunkToNormal(); } } } @@ -954,14 +985,15 @@ int main(int argc, char** argv) { // For zips, we only need to do this to the target: deflated // chunks are matched via filename, and normal chunks are patched // using the entire source file as the source. - MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); + MergeAdjacentNormalChunks(&tgt_chunks); + } else { // For images, we need to maintain the parallel structure of the // chunk lists, so do the merging in both the source and target // lists. - MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); - MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); - if (num_src_chunks != num_tgt_chunks) { + MergeAdjacentNormalChunks(&tgt_chunks); + MergeAdjacentNormalChunks(&src_chunks); + if (src_chunks.size() != tgt_chunks.size()) { // This shouldn't happen. printf("merging normal chunks went awry\n"); return 1; @@ -971,35 +1003,43 @@ int main(int argc, char** argv) { // Compute bsdiff patches for each chunk's data (the uncompressed // data, in the case of deflate chunks). - DumpChunks(src_chunks, num_src_chunks); + DumpChunks(src_chunks); - printf("Construct patches for %d chunks...\n", num_tgt_chunks); - unsigned char** patch_data = reinterpret_cast<unsigned char**>(malloc( - num_tgt_chunks * sizeof(unsigned char*))); - size_t* patch_size = reinterpret_cast<size_t*>(malloc(num_tgt_chunks * sizeof(size_t))); - for (i = 0; i < num_tgt_chunks; ++i) { + printf("Construct patches for %zu chunks...\n", tgt_chunks.size()); + std::vector<std::vector<uint8_t>> patch_data(tgt_chunks.size()); + saidx_t* bsdiff_cache = nullptr; + for (size_t i = 0; i < tgt_chunks.size(); ++i) { if (zip_mode) { ImageChunk* src; - if (tgt_chunks[i].type == CHUNK_DEFLATE && - (src = FindChunkByName(tgt_chunks[i].filename, src_chunks, - num_src_chunks))) { - patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i); + if (tgt_chunks[i].GetType() == CHUNK_DEFLATE && + (src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks))) { + if (!MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr)) { + printf("Failed to generate patch for target chunk %zu: ", i); + return 1; + } } else { - patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i); + if (!MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache)) { + printf("Failed to generate patch for target chunk %zu: ", i); + return 1; + } } } else { - if (i == 1 && bonus_data) { - printf(" using %zu bytes of bonus data for chunk %d\n", bonus_size, i); - src_chunks[i].data = reinterpret_cast<unsigned char*>(realloc(src_chunks[i].data, - src_chunks[i].len + bonus_size)); - memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size); - src_chunks[i].len += bonus_size; - } - - patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i); + if (i == 1 && !bonus_data.empty()) { + printf(" using %zu bytes of bonus data for chunk %zu\n", bonus_data.size(), i); + src_chunks[i].SetBonusData(bonus_data); + } + + if (!MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr)) { + printf("Failed to generate patch for target chunk %zu: ", i); + return 1; + } } - printf("patch %3d is %zu bytes (of %zu)\n", - i, patch_size[i], tgt_chunks[i].source_len); + printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data[i].size(), + src_chunks[i].GetRawDataLength()); + } + + if (bsdiff_cache != nullptr) { + free(bsdiff_cache); } // Figure out how big the imgdiff file header is going to be, so @@ -1007,77 +1047,38 @@ int main(int argc, char** argv) { // within the file. size_t total_header_size = 12; - for (i = 0; i < num_tgt_chunks; ++i) { - total_header_size += 4; - switch (tgt_chunks[i].type) { - case CHUNK_NORMAL: - total_header_size += 8*3; - break; - case CHUNK_DEFLATE: - total_header_size += 8*5 + 4*5; - break; - case CHUNK_RAW: - total_header_size += 4 + patch_size[i]; - break; - } + for (size_t i = 0; i < tgt_chunks.size(); ++i) { + total_header_size += tgt_chunks[i].GetHeaderSize(patch_data[i].size()); } size_t offset = total_header_size; - FILE* f = fopen(argv[3], "wb"); + android::base::unique_fd patch_fd(open(argv[3], O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)); + if (patch_fd == -1) { + printf("failed to open \"%s\": %s\n", argv[3], strerror(errno)); + return 1; + } // Write out the headers. - - fwrite("IMGDIFF2", 1, 8, f); - Write4(num_tgt_chunks, f); - for (i = 0; i < num_tgt_chunks; ++i) { - Write4(tgt_chunks[i].type, f); - - switch (tgt_chunks[i].type) { - case CHUNK_NORMAL: - printf("chunk %3d: normal (%10zu, %10zu) %10zu\n", i, - tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]); - Write8(tgt_chunks[i].source_start, f); - Write8(tgt_chunks[i].source_len, f); - Write8(offset, f); - offset += patch_size[i]; - break; - - case CHUNK_DEFLATE: - printf("chunk %3d: deflate (%10zu, %10zu) %10zu %s\n", i, - tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i], - tgt_chunks[i].filename); - Write8(tgt_chunks[i].source_start, f); - Write8(tgt_chunks[i].source_len, f); - Write8(offset, f); - Write8(tgt_chunks[i].source_uncompressed_len, f); - Write8(tgt_chunks[i].len, f); - Write4(tgt_chunks[i].level, f); - Write4(tgt_chunks[i].method, f); - Write4(tgt_chunks[i].windowBits, f); - Write4(tgt_chunks[i].memLevel, f); - Write4(tgt_chunks[i].strategy, f); - offset += patch_size[i]; - break; - - case CHUNK_RAW: - printf("chunk %3d: raw (%10zu, %10zu)\n", i, - tgt_chunks[i].start, tgt_chunks[i].len); - Write4(patch_size[i], f); - fwrite(patch_data[i], 1, patch_size[i], f); - break; - } + if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) { + printf("failed to write \"IMGDIFF2\" to \"%s\": %s\n", argv[3], strerror(errno)); + return 1; + } + Write4(patch_fd, static_cast<int32_t>(tgt_chunks.size())); + for (size_t i = 0; i < tgt_chunks.size(); ++i) { + printf("chunk %zu: ", i); + offset = tgt_chunks[i].WriteHeaderToFd(patch_fd, patch_data[i], offset); } // Append each chunk's bsdiff patch, in order. - - for (i = 0; i < num_tgt_chunks; ++i) { - if (tgt_chunks[i].type != CHUNK_RAW) { - fwrite(patch_data[i], 1, patch_size[i], f); + for (size_t i = 0; i < tgt_chunks.size(); ++i) { + if (tgt_chunks[i].GetType() != CHUNK_RAW) { + if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) { + CHECK(false) << "failed to write " << patch_data[i].size() << " bytes patch for chunk " + << i; + } } } - fclose(f); - return 0; } diff --git a/applypatch/imgdiff_main.cpp b/applypatch/imgdiff_main.cpp new file mode 100644 index 000000000..7d5bdf9aa --- /dev/null +++ b/applypatch/imgdiff_main.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016 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 "applypatch/imgdiff.h" + +int main(int argc, char** argv) { + return imgdiff(argc, const_cast<const char**>(argv)); +} diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp index d175d6385..adcc61fd6 100644 --- a/applypatch/imgpatch.cpp +++ b/applypatch/imgpatch.cpp @@ -14,31 +14,41 @@ * limitations under the License. */ -// See imgdiff.c in this directory for a description of the patch file +// See imgdiff.cpp in this directory for a description of the patch file // format. +#include <applypatch/imgpatch.h> + +#include <errno.h> #include <stdio.h> +#include <string.h> #include <sys/cdefs.h> #include <sys/stat.h> -#include <errno.h> #include <unistd.h> -#include <string.h> +#include <string> #include <vector> -#include "zlib.h" -#include "openssl/sha.h" -#include "applypatch.h" -#include "imgdiff.h" -#include "utils.h" +#include <applypatch/applypatch.h> +#include <applypatch/imgdiff.h> +#include <android-base/memory.h> +#include <openssl/sha.h> +#include <zlib.h> + +static inline int64_t Read8(const void *address) { + return android::base::get_unaligned<int64_t>(address); +} + +static inline int32_t Read4(const void *address) { + return android::base::get_unaligned<int32_t>(address); +} int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const unsigned char* patch_data, ssize_t patch_size, SinkFn sink, void* token) { - Value patch = {VAL_BLOB, patch_size, - reinterpret_cast<char*>(const_cast<unsigned char*>(patch_data))}; - return ApplyImagePatch( - old_data, old_size, &patch, sink, token, nullptr, nullptr); + Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size)); + + return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr); } /* @@ -47,203 +57,204 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, * file, and update the SHA context with the output data as well. * Return 0 on success. */ -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx, - const Value* bonus_data) { - ssize_t pos = 12; - char* header = patch->data; - if (patch->size < 12) { - printf("patch too short to contain header\n"); - return -1; +int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, + SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) { + if (patch->data.size() < 12) { + printf("patch too short to contain header\n"); + return -1; + } + + // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. + // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and + // CHUNK_GZIP.) + size_t pos = 12; + const char* header = &patch->data[0]; + if (memcmp(header, "IMGDIFF2", 8) != 0) { + printf("corrupt patch file header (magic number)\n"); + return -1; + } + + int num_chunks = Read4(header + 8); + + for (int i = 0; i < num_chunks; ++i) { + // each chunk's header record starts with 4 bytes. + if (pos + 4 > patch->data.size()) { + printf("failed to read chunk %d record\n", i); + return -1; } + int type = Read4(&patch->data[pos]); + pos += 4; - // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. - // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and - // CHUNK_GZIP.) - if (memcmp(header, "IMGDIFF2", 8) != 0) { - printf("corrupt patch file header (magic number)\n"); + if (type == CHUNK_NORMAL) { + const char* normal_header = &patch->data[pos]; + pos += 24; + if (pos > patch->data.size()) { + printf("failed to read chunk %d normal header data\n", i); return -1; - } + } - int num_chunks = Read4(header+8); + size_t src_start = static_cast<size_t>(Read8(normal_header)); + size_t src_len = static_cast<size_t>(Read8(normal_header + 8)); + size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16)); - int i; - for (i = 0; i < num_chunks; ++i) { - // each chunk's header record starts with 4 bytes. - if (pos + 4 > patch->size) { - printf("failed to read chunk %d record\n", i); - return -1; + if (src_start + src_len > static_cast<size_t>(old_size)) { + printf("source data too short\n"); + return -1; + } + ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx); + } else if (type == CHUNK_RAW) { + const char* raw_header = &patch->data[pos]; + pos += 4; + if (pos > patch->data.size()) { + printf("failed to read chunk %d raw header data\n", i); + return -1; + } + + ssize_t data_len = Read4(raw_header); + + if (pos + data_len > patch->data.size()) { + printf("failed to read chunk %d raw data\n", i); + return -1; + } + if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len); + if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len, token) != + data_len) { + printf("failed to write chunk %d raw data\n", i); + return -1; + } + pos += data_len; + } else if (type == CHUNK_DEFLATE) { + // deflate chunks have an additional 60 bytes in their chunk header. + const char* deflate_header = &patch->data[pos]; + pos += 60; + if (pos > patch->data.size()) { + printf("failed to read chunk %d deflate header data\n", i); + return -1; + } + + size_t src_start = static_cast<size_t>(Read8(deflate_header)); + size_t src_len = static_cast<size_t>(Read8(deflate_header + 8)); + size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16)); + size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24)); + size_t target_len = static_cast<size_t>(Read8(deflate_header + 32)); + int level = Read4(deflate_header + 40); + int method = Read4(deflate_header + 44); + int windowBits = Read4(deflate_header + 48); + int memLevel = Read4(deflate_header + 52); + int strategy = Read4(deflate_header + 56); + + if (src_start + src_len > static_cast<size_t>(old_size)) { + printf("source data too short\n"); + return -1; + } + + // Decompress the source data; the chunk header tells us exactly + // how big we expect it to be when decompressed. + + // Note: expanded_len will include the bonus data size if + // the patch was constructed with bonus data. The + // deflation will come up 'bonus_size' bytes short; these + // must be appended from the bonus_data value. + size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0; + + std::vector<unsigned char> expanded_source(expanded_len); + + // inflate() doesn't like strm.next_out being a nullptr even with + // avail_out being zero (Z_STREAM_ERROR). + if (expanded_len != 0) { + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = src_len; + strm.next_in = old_data + src_start; + strm.avail_out = expanded_len; + strm.next_out = expanded_source.data(); + + int ret = inflateInit2(&strm, -15); + if (ret != Z_OK) { + printf("failed to init source inflation: %d\n", ret); + return -1; } - int type = Read4(patch->data + pos); - pos += 4; - - if (type == CHUNK_NORMAL) { - char* normal_header = patch->data + pos; - pos += 24; - if (pos > patch->size) { - printf("failed to read chunk %d normal header data\n", i); - return -1; - } - - size_t src_start = Read8(normal_header); - size_t src_len = Read8(normal_header+8); - size_t patch_offset = Read8(normal_header+16); - - if (src_start + src_len > static_cast<size_t>(old_size)) { - printf("source data too short\n"); - return -1; - } - ApplyBSDiffPatch(old_data + src_start, src_len, - patch, patch_offset, sink, token, ctx); - } else if (type == CHUNK_RAW) { - char* raw_header = patch->data + pos; - pos += 4; - if (pos > patch->size) { - printf("failed to read chunk %d raw header data\n", i); - return -1; - } - - ssize_t data_len = Read4(raw_header); - - if (pos + data_len > patch->size) { - printf("failed to read chunk %d raw data\n", i); - return -1; - } - if (ctx) SHA1_Update(ctx, patch->data + pos, data_len); - if (sink((unsigned char*)patch->data + pos, - data_len, token) != data_len) { - printf("failed to write chunk %d raw data\n", i); - return -1; - } - pos += data_len; - } else if (type == CHUNK_DEFLATE) { - // deflate chunks have an additional 60 bytes in their chunk header. - char* deflate_header = patch->data + pos; - pos += 60; - if (pos > patch->size) { - printf("failed to read chunk %d deflate header data\n", i); - return -1; - } - - size_t src_start = Read8(deflate_header); - size_t src_len = Read8(deflate_header+8); - size_t patch_offset = Read8(deflate_header+16); - size_t expanded_len = Read8(deflate_header+24); - int level = Read4(deflate_header+40); - int method = Read4(deflate_header+44); - int windowBits = Read4(deflate_header+48); - int memLevel = Read4(deflate_header+52); - int strategy = Read4(deflate_header+56); - - if (src_start + src_len > static_cast<size_t>(old_size)) { - printf("source data too short\n"); - return -1; - } - - // Decompress the source data; the chunk header tells us exactly - // how big we expect it to be when decompressed. - - // Note: expanded_len will include the bonus data size if - // the patch was constructed with bonus data. The - // deflation will come up 'bonus_size' bytes short; these - // must be appended from the bonus_data value. - size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0; - - std::vector<unsigned char> expanded_source(expanded_len); - - // inflate() doesn't like strm.next_out being a nullptr even with - // avail_out being zero (Z_STREAM_ERROR). - if (expanded_len != 0) { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = src_len; - strm.next_in = (unsigned char*)(old_data + src_start); - strm.avail_out = expanded_len; - strm.next_out = expanded_source.data(); - - int ret; - ret = inflateInit2(&strm, -15); - if (ret != Z_OK) { - printf("failed to init source inflation: %d\n", ret); - return -1; - } - - // Because we've provided enough room to accommodate the output - // data, we expect one call to inflate() to suffice. - ret = inflate(&strm, Z_SYNC_FLUSH); - if (ret != Z_STREAM_END) { - printf("source inflation returned %d\n", ret); - return -1; - } - // We should have filled the output buffer exactly, except - // for the bonus_size. - if (strm.avail_out != bonus_size) { - printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size); - return -1; - } - inflateEnd(&strm); - - if (bonus_size) { - memcpy(expanded_source.data() + (expanded_len - bonus_size), - bonus_data->data, bonus_size); - } - } - - // Next, apply the bsdiff patch (in memory) to the uncompressed - // data. - std::vector<unsigned char> uncompressed_target_data; - if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, - patch, patch_offset, - &uncompressed_target_data) != 0) { - return -1; - } - - // Now compress the target data and append it to the output. - - // we're done with the expanded_source data buffer, so we'll - // reuse that memory to receive the output of deflate. - if (expanded_source.size() < 32768U) { - expanded_source.resize(32768U); - } - - { - std::vector<unsigned char>& temp_data = expanded_source; - - // now the deflate stream - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = uncompressed_target_data.size(); - strm.next_in = uncompressed_target_data.data(); - int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); - if (ret != Z_OK) { - printf("failed to init uncompressed data deflation: %d\n", ret); - return -1; - } - do { - strm.avail_out = temp_data.size(); - strm.next_out = temp_data.data(); - ret = deflate(&strm, Z_FINISH); - ssize_t have = temp_data.size() - strm.avail_out; - - if (sink(temp_data.data(), have, token) != have) { - printf("failed to write %ld compressed bytes to output\n", - (long)have); - return -1; - } - if (ctx) SHA1_Update(ctx, temp_data.data(), have); - } while (ret != Z_STREAM_END); - deflateEnd(&strm); - } - } else { - printf("patch chunk %d is unknown type %d\n", i, type); - return -1; + + // Because we've provided enough room to accommodate the output + // data, we expect one call to inflate() to suffice. + ret = inflate(&strm, Z_SYNC_FLUSH); + if (ret != Z_STREAM_END) { + printf("source inflation returned %d\n", ret); + return -1; + } + // We should have filled the output buffer exactly, except + // for the bonus_size. + if (strm.avail_out != bonus_size) { + printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size); + return -1; } + inflateEnd(&strm); + + if (bonus_size) { + memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0], + bonus_size); + } + } + + // Next, apply the bsdiff patch (in memory) to the uncompressed data. + std::vector<unsigned char> uncompressed_target_data; + // TODO(senj): Remove the only usage of ApplyBSDiffPatchMem here, + // replace it with ApplyBSDiffPatch with a custom sink function that + // wraps the given sink function to stream output to save memory. + if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset, + &uncompressed_target_data) != 0) { + return -1; + } + if (uncompressed_target_data.size() != target_len) { + printf("expected target len to be %zu, but it's %zu\n", target_len, + uncompressed_target_data.size()); + return -1; + } + + // Now compress the target data and append it to the output. + + // we're done with the expanded_source data buffer, so we'll + // reuse that memory to receive the output of deflate. + if (expanded_source.size() < 32768U) { + expanded_source.resize(32768U); + } + + { + std::vector<unsigned char>& temp_data = expanded_source; + + // now the deflate stream + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = uncompressed_target_data.size(); + strm.next_in = uncompressed_target_data.data(); + int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); + if (ret != Z_OK) { + printf("failed to init uncompressed data deflation: %d\n", ret); + return -1; + } + do { + strm.avail_out = temp_data.size(); + strm.next_out = temp_data.data(); + ret = deflate(&strm, Z_FINISH); + ssize_t have = temp_data.size() - strm.avail_out; + + if (sink(temp_data.data(), have, token) != have) { + printf("failed to write %zd compressed bytes to output\n", have); + return -1; + } + if (ctx) SHA1_Update(ctx, temp_data.data(), have); + } while (ret != Z_STREAM_END); + deflateEnd(&strm); + } + } else { + printf("patch chunk %d is unknown type %d\n", i, type); + return -1; } + } - return 0; + return 0; } diff --git a/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h index f392c5534..4489decb6 100644 --- a/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -17,11 +17,15 @@ #ifndef _APPLYPATCH_H #define _APPLYPATCH_H +#include <stdint.h> #include <sys/stat.h> +#include <memory> +#include <string> #include <vector> -#include "openssl/sha.h" +#include <openssl/sha.h> + #include "edify/expr.h" struct FileContents { @@ -39,33 +43,28 @@ struct FileContents { typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); -// applypatch.c +// applypatch.cpp int ShowLicenses(); size_t FreeSpaceForFile(const char* filename); int CacheSizeCheck(size_t bytes); int ParseSha1(const char* str, uint8_t* digest); -int applypatch_flash(const char* source_filename, const char* target_filename, - const char* target_sha1_str, size_t target_size); int applypatch(const char* source_filename, const char* target_filename, const char* target_sha1_str, size_t target_size, - int num_patches, - char** const patch_sha1_str, - Value** patch_data, - Value* bonus_data); + const std::vector<std::string>& patch_sha1_str, + const std::vector<std::unique_ptr<Value>>& patch_data, + const Value* bonus_data); int applypatch_check(const char* filename, - int num_patches, - char** const patch_sha1_str); + const std::vector<std::string>& patch_sha1_str); +int applypatch_flash(const char* source_filename, const char* target_filename, + const char* target_sha1_str, size_t target_size); int LoadFileContents(const char* filename, FileContents* file); int SaveFileContents(const char* filename, const FileContents* file); -void FreeFileContents(FileContents* file); -int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str, - int num_patches); -// bsdiff.cpp +// bspatch.cpp void ShowBSDiffLicense(); int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, ssize_t patch_offset, diff --git a/applypatch/imgdiff.h b/applypatch/include/applypatch/imgdiff.h index f2069b4f3..22cbd4fa0 100644 --- a/applypatch/imgdiff.h +++ b/applypatch/include/applypatch/imgdiff.h @@ -14,17 +14,26 @@ * limitations under the License. */ +#ifndef _APPLYPATCH_IMGDIFF_H +#define _APPLYPATCH_IMGDIFF_H + +#include <stddef.h> + // Image patch chunk types -#define CHUNK_NORMAL 0 -#define CHUNK_GZIP 1 // version 1 only -#define CHUNK_DEFLATE 2 // version 2 only -#define CHUNK_RAW 3 // version 2 only +#define CHUNK_NORMAL 0 +#define CHUNK_GZIP 1 // version 1 only +#define CHUNK_DEFLATE 2 // version 2 only +#define CHUNK_RAW 3 // version 2 only // The gzip header size is actually variable, but we currently don't // support gzipped data with any of the optional fields, so for now it // will always be ten bytes. See RFC 1952 for the definition of the // gzip format. -#define GZIP_HEADER_LEN 10 +static constexpr size_t GZIP_HEADER_LEN = 10; // The gzip footer size really is fixed. -#define GZIP_FOOTER_LEN 8 +static constexpr size_t GZIP_FOOTER_LEN = 8; + +int imgdiff(int argc, const char** argv); + +#endif // _APPLYPATCH_IMGDIFF_H diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h index 64d9aa9eb..6549f79f0 100644 --- a/applypatch/include/applypatch/imgpatch.h +++ b/applypatch/include/applypatch/imgpatch.h @@ -14,13 +14,15 @@ * limitations under the License. */ -#ifndef _IMGPATCH_H -#define _IMGPATCH_H +#ifndef _APPLYPATCH_IMGPATCH_H +#define _APPLYPATCH_IMGPATCH_H -typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); +#include <sys/types.h> + +using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*); int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const unsigned char* patch_data, ssize_t patch_size, SinkFn sink, void* token); -#endif //_IMGPATCH_H +#endif // _APPLYPATCH_IMGPATCH_H diff --git a/applypatch/libimgpatch.pc b/applypatch/libimgpatch.pc new file mode 100644 index 000000000..e5002934f --- /dev/null +++ b/applypatch/libimgpatch.pc @@ -0,0 +1,6 @@ +# This file is for libimgpatch in Chrome OS. + +Name: libimgpatch +Description: Apply imgdiff patch +Version: 0.0.1 +Libs: -limgpatch -lbz2 -lz diff --git a/applypatch/utils.cpp b/applypatch/utils.cpp deleted file mode 100644 index 4a80be75f..000000000 --- a/applypatch/utils.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2009 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 <stdio.h> - -#include "utils.h" - -/** Write a 4-byte value to f in little-endian order. */ -void Write4(int value, FILE* f) { - fputc(value & 0xff, f); - fputc((value >> 8) & 0xff, f); - fputc((value >> 16) & 0xff, f); - fputc((value >> 24) & 0xff, f); -} - -/** Write an 8-byte value to f in little-endian order. */ -void Write8(long long value, FILE* f) { - fputc(value & 0xff, f); - fputc((value >> 8) & 0xff, f); - fputc((value >> 16) & 0xff, f); - fputc((value >> 24) & 0xff, f); - fputc((value >> 32) & 0xff, f); - fputc((value >> 40) & 0xff, f); - fputc((value >> 48) & 0xff, f); - fputc((value >> 56) & 0xff, f); -} - -int Read2(void* pv) { - unsigned char* p = reinterpret_cast<unsigned char*>(pv); - return (int)(((unsigned int)p[1] << 8) | - (unsigned int)p[0]); -} - -int Read4(void* pv) { - unsigned char* p = reinterpret_cast<unsigned char*>(pv); - return (int)(((unsigned int)p[3] << 24) | - ((unsigned int)p[2] << 16) | - ((unsigned int)p[1] << 8) | - (unsigned int)p[0]); -} - -long long Read8(void* pv) { - unsigned char* p = reinterpret_cast<unsigned char*>(pv); - return (long long)(((unsigned long long)p[7] << 56) | - ((unsigned long long)p[6] << 48) | - ((unsigned long long)p[5] << 40) | - ((unsigned long long)p[4] << 32) | - ((unsigned long long)p[3] << 24) | - ((unsigned long long)p[2] << 16) | - ((unsigned long long)p[1] << 8) | - (unsigned long long)p[0]); -} diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp index e7aef781c..285214f16 100644 --- a/asn1_decoder.cpp +++ b/asn1_decoder.cpp @@ -14,178 +14,145 @@ * limitations under the License. */ -#include <malloc.h> -#include <stdint.h> -#include <string.h> - #include "asn1_decoder.h" +#include <stdint.h> -typedef struct asn1_context { - size_t length; - uint8_t* p; - int app_type; -} asn1_context_t; - - -static const int kMaskConstructed = 0xE0; -static const int kMaskTag = 0x7F; -static const int kMaskAppType = 0x1F; - -static const int kTagOctetString = 0x04; -static const int kTagOid = 0x06; -static const int kTagSequence = 0x30; -static const int kTagSet = 0x31; -static const int kTagConstructed = 0xA0; - -asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) { - asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t)); - if (ctx == NULL) { - return NULL; - } - ctx->p = buffer; - ctx->length = length; - return ctx; -} - -void asn1_context_free(asn1_context_t* ctx) { - free(ctx); +int asn1_context::peek_byte() const { + if (length_ == 0) { + return -1; + } + return *p_; } -static inline int peek_byte(asn1_context_t* ctx) { - if (ctx->length <= 0) { - return -1; - } - return *ctx->p; -} +int asn1_context::get_byte() { + if (length_ == 0) { + return -1; + } -static inline int get_byte(asn1_context_t* ctx) { - if (ctx->length <= 0) { - return -1; - } - int byte = *ctx->p; - ctx->p++; - ctx->length--; - return byte; + int byte = *p_; + p_++; + length_--; + return byte; } -static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) { - if (ctx->length < num_skip) { - return false; - } - ctx->p += num_skip; - ctx->length -= num_skip; - return true; +bool asn1_context::skip_bytes(size_t num_skip) { + if (length_ < num_skip) { + return false; + } + p_ += num_skip; + length_ -= num_skip; + return true; } -static bool decode_length(asn1_context_t* ctx, size_t* out_len) { - int num_octets = get_byte(ctx); - if (num_octets == -1) { - return false; - } - if ((num_octets & 0x80) == 0x00) { - *out_len = num_octets; - return 1; - } - num_octets &= kMaskTag; - if ((size_t)num_octets >= sizeof(size_t)) { - return false; - } - size_t length = 0; - for (int i = 0; i < num_octets; ++i) { - int byte = get_byte(ctx); - if (byte == -1) { - return false; - } - length <<= 8; - length += byte; - } - *out_len = length; +bool asn1_context::decode_length(size_t* out_len) { + int num_octets = get_byte(); + if (num_octets == -1) { + return false; + } + if ((num_octets & 0x80) == 0x00) { + *out_len = num_octets; return true; + } + num_octets &= kMaskTag; + if (static_cast<size_t>(num_octets) >= sizeof(size_t)) { + return false; + } + size_t length = 0; + for (int i = 0; i < num_octets; ++i) { + int byte = get_byte(); + if (byte == -1) { + return false; + } + length <<= 8; + length += byte; + } + *out_len = length; + return true; } /** * Returns the constructed type and advances the pointer. E.g. A0 -> 0 */ -asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) { - int type = get_byte(ctx); - if (type == -1 || (type & kMaskConstructed) != kTagConstructed) { - return NULL; - } - size_t length; - if (!decode_length(ctx, &length) || length > ctx->length) { - return NULL; - } - asn1_context_t* app_ctx = asn1_context_new(ctx->p, length); - app_ctx->app_type = type & kMaskAppType; - return app_ctx; +asn1_context* asn1_context::asn1_constructed_get() { + int type = get_byte(); + if (type == -1 || (type & kMaskConstructed) != kTagConstructed) { + return nullptr; + } + size_t length; + if (!decode_length(&length) || length > length_) { + return nullptr; + } + asn1_context* app_ctx = new asn1_context(p_, length); + app_ctx->app_type_ = type & kMaskAppType; + return app_ctx; } -bool asn1_constructed_skip_all(asn1_context_t* ctx) { - int byte = peek_byte(ctx); - while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) { - skip_bytes(ctx, 1); - size_t length; - if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) { - return false; - } - byte = peek_byte(ctx); +bool asn1_context::asn1_constructed_skip_all() { + int byte = peek_byte(); + while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) { + skip_bytes(1); + size_t length; + if (!decode_length(&length) || !skip_bytes(length)) { + return false; } - return byte != -1; + byte = peek_byte(); + } + return byte != -1; } -int asn1_constructed_type(asn1_context_t* ctx) { - return ctx->app_type; +int asn1_context::asn1_constructed_type() const { + return app_type_; } -asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) { - if ((get_byte(ctx) & kMaskTag) != kTagSequence) { - return NULL; - } - size_t length; - if (!decode_length(ctx, &length) || length > ctx->length) { - return NULL; - } - return asn1_context_new(ctx->p, length); +asn1_context* asn1_context::asn1_sequence_get() { + if ((get_byte() & kMaskTag) != kTagSequence) { + return nullptr; + } + size_t length; + if (!decode_length(&length) || length > length_) { + return nullptr; + } + return new asn1_context(p_, length); } -asn1_context_t* asn1_set_get(asn1_context_t* ctx) { - if ((get_byte(ctx) & kMaskTag) != kTagSet) { - return NULL; - } - size_t length; - if (!decode_length(ctx, &length) || length > ctx->length) { - return NULL; - } - return asn1_context_new(ctx->p, length); +asn1_context* asn1_context::asn1_set_get() { + if ((get_byte() & kMaskTag) != kTagSet) { + return nullptr; + } + size_t length; + if (!decode_length(&length) || length > length_) { + return nullptr; + } + return new asn1_context(p_, length); } -bool asn1_sequence_next(asn1_context_t* ctx) { - size_t length; - if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) { - return false; - } - return true; +bool asn1_context::asn1_sequence_next() { + size_t length; + if (get_byte() == -1 || !decode_length(&length) || !skip_bytes(length)) { + return false; + } + return true; } -bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) { - if (get_byte(ctx) != kTagOid) { - return false; - } - if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) { - return false; - } - *oid = ctx->p; - return true; +bool asn1_context::asn1_oid_get(const uint8_t** oid, size_t* length) { + if (get_byte() != kTagOid) { + return false; + } + if (!decode_length(length) || *length == 0 || *length > length_) { + return false; + } + *oid = p_; + return true; } -bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) { - if (get_byte(ctx) != kTagOctetString) { - return false; - } - if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) { - return false; - } - *octet_string = ctx->p; - return true; +bool asn1_context::asn1_octet_string_get(const uint8_t** octet_string, size_t* length) { + if (get_byte() != kTagOctetString) { + return false; + } + if (!decode_length(length) || *length == 0 || *length > length_) { + return false; + } + *octet_string = p_; + return true; } diff --git a/asn1_decoder.h b/asn1_decoder.h index b17141c44..3e992115a 100644 --- a/asn1_decoder.h +++ b/asn1_decoder.h @@ -14,23 +14,42 @@ * limitations under the License. */ - #ifndef ASN1_DECODER_H_ #define ASN1_DECODER_H_ #include <stdint.h> -typedef struct asn1_context asn1_context_t; - -asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length); -void asn1_context_free(asn1_context_t* ctx); -asn1_context_t* asn1_constructed_get(asn1_context_t* ctx); -bool asn1_constructed_skip_all(asn1_context_t* ctx); -int asn1_constructed_type(asn1_context_t* ctx); -asn1_context_t* asn1_sequence_get(asn1_context_t* ctx); -asn1_context_t* asn1_set_get(asn1_context_t* ctx); -bool asn1_sequence_next(asn1_context_t* seq); -bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length); -bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length); +class asn1_context { + public: + asn1_context(const uint8_t* buffer, size_t length) : p_(buffer), length_(length), app_type_(0) {} + int asn1_constructed_type() const; + asn1_context* asn1_constructed_get(); + bool asn1_constructed_skip_all(); + asn1_context* asn1_sequence_get(); + asn1_context* asn1_set_get(); + bool asn1_sequence_next(); + bool asn1_oid_get(const uint8_t** oid, size_t* length); + bool asn1_octet_string_get(const uint8_t** octet_string, size_t* length); + + private: + static constexpr int kMaskConstructed = 0xE0; + static constexpr int kMaskTag = 0x7F; + static constexpr int kMaskAppType = 0x1F; + + static constexpr int kTagOctetString = 0x04; + static constexpr int kTagOid = 0x06; + static constexpr int kTagSequence = 0x30; + static constexpr int kTagSet = 0x31; + static constexpr int kTagConstructed = 0xA0; + + int peek_byte() const; + int get_byte(); + bool skip_bytes(size_t num_skip); + bool decode_length(size_t* out_len); + + const uint8_t* p_; + size_t length_; + int app_type_; +}; #endif /* ASN1_DECODER_H_ */ diff --git a/bootloader_message/Android.mk b/bootloader_message/Android.mk index 815ac67d7..a8c50819b 100644 --- a/bootloader_message/Android.mk +++ b/bootloader_message/Android.mk @@ -19,6 +19,7 @@ LOCAL_CLANG := true LOCAL_SRC_FILES := bootloader_message.cpp LOCAL_MODULE := libbootloader_message LOCAL_STATIC_LIBRARIES := libbase libfs_mgr +LOCAL_CFLAGS := -Werror LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_STATIC_LIBRARY) diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index 9de7dff40..d8086be28 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -19,38 +19,24 @@ #include <errno.h> #include <fcntl.h> #include <string.h> -#include <sys/system_properties.h> #include <string> #include <vector> #include <android-base/file.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> #include <fs_mgr.h> -static struct fstab* read_fstab(std::string* err) { - // The fstab path is always "/fstab.${ro.hardware}". - std::string fstab_path = "/fstab."; - char value[PROP_VALUE_MAX]; - if (__system_property_get("ro.hardware", value) == 0) { - *err = "failed to get ro.hardware"; - return nullptr; - } - fstab_path += value; - struct fstab* fstab = fs_mgr_read_fstab(fstab_path.c_str()); - if (fstab == nullptr) { - *err = "failed to read " + fstab_path; - } - return fstab; -} - static std::string get_misc_blk_device(std::string* err) { - struct fstab* fstab = read_fstab(err); - if (fstab == nullptr) { + std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(), + fs_mgr_free_fstab); + if (!fstab) { + *err = "failed to read default fstab"; return ""; } - fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc"); + fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); if (record == nullptr) { *err = "failed to find /misc partition"; return ""; @@ -81,26 +67,23 @@ static bool wait_for_device(const std::string& blk_device, std::string* err) { return ret == 0; } -static bool read_misc_partition(void* p, size_t size, size_t offset, std::string* err) { - std::string misc_blk_device = get_misc_blk_device(err); - if (misc_blk_device.empty()) { - return false; - } +static bool read_misc_partition(void* p, size_t size, const std::string& misc_blk_device, + size_t offset, std::string* err) { if (!wait_for_device(misc_blk_device, err)) { return false; } android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY)); - if (fd.get() == -1) { + if (fd == -1) { *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { + if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (!android::base::ReadFully(fd.get(), p, size)) { + if (!android::base::ReadFully(fd, p, size)) { *err = android::base::StringPrintf("failed to read %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; @@ -108,30 +91,25 @@ static bool read_misc_partition(void* p, size_t size, size_t offset, std::string return true; } -static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) { - std::string misc_blk_device = get_misc_blk_device(err); - if (misc_blk_device.empty()) { - return false; - } - android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); - if (fd.get() == -1) { +static bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device, + size_t offset, std::string* err) { + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY)); + if (fd == -1) { *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { + if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (!android::base::WriteFully(fd.get(), p, size)) { + if (!android::base::WriteFully(fd, p, size)) { *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - - // TODO: O_SYNC and fsync duplicates each other? - if (fsync(fd.get()) == -1) { + if (fsync(fd) == -1) { *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; @@ -139,12 +117,32 @@ static bool write_misc_partition(const void* p, size_t size, size_t offset, std: return true; } +bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, + std::string* err) { + return read_misc_partition(boot, sizeof(*boot), misc_blk_device, + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); +} + bool read_bootloader_message(bootloader_message* boot, std::string* err) { - return read_misc_partition(boot, sizeof(*boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return read_bootloader_message_from(boot, misc_blk_device, err); +} + +bool write_bootloader_message_to(const bootloader_message& boot, const std::string& misc_blk_device, + std::string* err) { + return write_misc_partition(&boot, sizeof(boot), misc_blk_device, + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); } bool write_bootloader_message(const bootloader_message& boot, std::string* err) { - return write_misc_partition(&boot, sizeof(boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_bootloader_message_to(boot, misc_blk_device, err); } bool clear_bootloader_message(std::string* err) { @@ -165,16 +163,64 @@ bool write_bootloader_message(const std::vector<std::string>& options, std::stri return write_bootloader_message(boot, err); } +bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) { + bootloader_message boot; + if (!read_bootloader_message(&boot, err)) { + return false; + } + + // Zero out the entire fields. + memset(boot.command, 0, sizeof(boot.command)); + memset(boot.recovery, 0, sizeof(boot.recovery)); + + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); + for (const auto& s : options) { + strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery)); + if (s.back() != '\n') { + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + } + return write_bootloader_message(boot, err); +} + +bool write_reboot_bootloader(std::string* err) { + bootloader_message boot; + if (!read_bootloader_message(&boot, err)) { + return false; + } + if (boot.command[0] != '\0') { + *err = "Bootloader command pending."; + return false; + } + strlcpy(boot.command, "bootonce-bootloader", sizeof(boot.command)); + return write_bootloader_message(boot, err); +} + bool read_wipe_package(std::string* package_data, size_t size, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } package_data->resize(size); - return read_misc_partition(&(*package_data)[0], size, WIPE_PACKAGE_OFFSET_IN_MISC, err); + return read_misc_partition(&(*package_data)[0], size, misc_blk_device, + WIPE_PACKAGE_OFFSET_IN_MISC, err); } bool write_wipe_package(const std::string& package_data, std::string* err) { - return write_misc_partition(package_data.data(), package_data.size(), + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device, WIPE_PACKAGE_OFFSET_IN_MISC, err); } +extern "C" bool write_reboot_bootloader(void) { + std::string err; + return write_reboot_bootloader(&err); +} + extern "C" bool write_bootloader_message(const char* options) { std::string err; return write_bootloader_message({options}, &err); diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index c63aeca6f..bc5104ddf 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -17,18 +17,21 @@ #ifndef _BOOTLOADER_MESSAGE_H #define _BOOTLOADER_MESSAGE_H +#include <assert.h> #include <stddef.h> +#include <stdint.h> // Spaces used by misc partition are as below: -// 0 - 2K Bootloader Message -// 2K - 16K Used by Vendor's bootloader +// 0 - 2K For bootloader_message +// 2K - 16K Used by Vendor's bootloader (the 2K - 4K range may be optionally used +// as bootloader_message_ab struct) // 16K - 64K Used by uncrypt and recovery to store wipe_package for A/B devices // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they // are not configurable without changing all of them. static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; -/* Bootloader Message +/* Bootloader Message (2-KiB) * * This structure describes the content of a block in flash * that is used for recovery and the bootloader to talk to @@ -39,8 +42,9 @@ static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; * It is also updated by the bootloader when firmware update * is complete (to boot into recovery for any final cleanup) * - * The status field is written by the bootloader after the - * completion of an "update-radio" or "update-hboot" command. + * The status field was used by the bootloader after the completion + * of an "update-radio" or "update-hboot" command, which has been + * deprecated since Froyo. * * The recovery field is only written by linux and used * for the system to send a message to recovery or the @@ -51,12 +55,10 @@ static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; * package it is. If the value is of the format "#/#" (eg, "1/3"), * the UI will add a simple indicator of that status. * - * The slot_suffix field is used for A/B implementations where the - * bootloader does not set the androidboot.ro.boot.slot_suffix kernel - * commandline parameter. This is used by fs_mgr to mount /system and - * other partitions with the slotselect flag set in fstab. A/B - * implementations are free to use all 32 bytes and may store private - * data past the first NUL-byte in this field. + * We used to have slot_suffix field for A/B boot control metadata in + * this struct, which gets unintentionally cleared by recovery or + * uncrypt. Move it into struct bootloader_message_ab to avoid the + * issue. */ struct bootloader_message { char command[32]; @@ -69,23 +71,148 @@ struct bootloader_message { // stage string (for multistage packages) and possible future // expansion. char stage[32]; + + // The 'reserved' field used to be 224 bytes when it was initially + // carved off from the 1024-byte recovery field. Bump it up to + // 1184-byte so that the entire bootloader_message struct rounds up + // to 2048-byte. + char reserved[1184]; +}; + +/** + * We must be cautious when changing the bootloader_message struct size, + * because A/B-specific fields may end up with different offsets. + */ +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_message) == 2048, + "struct bootloader_message size changes, which may break A/B devices"); +#endif + +/** + * The A/B-specific bootloader message structure (4-KiB). + * + * We separate A/B boot control metadata from the regular bootloader + * message struct and keep it here. Everything that's A/B-specific + * stays after struct bootloader_message, which should be managed by + * the A/B-bootloader or boot control HAL. + * + * The slot_suffix field is used for A/B implementations where the + * bootloader does not set the androidboot.ro.boot.slot_suffix kernel + * commandline parameter. This is used by fs_mgr to mount /system and + * other partitions with the slotselect flag set in fstab. A/B + * implementations are free to use all 32 bytes and may store private + * data past the first NUL-byte in this field. It is encouraged, but + * not mandatory, to use 'struct bootloader_control' described below. + */ +struct bootloader_message_ab { + struct bootloader_message message; char slot_suffix[32]; - char reserved[192]; + + // Round up the entire struct to 4096-byte. + char reserved[2016]; }; +/** + * Be cautious about the struct size change, in case we put anything post + * bootloader_message_ab struct (b/29159185). + */ +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_message_ab) == 4096, + "struct bootloader_message_ab size changes"); +#endif + +#define BOOT_CTRL_MAGIC 0x42414342 /* Bootloader Control AB */ +#define BOOT_CTRL_VERSION 1 + +struct slot_metadata { + // Slot priority with 15 meaning highest priority, 1 lowest + // priority and 0 the slot is unbootable. + uint8_t priority : 4; + // Number of times left attempting to boot this slot. + uint8_t tries_remaining : 3; + // 1 if this slot has booted successfully, 0 otherwise. + uint8_t successful_boot : 1; + // 1 if this slot is corrupted from a dm-verity corruption, 0 + // otherwise. + uint8_t verity_corrupted : 1; + // Reserved for further use. + uint8_t reserved : 7; +} __attribute__((packed)); + +/* Bootloader Control AB + * + * This struct can be used to manage A/B metadata. It is designed to + * be put in the 'slot_suffix' field of the 'bootloader_message' + * structure described above. It is encouraged to use the + * 'bootloader_control' structure to store the A/B metadata, but not + * mandatory. + */ +struct bootloader_control { + // NUL terminated active slot suffix. + char slot_suffix[4]; + // Bootloader Control AB magic number (see BOOT_CTRL_MAGIC). + uint32_t magic; + // Version of struct being used (see BOOT_CTRL_VERSION). + uint8_t version; + // Number of slots being managed. + uint8_t nb_slot : 3; + // Number of times left attempting to boot recovery. + uint8_t recovery_tries_remaining : 3; + // Ensure 4-bytes alignment for slot_info field. + uint8_t reserved0[2]; + // Per-slot information. Up to 4 slots. + struct slot_metadata slot_info[4]; + // Reserved for further use. + uint8_t reserved1[8]; + // CRC32 of all 28 bytes preceding this field (little endian + // format). + uint32_t crc32_le; +} __attribute__((packed)); + +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_control) == + sizeof(((struct bootloader_message_ab *)0)->slot_suffix), + "struct bootloader_control has wrong size"); +#endif + #ifdef __cplusplus #include <string> #include <vector> +// Read bootloader message into boot. Error message will be set in err. bool read_bootloader_message(bootloader_message* boot, std::string* err); + +// Read bootloader message from the specified misc device into boot. +bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, + std::string* err); + +// Write bootloader message to BCB. bool write_bootloader_message(const bootloader_message& boot, std::string* err); + +// Write bootloader message to the specified BCB device. +bool write_bootloader_message_to(const bootloader_message& boot, + const std::string& misc_blk_device, std::string* err); + +// Write bootloader message (boots into recovery with the options) to BCB. Will +// set the command and recovery fields, and reset the rest. bool write_bootloader_message(const std::vector<std::string>& options, std::string* err); + +// Update bootloader message (boots into recovery with the options) to BCB. Will +// only update the command and recovery fields. +bool update_bootloader_message(const std::vector<std::string>& options, std::string* err); + +// Clear BCB. bool clear_bootloader_message(std::string* err); +// Writes the reboot-bootloader reboot reason to the bootloader_message. +bool write_reboot_bootloader(std::string* err); + +// Read the wipe package from BCB (from offset WIPE_PACKAGE_OFFSET_IN_MISC). bool read_wipe_package(std::string* package_data, size_t size, std::string* err); -bool write_wipe_package(const std::string& package_data, std::string* err); +// Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC). +bool write_wipe_package(const std::string& package_data, std::string* err); #else @@ -93,6 +220,7 @@ bool write_wipe_package(const std::string& package_data, std::string* err); // C Interface. bool write_bootloader_message(const char* options); +bool write_reboot_bootloader(void); #endif // ifdef __cplusplus @@ -17,27 +17,24 @@ #ifndef RECOVERY_COMMON_H #define RECOVERY_COMMON_H -#include <stdbool.h> #include <stdio.h> #include <stdarg.h> -#define LOGE(...) ui_print("E:" __VA_ARGS__) -#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__) -#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__) - -#if 0 -#define LOGV(...) fprintf(stdout, "V:" __VA_ARGS__) -#define LOGD(...) fprintf(stdout, "D:" __VA_ARGS__) -#else -#define LOGV(...) do {} while (0) -#define LOGD(...) do {} while (0) -#endif +#include <string> #define STRINGIFY(x) #x #define EXPAND(x) STRINGIFY(x) +class RecoveryUI; + +extern RecoveryUI* ui; extern bool modified_flash; -typedef struct fstab_rec Volume; + +// The current stage, e.g. "1/2". +extern std::string stage; + +// The reason argument provided in "--reason=". +extern const char* reason; // fopen a file, mounting volumes and making parent dirs as necessary. FILE* fopen_path(const char *path, const char *mode); @@ -46,4 +43,6 @@ void ui_print(const char* format, ...); bool is_ro_debuggable(); +bool reboot(const std::string& command); + #endif // RECOVERY_COMMON_H diff --git a/device.cpp b/device.cpp index e717dddf7..61501869e 100644 --- a/device.cpp +++ b/device.cpp @@ -60,7 +60,7 @@ Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position]; } -int Device::HandleMenuKey(int key, int visible) { +int Device::HandleMenuKey(int key, bool visible) { if (!visible) { return kNoAction; } @@ -20,96 +20,90 @@ #include "ui.h" class Device { - public: - Device(RecoveryUI* ui) : ui_(ui) { } - virtual ~Device() { } + public: + explicit Device(RecoveryUI* ui) : ui_(ui) {} + virtual ~Device() {} - // Called to obtain the UI object that should be used to display - // the recovery user interface for this device. You should not - // have called Init() on the UI object already, the caller will do - // that after this method returns. - virtual RecoveryUI* GetUI() { return ui_; } + // Called to obtain the UI object that should be used to display the recovery user interface for + // this device. You should not have called Init() on the UI object already, the caller will do + // that after this method returns. + virtual RecoveryUI* GetUI() { + return ui_; + } - // Called when recovery starts up (after the UI has been obtained - // and initialized and after the arguments have been parsed, but - // before anything else). - virtual void StartRecovery() { }; + // Called when recovery starts up (after the UI has been obtained and initialized and after the + // arguments have been parsed, but before anything else). + virtual void StartRecovery() {}; - // Called from the main thread when recovery is at the main menu - // and waiting for input, and a key is pressed. (Note that "at" - // the main menu does not necessarily mean the menu is visible; - // recovery will be at the main menu with it invisible after an - // unsuccessful operation [ie OTA package failure], or if recovery - // is started with no command.) - // - // key is the code of the key just pressed. (You can call - // IsKeyPressed() on the RecoveryUI object you returned from GetUI - // if you want to find out if other keys are held down.) - // - // visible is true if the menu is visible. - // - // Return one of the defined constants below in order to: - // - // - move the menu highlight (kHighlight{Up,Down}) - // - invoke the highlighted item (kInvokeItem) - // - do nothing (kNoAction) - // - invoke a specific action (a menu position: any non-negative number) - virtual int HandleMenuKey(int key, int visible); + // Called from the main thread when recovery is at the main menu and waiting for input, and a key + // is pressed. (Note that "at" the main menu does not necessarily mean the menu is visible; + // recovery will be at the main menu with it invisible after an unsuccessful operation [ie OTA + // package failure], or if recovery is started with no command.) + // + // 'key' is the code of the key just pressed. (You can call IsKeyPressed() on the RecoveryUI + // object you returned from GetUI if you want to find out if other keys are held down.) + // + // 'visible' is true if the menu is visible. + // + // Returns one of the defined constants below in order to: + // + // - move the menu highlight (kHighlight{Up,Down}) + // - invoke the highlighted item (kInvokeItem) + // - do nothing (kNoAction) + // - invoke a specific action (a menu position: any non-negative number) + virtual int HandleMenuKey(int key, bool visible); - enum BuiltinAction { - NO_ACTION = 0, - REBOOT = 1, - APPLY_SDCARD = 2, - // APPLY_CACHE was 3. - APPLY_ADB_SIDELOAD = 4, - WIPE_DATA = 5, - WIPE_CACHE = 6, - REBOOT_BOOTLOADER = 7, - SHUTDOWN = 8, - VIEW_RECOVERY_LOGS = 9, - MOUNT_SYSTEM = 10, - RUN_GRAPHICS_TEST = 11, - }; + enum BuiltinAction { + NO_ACTION = 0, + REBOOT = 1, + APPLY_SDCARD = 2, + // APPLY_CACHE was 3. + APPLY_ADB_SIDELOAD = 4, + WIPE_DATA = 5, + WIPE_CACHE = 6, + REBOOT_BOOTLOADER = 7, + SHUTDOWN = 8, + VIEW_RECOVERY_LOGS = 9, + MOUNT_SYSTEM = 10, + RUN_GRAPHICS_TEST = 11, + }; - // Return the list of menu items (an array of strings, - // NULL-terminated). The menu_position passed to InvokeMenuItem - // will correspond to the indexes into this array. - virtual const char* const* GetMenuItems(); + // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed + // to InvokeMenuItem will correspond to the indexes into this array. + virtual const char* const* GetMenuItems(); - // Perform a recovery action selected from the menu. - // 'menu_position' will be the item number of the selected menu - // item, or a non-negative number returned from - // device_handle_key(). The menu will be hidden when this is - // called; implementations can call ui_print() to print - // information to the screen. If the menu position is one of the - // builtin actions, you can just return the corresponding enum - // value. If it is an action specific to your device, you - // actually perform it here and return NO_ACTION. - virtual BuiltinAction InvokeMenuItem(int menu_position); + // Perform a recovery action selected from the menu. 'menu_position' will be the item number of + // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will + // be hidden when this is called; implementations can call ui_print() to print information to the + // screen. If the menu position is one of the builtin actions, you can just return the + // corresponding enum value. If it is an action specific to your device, you actually perform it + // here and return NO_ACTION. + virtual BuiltinAction InvokeMenuItem(int menu_position); - static const int kNoAction = -1; - static const int kHighlightUp = -2; - static const int kHighlightDown = -3; - static const int kInvokeItem = -4; + static const int kNoAction = -1; + static const int kHighlightUp = -2; + static const int kHighlightDown = -3; + static const int kInvokeItem = -4; - // Called before and after we do a wipe data/factory reset operation, - // either via a reboot from the main system with the --wipe_data flag, - // or when the user boots into recovery image manually and selects the - // option from the menu, to perform whatever device-specific wiping - // actions are needed. - // Return true on success; returning false from PreWipeData will prevent - // the regular wipe, and returning false from PostWipeData will cause - // the wipe to be considered a failure. - virtual bool PreWipeData() { return true; } - virtual bool PostWipeData() { return true; } + // Called before and after we do a wipe data/factory reset operation, either via a reboot from the + // main system with the --wipe_data flag, or when the user boots into recovery image manually and + // selects the option from the menu, to perform whatever device-specific wiping actions as needed. + // Returns true on success; returning false from PreWipeData will prevent the regular wipe, and + // returning false from PostWipeData will cause the wipe to be considered a failure. + virtual bool PreWipeData() { + return true; + } - private: - RecoveryUI* ui_; + virtual bool PostWipeData() { + return true; + } + + private: + RecoveryUI* ui_; }; -// The device-specific library must define this function (or the -// default one will be used, if there is no device-specific library). -// It returns the Device object that recovery should use. +// The device-specific library must define this function (or the default one will be used, if there +// is no device-specific library). It returns the Device object that recovery should use. Device* make_device(); #endif // _DEVICE_H diff --git a/edify/Android.mk b/edify/Android.mk index 71cf7652a..d8058c16f 100644 --- a/edify/Android.mk +++ b/edify/Android.mk @@ -1,23 +1,36 @@ # Copyright 2009 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. LOCAL_PATH := $(call my-dir) edify_src_files := \ - lexer.ll \ - parser.yy \ - expr.cpp + lexer.ll \ + parser.yy \ + expr.cpp # -# Build the host-side command line tool +# Build the host-side command line tool (host executable) # include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - $(edify_src_files) \ - main.cpp + $(edify_src_files) \ + edify_parser.cpp +LOCAL_CFLAGS := -Werror LOCAL_CPPFLAGS := -g -O0 -LOCAL_MODULE := edify +LOCAL_MODULE := edify_parser LOCAL_YACCFLAGS := -v LOCAL_CPPFLAGS += -Wno-unused-parameter LOCAL_CPPFLAGS += -Wno-deprecated-register @@ -28,12 +41,13 @@ LOCAL_STATIC_LIBRARIES += libbase include $(BUILD_HOST_EXECUTABLE) # -# Build the device-side library +# Build the device-side library (static library) # include $(CLEAR_VARS) LOCAL_SRC_FILES := $(edify_src_files) +LOCAL_CFLAGS := -Werror LOCAL_CPPFLAGS := -Wno-unused-parameter LOCAL_CPPFLAGS += -Wno-deprecated-register LOCAL_MODULE := libedify diff --git a/edify/README b/edify/README.md index 810455cca..b3330e23a 100644 --- a/edify/README +++ b/edify/README.md @@ -1,3 +1,6 @@ +edify +===== + Update scripts (from donut onwards) are written in a new little scripting language ("edify") that is superficially somewhat similar to the old one ("amend"). This is a brief overview of the new language. diff --git a/edify/edify_parser.cpp b/edify/edify_parser.cpp new file mode 100644 index 000000000..f1b56284c --- /dev/null +++ b/edify/edify_parser.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is a host-side tool for validating a given edify script file. + * + * We used to have edify test cases here, which have been moved to + * tests/component/edify_test.cpp. + * + * Caveat: It doesn't recognize functions defined through updater, which + * makes the tool less useful. We should either extend the tool or remove it. + */ + +#include <errno.h> +#include <stdio.h> + +#include <memory> +#include <string> + +#include <android-base/file.h> + +#include "expr.h" + +static void ExprDump(int depth, const std::unique_ptr<Expr>& n, const std::string& script) { + printf("%*s", depth*2, ""); + printf("%s %p (%d-%d) \"%s\"\n", + n->name.c_str(), n->fn, n->start, n->end, + script.substr(n->start, n->end - n->start).c_str()); + for (size_t i = 0; i < n->argv.size(); ++i) { + ExprDump(depth+1, n->argv[i], script); + } +} + +int main(int argc, char** argv) { + RegisterBuiltins(); + + if (argc != 2) { + printf("Usage: %s <edify script>\n", argv[0]); + return 1; + } + + std::string buffer; + if (!android::base::ReadFileToString(argv[1], &buffer)) { + printf("%s: failed to read %s: %s\n", argv[0], argv[1], strerror(errno)); + return 1; + } + + std::unique_ptr<Expr> root; + int error_count = 0; + int error = parse_string(buffer.data(), &root, &error_count); + printf("parse returned %d; %d errors encountered\n", error, error_count); + if (error == 0 || error_count > 0) { + + ExprDump(0, root, buffer); + + State state(buffer, nullptr); + std::string result; + if (!Evaluate(&state, root, &result)) { + printf("result was NULL, message is: %s\n", + (state.errmsg.empty() ? "(NULL)" : state.errmsg.c_str())); + } else { + printf("result is [%s]\n", result.c_str()); + } + } + return 0; +} diff --git a/edify/expr.cpp b/edify/expr.cpp index cc14fbe93..54ab3325c 100644 --- a/edify/expr.cpp +++ b/edify/expr.cpp @@ -14,201 +14,172 @@ * limitations under the License. */ -#include <string.h> -#include <stdbool.h> +#include "expr.h" + +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> -#include <stdarg.h> +#include <string.h> #include <unistd.h> +#include <memory> #include <string> +#include <unordered_map> +#include <vector> +#include <android-base/parseint.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include "expr.h" - // Functions should: // // - return a malloc()'d string -// - if Evaluate() on any argument returns NULL, return NULL. +// - if Evaluate() on any argument returns nullptr, return nullptr. -int BooleanString(const char* s) { - return s[0] != '\0'; +static bool BooleanString(const std::string& s) { + return !s.empty(); } -char* Evaluate(State* state, Expr* expr) { - Value* v = expr->fn(expr->name, state, expr->argc, expr->argv); - if (v == NULL) return NULL; +bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result) { + if (result == nullptr) { + return false; + } + + std::unique_ptr<Value> v(expr->fn(expr->name.c_str(), state, expr->argv)); + if (!v) { + return false; + } if (v->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type); - FreeValue(v); - return NULL; + return false; } - char* result = v->data; - free(v); - return result; + + *result = v->data; + return true; } -Value* EvaluateValue(State* state, Expr* expr) { - return expr->fn(expr->name, state, expr->argc, expr->argv); +Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr) { + return expr->fn(expr->name.c_str(), state, expr->argv); } -Value* StringValue(char* str) { - if (str == NULL) return NULL; - Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value))); - v->type = VAL_STRING; - v->size = strlen(str); - v->data = str; - return v; +Value* StringValue(const char* str) { + if (str == nullptr) { + return nullptr; + } + return new Value(VAL_STRING, str); } -void FreeValue(Value* v) { - if (v == NULL) return; - free(v->data); - free(v); +Value* StringValue(const std::string& str) { + return StringValue(str.c_str()); } -Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc == 0) { - return StringValue(strdup("")); - } - char** strings = reinterpret_cast<char**>(malloc(argc * sizeof(char*))); - int i; - for (i = 0; i < argc; ++i) { - strings[i] = NULL; - } - char* result = NULL; - int length = 0; - for (i = 0; i < argc; ++i) { - strings[i] = Evaluate(state, argv[i]); - if (strings[i] == NULL) { - goto done; - } - length += strlen(strings[i]); +Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.empty()) { + return StringValue(""); } - - result = reinterpret_cast<char*>(malloc(length+1)); - int p; - p = 0; - for (i = 0; i < argc; ++i) { - strcpy(result+p, strings[i]); - p += strlen(strings[i]); + std::string result; + for (size_t i = 0; i < argv.size(); ++i) { + std::string str; + if (!Evaluate(state, argv[i], &str)) { + return nullptr; + } + result += str; } - result[p] = '\0'; - done: - for (i = 0; i < argc; ++i) { - free(strings[i]); - } - free(strings); return StringValue(result); } -Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2 && argc != 3) { - free(state->errmsg); - state->errmsg = strdup("ifelse expects 2 or 3 arguments"); - return NULL; +Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2 && argv.size() != 3) { + state->errmsg = "ifelse expects 2 or 3 arguments"; + return nullptr; } - char* cond = Evaluate(state, argv[0]); - if (cond == NULL) { - return NULL; + + std::string cond; + if (!Evaluate(state, argv[0], &cond)) { + return nullptr; } - if (BooleanString(cond) == true) { - free(cond); + if (!cond.empty()) { return EvaluateValue(state, argv[1]); - } else { - if (argc == 3) { - free(cond); - return EvaluateValue(state, argv[2]); - } else { - return StringValue(cond); - } + } else if (argv.size() == 3) { + return EvaluateValue(state, argv[2]); } + + return StringValue(""); } -Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { - char* msg = NULL; - if (argc > 0) { - msg = Evaluate(state, argv[0]); - } - free(state->errmsg); - if (msg) { +Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + std::string msg; + if (!argv.empty() && Evaluate(state, argv[0], &msg)) { state->errmsg = msg; } else { - state->errmsg = strdup("called abort()"); + state->errmsg = "called abort()"; } - return NULL; + return nullptr; } -Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { - int i; - for (i = 0; i < argc; ++i) { - char* v = Evaluate(state, argv[i]); - if (v == NULL) { - return NULL; +Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + for (size_t i = 0; i < argv.size(); ++i) { + std::string result; + if (!Evaluate(state, argv[i], &result)) { + return nullptr; } - int b = BooleanString(v); - free(v); - if (!b) { - int prefix_len; + if (result.empty()) { int len = argv[i]->end - argv[i]->start; - char* err_src = reinterpret_cast<char*>(malloc(len + 20)); - strcpy(err_src, "assert failed: "); - prefix_len = strlen(err_src); - memcpy(err_src + prefix_len, state->script + argv[i]->start, len); - err_src[prefix_len + len] = '\0'; - free(state->errmsg); - state->errmsg = err_src; - return NULL; + state->errmsg = "assert failed: " + state->script.substr(argv[i]->start, len); + return nullptr; } } - return StringValue(strdup("")); + return StringValue(""); } -Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { - char* val = Evaluate(state, argv[0]); - if (val == NULL) { - return NULL; +Value* SleepFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + std::string val; + if (!Evaluate(state, argv[0], &val)) { + return nullptr; + } + + int v; + if (!android::base::ParseInt(val.c_str(), &v, 0)) { + return nullptr; } - int v = strtol(val, NULL, 10); sleep(v); + return StringValue(val); } -Value* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { - int i; - for (i = 0; i < argc; ++i) { - char* v = Evaluate(state, argv[i]); - if (v == NULL) { - return NULL; +Value* StdoutFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + for (size_t i = 0; i < argv.size(); ++i) { + std::string v; + if (!Evaluate(state, argv[i], &v)) { + return nullptr; } - fputs(v, stdout); - free(v); + fputs(v.c_str(), stdout); } - return StringValue(strdup("")); + return StringValue(""); } Value* LogicalAndFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* left = Evaluate(state, argv[0]); - if (left == NULL) return NULL; - if (BooleanString(left) == true) { - free(left); + const std::vector<std::unique_ptr<Expr>>& argv) { + std::string left; + if (!Evaluate(state, argv[0], &left)) { + return nullptr; + } + if (BooleanString(left)) { return EvaluateValue(state, argv[1]); } else { - return StringValue(left); + return StringValue(""); } } Value* LogicalOrFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* left = Evaluate(state, argv[0]); - if (left == NULL) return NULL; - if (BooleanString(left) == false) { - free(left); + const std::vector<std::unique_ptr<Expr>>& argv) { + std::string left; + if (!Evaluate(state, argv[0], &left)) { + return nullptr; + } + if (!BooleanString(left)) { return EvaluateValue(state, argv[1]); } else { return StringValue(left); @@ -216,174 +187,144 @@ Value* LogicalOrFn(const char* name, State* state, } Value* LogicalNotFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* val = Evaluate(state, argv[0]); - if (val == NULL) return NULL; - bool bv = BooleanString(val); - free(val); - return StringValue(strdup(bv ? "" : "t")); + const std::vector<std::unique_ptr<Expr>>& argv) { + std::string val; + if (!Evaluate(state, argv[0], &val)) { + return nullptr; + } + + return StringValue(BooleanString(val) ? "" : "t"); } Value* SubstringFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* needle = Evaluate(state, argv[0]); - if (needle == NULL) return NULL; - char* haystack = Evaluate(state, argv[1]); - if (haystack == NULL) { - free(needle); - return NULL; - } - - char* result = strdup(strstr(haystack, needle) ? "t" : ""); - free(needle); - free(haystack); + const std::vector<std::unique_ptr<Expr>>& argv) { + std::string needle; + if (!Evaluate(state, argv[0], &needle)) { + return nullptr; + } + + std::string haystack; + if (!Evaluate(state, argv[1], &haystack)) { + return nullptr; + } + + std::string result = (haystack.find(needle) != std::string::npos) ? "t" : ""; return StringValue(result); } -Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { - char* left = Evaluate(state, argv[0]); - if (left == NULL) return NULL; - char* right = Evaluate(state, argv[1]); - if (right == NULL) { - free(left); - return NULL; +Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + std::string left; + if (!Evaluate(state, argv[0], &left)) { + return nullptr; + } + std::string right; + if (!Evaluate(state, argv[1], &right)) { + return nullptr; } - char* result = strdup(strcmp(left, right) == 0 ? "t" : ""); - free(left); - free(right); + const char* result = (left == right) ? "t" : ""; return StringValue(result); } -Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { - char* left = Evaluate(state, argv[0]); - if (left == NULL) return NULL; - char* right = Evaluate(state, argv[1]); - if (right == NULL) { - free(left); - return NULL; +Value* InequalityFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + std::string left; + if (!Evaluate(state, argv[0], &left)) { + return nullptr; + } + std::string right; + if (!Evaluate(state, argv[1], &right)) { + return nullptr; } - char* result = strdup(strcmp(left, right) != 0 ? "t" : ""); - free(left); - free(right); + const char* result = (left != right) ? "t" : ""; return StringValue(result); } -Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) { - Value* left = EvaluateValue(state, argv[0]); - if (left == NULL) return NULL; - FreeValue(left); +Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + std::unique_ptr<Value> left(EvaluateValue(state, argv[0])); + if (!left) { + return nullptr; + } return EvaluateValue(state, argv[1]); } -Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - free(state->errmsg); - state->errmsg = strdup("less_than_int expects 2 arguments"); - return NULL; +Value* LessThanIntFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + state->errmsg = "less_than_int expects 2 arguments"; + return nullptr; } - char* left; - char* right; - if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL; - - bool result = false; - char* end; - - long l_int = strtol(left, &end, 10); - if (left[0] == '\0' || *end != '\0') { - goto done; + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return nullptr; } - long r_int; - r_int = strtol(right, &end, 10); - if (right[0] == '\0' || *end != '\0') { - goto done; + // Parse up to at least long long or 64-bit integers. + int64_t l_int; + if (!android::base::ParseInt(args[0].c_str(), &l_int)) { + state->errmsg = "failed to parse int in " + args[0]; + return nullptr; } - result = l_int < r_int; + int64_t r_int; + if (!android::base::ParseInt(args[1].c_str(), &r_int)) { + state->errmsg = "failed to parse int in " + args[1]; + return nullptr; + } - done: - free(left); - free(right); - return StringValue(strdup(result ? "t" : "")); + return StringValue(l_int < r_int ? "t" : ""); } Value* GreaterThanIntFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc != 2) { - free(state->errmsg); - state->errmsg = strdup("greater_than_int expects 2 arguments"); - return NULL; + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + state->errmsg = "greater_than_int expects 2 arguments"; + return nullptr; } - Expr* temp[2]; - temp[0] = argv[1]; - temp[1] = argv[0]; + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return nullptr; + } - return LessThanIntFn(name, state, 2, temp); -} + // Parse up to at least long long or 64-bit integers. + int64_t l_int; + if (!android::base::ParseInt(args[0].c_str(), &l_int)) { + state->errmsg = "failed to parse int in " + args[0]; + return nullptr; + } + + int64_t r_int; + if (!android::base::ParseInt(args[1].c_str(), &r_int)) { + state->errmsg = "failed to parse int in " + args[1]; + return nullptr; + } -Value* Literal(const char* name, State* state, int argc, Expr* argv[]) { - return StringValue(strdup(name)); + return StringValue(l_int > r_int ? "t" : ""); } -Expr* Build(Function fn, YYLTYPE loc, int count, ...) { - va_list v; - va_start(v, count); - Expr* e = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); - e->fn = fn; - e->name = "(operator)"; - e->argc = count; - e->argv = reinterpret_cast<Expr**>(malloc(count * sizeof(Expr*))); - int i; - for (i = 0; i < count; ++i) { - e->argv[i] = va_arg(v, Expr*); - } - va_end(v); - e->start = loc.start; - e->end = loc.end; - return e; +Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + return StringValue(name); } // ----------------------------------------------------------------- // the function table // ----------------------------------------------------------------- -static int fn_entries = 0; -static int fn_size = 0; -NamedFunction* fn_table = NULL; - -void RegisterFunction(const char* name, Function fn) { - if (fn_entries >= fn_size) { - fn_size = fn_size*2 + 1; - fn_table = reinterpret_cast<NamedFunction*>(realloc(fn_table, fn_size * sizeof(NamedFunction))); - } - fn_table[fn_entries].name = name; - fn_table[fn_entries].fn = fn; - ++fn_entries; -} - -static int fn_entry_compare(const void* a, const void* b) { - const char* na = ((const NamedFunction*)a)->name; - const char* nb = ((const NamedFunction*)b)->name; - return strcmp(na, nb); -} +static std::unordered_map<std::string, Function> fn_table; -void FinishRegistration() { - qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare); +void RegisterFunction(const std::string& name, Function fn) { + fn_table[name] = fn; } -Function FindFunction(const char* name) { - NamedFunction key; - key.name = name; - NamedFunction* nf = reinterpret_cast<NamedFunction*>(bsearch(&key, fn_table, fn_entries, - sizeof(NamedFunction), fn_entry_compare)); - if (nf == NULL) { - return NULL; +Function FindFunction(const std::string& name) { + if (fn_table.find(name) == fn_table.end()) { + return nullptr; + } else { + return fn_table[name]; } - return nf->fn; } void RegisterBuiltins() { @@ -404,106 +345,56 @@ void RegisterBuiltins() { // convenience methods for functions // ----------------------------------------------------------------- -// Evaluate the expressions in argv, giving 'count' char* (the ... is -// zero or more char** to put them in). If any expression evaluates -// to NULL, free the rest and return -1. Return 0 on success. -int ReadArgs(State* state, Expr* argv[], int count, ...) { - char** args = reinterpret_cast<char**>(malloc(count * sizeof(char*))); - va_list v; - va_start(v, count); - int i; - for (i = 0; i < count; ++i) { - args[i] = Evaluate(state, argv[i]); - if (args[i] == NULL) { - va_end(v); - int j; - for (j = 0; j < i; ++j) { - free(args[j]); - } - free(args); - return -1; - } - *(va_arg(v, char**)) = args[i]; - } - va_end(v); - free(args); - return 0; +// Evaluate the expressions in argv, and put the results of strings in args. If any expression +// evaluates to nullptr, return false. Return true on success. +bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::string>* args) { + return ReadArgs(state, argv, args, 0, argv.size()); } -// Evaluate the expressions in argv, giving 'count' Value* (the ... is -// zero or more Value** to put them in). If any expression evaluates -// to NULL, free the rest and return -1. Return 0 on success. -int ReadValueArgs(State* state, Expr* argv[], int count, ...) { - Value** args = reinterpret_cast<Value**>(malloc(count * sizeof(Value*))); - va_list v; - va_start(v, count); - int i; - for (i = 0; i < count; ++i) { - args[i] = EvaluateValue(state, argv[i]); - if (args[i] == NULL) { - va_end(v); - int j; - for (j = 0; j < i; ++j) { - FreeValue(args[j]); - } - free(args); - return -1; +bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::string>* args, size_t start, size_t len) { + if (args == nullptr) { + return false; + } + if (start + len > argv.size()) { + return false; + } + for (size_t i = start; i < start + len; ++i) { + std::string var; + if (!Evaluate(state, argv[i], &var)) { + args->clear(); + return false; } - *(va_arg(v, Value**)) = args[i]; + args->push_back(var); } - va_end(v); - free(args); - return 0; + return true; } -// Evaluate the expressions in argv, returning an array of char* -// results. If any evaluate to NULL, free the rest and return NULL. -// The caller is responsible for freeing the returned array and the -// strings it contains. -char** ReadVarArgs(State* state, int argc, Expr* argv[]) { - char** args = (char**)malloc(argc * sizeof(char*)); - int i = 0; - for (i = 0; i < argc; ++i) { - args[i] = Evaluate(state, argv[i]); - if (args[i] == NULL) { - int j; - for (j = 0; j < i; ++j) { - free(args[j]); - } - free(args); - return NULL; - } - } - return args; +// Evaluate the expressions in argv, and put the results of Value* in args. If any expression +// evaluate to nullptr, return false. Return true on success. +bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::unique_ptr<Value>>* args) { + return ReadValueArgs(state, argv, args, 0, argv.size()); } -// Evaluate the expressions in argv, returning an array of Value* -// results. If any evaluate to NULL, free the rest and return NULL. -// The caller is responsible for freeing the returned array and the -// Values it contains. -Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) { - Value** args = (Value**)malloc(argc * sizeof(Value*)); - int i = 0; - for (i = 0; i < argc; ++i) { - args[i] = EvaluateValue(state, argv[i]); - if (args[i] == NULL) { - int j; - for (j = 0; j < i; ++j) { - FreeValue(args[j]); - } - free(args); - return NULL; +bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len) { + if (args == nullptr) { + return false; + } + if (len == 0 || start + len > argv.size()) { + return false; + } + for (size_t i = start; i < start + len; ++i) { + std::unique_ptr<Value> v(EvaluateValue(state, argv[i])); + if (!v) { + args->clear(); + return false; } + args->push_back(std::move(v)); } - return args; -} - -static void ErrorAbortV(State* state, const char* format, va_list ap) { - std::string buffer; - android::base::StringAppendV(&buffer, format, ap); - free(state->errmsg); - state->errmsg = strdup(buffer.c_str()); - return; + return true; } // Use printf-style arguments to compose an error message to put into @@ -511,7 +402,7 @@ static void ErrorAbortV(State* state, const char* format, va_list ap) { Value* ErrorAbort(State* state, const char* format, ...) { va_list ap; va_start(ap, format); - ErrorAbortV(state, format, ap); + android::base::StringAppendV(&state->errmsg, format, ap); va_end(ap); return nullptr; } @@ -519,8 +410,14 @@ Value* ErrorAbort(State* state, const char* format, ...) { Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) { va_list ap; va_start(ap, format); - ErrorAbortV(state, format, ap); + android::base::StringAppendV(&state->errmsg, format, ap); va_end(ap); state->cause_code = cause_code; return nullptr; } + +State::State(const std::string& script, void* cookie) : + script(script), + cookie(cookie) { +} + diff --git a/edify/expr.h b/edify/expr.h index 886347991..4838d20c0 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -19,27 +19,26 @@ #include <unistd.h> +#include <memory> +#include <string> +#include <vector> + #include "error_code.h" -#include "yydefs.h" -#define MAX_STRING_LEN 1024 +struct State { + State(const std::string& script, void* cookie); -typedef struct Expr Expr; + // The source of the original script. + const std::string& script; -typedef struct { // Optional pointer to app-specific data; the core of edify never // uses this value. void* cookie; - // The source of the original script. Must be NULL-terminated, - // and in writable memory (Evaluate may make temporary changes to - // it but will restore it when done). - char* script; - // The error message (if any) returned if the evaluation aborts. - // Should be NULL initially, will be either NULL or a malloc'd - // pointer after Evaluate() returns. - char* errmsg; + // Should be empty initially, will be either empty or a string that + // Evaluate() returns. + std::string errmsg; // error code indicates the type of failure (e.g. failure to update system image) // during the OTA process. @@ -50,117 +49,95 @@ typedef struct { CauseCode cause_code = kNoCause; bool is_retry = false; +}; + +enum ValueType { + VAL_INVALID = -1, + VAL_STRING = 1, + VAL_BLOB = 2, +}; -} State; +struct Value { + ValueType type; + std::string data; -#define VAL_STRING 1 // data will be NULL-terminated; size doesn't count null -#define VAL_BLOB 2 + Value(ValueType type, const std::string& str) : + type(type), + data(str) {} +}; -typedef struct { - int type; - ssize_t size; - char* data; -} Value; +struct Expr; -typedef Value* (*Function)(const char* name, State* state, - int argc, Expr* argv[]); +using Function = Value* (*)(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv); struct Expr { - Function fn; - const char* name; - int argc; - Expr** argv; - int start, end; + Function fn; + std::string name; + std::vector<std::unique_ptr<Expr>> argv; + int start, end; + + Expr(Function fn, const std::string& name, int start, int end) : + fn(fn), + name(name), + start(start), + end(end) {} }; -// Take one of the Expr*s passed to the function as an argument, -// evaluate it, return the resulting Value. The caller takes -// ownership of the returned Value. -Value* EvaluateValue(State* state, Expr* expr); +// Evaluate the input expr, return the resulting Value. +Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr); -// Take one of the Expr*s passed to the function as an argument, -// evaluate it, assert that it is a string, and return the resulting -// char*. The caller takes ownership of the returned char*. This is -// a convenience function for older functions that want to deal only -// with strings. -char* Evaluate(State* state, Expr* expr); +// Evaluate the input expr, assert that it is a string, and update the result parameter. This +// function returns true if the evaluation succeeds. This is a convenience function for older +// functions that want to deal only with strings. +bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result); // Glue to make an Expr out of a literal. -Value* Literal(const char* name, State* state, int argc, Expr* argv[]); +Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); // Functions corresponding to various syntactic sugar operators. // ("concat" is also available as a builtin function, to concatenate // more than two strings.) -Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]); -Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]); -Value* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]); -Value* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]); -Value* SubstringFn(const char* name, State* state, int argc, Expr* argv[]); -Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); -Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); -Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]); - -// Convenience function for building expressions with a fixed number -// of arguments. -Expr* Build(Function fn, YYLTYPE loc, int count, ...); +Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* LogicalAndFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* LogicalOrFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* LogicalNotFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* SubstringFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* InequalityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); // Global builtins, registered by RegisterBuiltins(). -Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); -Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]); -Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]); - - -// For setting and getting the global error string (when returning -// NULL from a function). -void SetError(const char* message); // makes a copy -const char* GetError(); // retains ownership -void ClearError(); - - -typedef struct { - const char* name; - Function fn; -} NamedFunction; +Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); +Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv); // Register a new function. The same Function may be registered under // multiple names, but a given name should only be used once. -void RegisterFunction(const char* name, Function fn); +void RegisterFunction(const std::string& name, Function fn); // Register all the builtins. void RegisterBuiltins(); -// Call this after all calls to RegisterFunction() but before parsing -// any scripts to finish building the function table. -void FinishRegistration(); - // Find the Function for a given name; return NULL if no such function // exists. -Function FindFunction(const char* name); - +Function FindFunction(const std::string& name); // --- convenience functions for use in functions --- -// Evaluate the expressions in argv, giving 'count' char* (the ... is -// zero or more char** to put them in). If any expression evaluates -// to NULL, free the rest and return -1. Return 0 on success. -int ReadArgs(State* state, Expr* argv[], int count, ...); - -// Evaluate the expressions in argv, giving 'count' Value* (the ... is -// zero or more Value** to put them in). If any expression evaluates -// to NULL, free the rest and return -1. Return 0 on success. -int ReadValueArgs(State* state, Expr* argv[], int count, ...); - -// Evaluate the expressions in argv, returning an array of char* -// results. If any evaluate to NULL, free the rest and return NULL. -// The caller is responsible for freeing the returned array and the -// strings it contains. -char** ReadVarArgs(State* state, int argc, Expr* argv[]); +// Evaluate the expressions in argv, and put the results of strings in args. If any expression +// evaluates to nullptr, return false. Return true on success. +bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::string>* args); +bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::string>* args, size_t start, size_t len); -// Evaluate the expressions in argv, returning an array of Value* -// results. If any evaluate to NULL, free the rest and return NULL. -// The caller is responsible for freeing the returned array and the -// Values it contains. -Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]); +// Evaluate the expressions in argv, and put the results of Value* in args. If any +// expression evaluate to nullptr, return false. Return true on success. +bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::unique_ptr<Value>>* args); +bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv, + std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len); // Use printf-style arguments to compose an error message to put into // *state. Returns NULL. @@ -172,12 +149,11 @@ Value* ErrorAbort(State* state, const char* format, ...) Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) __attribute__((format(printf, 3, 4))); -// Wrap a string into a Value, taking ownership of the string. -Value* StringValue(char* str); +// Copying the string into a Value. +Value* StringValue(const char* str); -// Free a Value object. -void FreeValue(Value* v); +Value* StringValue(const std::string& str); -int parse_string(const char* str, Expr** root, int* error_count); +int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count); #endif // _EXPRESSION_H diff --git a/edify/main.cpp b/edify/main.cpp deleted file mode 100644 index 6007a3d58..000000000 --- a/edify/main.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2009 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 <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <string> - -#include "expr.h" -#include "parser.h" - -extern int yyparse(Expr** root, int* error_count); - -int expect(const char* expr_str, const char* expected, int* errors) { - Expr* e; - char* result; - - printf("."); - - int error_count = parse_string(expr_str, &e, &error_count); - if (error_count > 0) { - printf("error parsing \"%s\" (%d errors)\n", - expr_str, error_count); - ++*errors; - return 0; - } - - State state; - state.cookie = NULL; - state.script = strdup(expr_str); - state.errmsg = NULL; - - result = Evaluate(&state, e); - free(state.errmsg); - free(state.script); - if (result == NULL && expected != NULL) { - printf("error evaluating \"%s\"\n", expr_str); - ++*errors; - return 0; - } - - if (result == NULL && expected == NULL) { - return 1; - } - - if (strcmp(result, expected) != 0) { - printf("evaluating \"%s\": expected \"%s\", got \"%s\"\n", - expr_str, expected, result); - ++*errors; - free(result); - return 0; - } - - free(result); - return 1; -} - -int test() { - int errors = 0; - - expect("a", "a", &errors); - expect("\"a\"", "a", &errors); - expect("\"\\x61\"", "a", &errors); - expect("# this is a comment\n" - " a\n" - " \n", - "a", &errors); - - - // sequence operator - expect("a; b; c", "c", &errors); - - // string concat operator - expect("a + b", "ab", &errors); - expect("a + \n \"b\"", "ab", &errors); - expect("a + b +\nc\n", "abc", &errors); - - // string concat function - expect("concat(a, b)", "ab", &errors); - expect("concat(a,\n \"b\")", "ab", &errors); - expect("concat(a + b,\nc,\"d\")", "abcd", &errors); - expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors); - - // logical and - expect("a && b", "b", &errors); - expect("a && \"\"", "", &errors); - expect("\"\" && b", "", &errors); - expect("\"\" && \"\"", "", &errors); - expect("\"\" && abort()", "", &errors); // test short-circuiting - expect("t && abort()", NULL, &errors); - - // logical or - expect("a || b", "a", &errors); - expect("a || \"\"", "a", &errors); - expect("\"\" || b", "b", &errors); - expect("\"\" || \"\"", "", &errors); - expect("a || abort()", "a", &errors); // test short-circuiting - expect("\"\" || abort()", NULL, &errors); - - // logical not - expect("!a", "", &errors); - expect("! \"\"", "t", &errors); - expect("!!a", "t", &errors); - - // precedence - expect("\"\" == \"\" && b", "b", &errors); - expect("a + b == ab", "t", &errors); - expect("ab == a + b", "t", &errors); - expect("a + (b == ab)", "a", &errors); - expect("(ab == a) + b", "b", &errors); - - // substring function - expect("is_substring(cad, abracadabra)", "t", &errors); - expect("is_substring(abrac, abracadabra)", "t", &errors); - expect("is_substring(dabra, abracadabra)", "t", &errors); - expect("is_substring(cad, abracxadabra)", "", &errors); - expect("is_substring(abrac, axbracadabra)", "", &errors); - expect("is_substring(dabra, abracadabrxa)", "", &errors); - - // ifelse function - expect("ifelse(t, yes, no)", "yes", &errors); - expect("ifelse(!t, yes, no)", "no", &errors); - expect("ifelse(t, yes, abort())", "yes", &errors); - expect("ifelse(!t, abort(), no)", "no", &errors); - - // if "statements" - expect("if t then yes else no endif", "yes", &errors); - expect("if \"\" then yes else no endif", "no", &errors); - expect("if \"\" then yes endif", "", &errors); - expect("if \"\"; t then yes endif", "yes", &errors); - - // numeric comparisons - expect("less_than_int(3, 14)", "t", &errors); - expect("less_than_int(14, 3)", "", &errors); - expect("less_than_int(x, 3)", "", &errors); - expect("less_than_int(3, x)", "", &errors); - expect("greater_than_int(3, 14)", "", &errors); - expect("greater_than_int(14, 3)", "t", &errors); - expect("greater_than_int(x, 3)", "", &errors); - expect("greater_than_int(3, x)", "", &errors); - - // big string - expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str(), &errors); - - printf("\n"); - - return errors; -} - -void ExprDump(int depth, Expr* n, char* script) { - printf("%*s", depth*2, ""); - char temp = script[n->end]; - script[n->end] = '\0'; - printf("%s %p (%d-%d) \"%s\"\n", - n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end, - script+n->start); - script[n->end] = temp; - int i; - for (i = 0; i < n->argc; ++i) { - ExprDump(depth+1, n->argv[i], script); - } -} - -int main(int argc, char** argv) { - RegisterBuiltins(); - FinishRegistration(); - - if (argc == 1) { - return test() != 0; - } - - FILE* f = fopen(argv[1], "r"); - if (f == NULL) { - printf("%s: %s: No such file or directory\n", argv[0], argv[1]); - return 1; - } - char buffer[8192]; - int size = fread(buffer, 1, 8191, f); - fclose(f); - buffer[size] = '\0'; - - Expr* root; - int error_count = 0; - int error = parse_string(buffer, &root, &error_count); - printf("parse returned %d; %d errors encountered\n", error, error_count); - if (error == 0 || error_count > 0) { - - ExprDump(0, root, buffer); - - State state; - state.cookie = NULL; - state.script = buffer; - state.errmsg = NULL; - - char* result = Evaluate(&state, root); - if (result == NULL) { - printf("result was NULL, message is: %s\n", - (state.errmsg == NULL ? "(NULL)" : state.errmsg)); - free(state.errmsg); - } else { - printf("result is [%s]\n", result); - } - } - return 0; -} diff --git a/edify/parser.yy b/edify/parser.yy index 098a6370a..97205fe3b 100644 --- a/edify/parser.yy +++ b/edify/parser.yy @@ -19,6 +19,10 @@ #include <stdlib.h> #include <string.h> +#include <memory> +#include <string> +#include <vector> + #include "expr.h" #include "yydefs.h" #include "parser.h" @@ -26,13 +30,26 @@ extern int gLine; extern int gColumn; -void yyerror(Expr** root, int* error_count, const char* s); -int yyparse(Expr** root, int* error_count); +void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s); +int yyparse(std::unique_ptr<Expr>* root, int* error_count); struct yy_buffer_state; void yy_switch_to_buffer(struct yy_buffer_state* new_buffer); struct yy_buffer_state* yy_scan_string(const char* yystr); +// Convenience function for building expressions with a fixed number +// of arguments. +static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) { + va_list v; + va_start(v, count); + Expr* e = new Expr(fn, "(operator)", loc.start, loc.end); + for (size_t i = 0; i < count; ++i) { + e->argv.emplace_back(va_arg(v, Expr*)); + } + va_end(v); + return e; +} + %} %locations @@ -40,10 +57,7 @@ struct yy_buffer_state* yy_scan_string(const char* yystr); %union { char* str; Expr* expr; - struct { - int argc; - Expr** argv; - } args; + std::vector<std::unique_ptr<Expr>>* args; } %token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF @@ -51,7 +65,10 @@ struct yy_buffer_state* yy_scan_string(const char* yystr); %type <expr> expr %type <args> arglist -%parse-param {Expr** root} +%destructor { delete $$; } expr +%destructor { delete $$; } arglist + +%parse-param {std::unique_ptr<Expr>* root} %parse-param {int* error_count} %error-verbose @@ -66,17 +83,11 @@ struct yy_buffer_state* yy_scan_string(const char* yystr); %% -input: expr { *root = $1; } +input: expr { root->reset($1); } ; expr: STRING { - $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); - $$->fn = Literal; - $$->name = $1; - $$->argc = 0; - $$->argv = NULL; - $$->start = @$.start; - $$->end = @$.end; + $$ = new Expr(Literal, $1, @$.start, @$.end); } | '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; } | expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; } @@ -91,41 +102,32 @@ expr: STRING { | IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); } | IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); } | STRING '(' arglist ')' { - $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr))); - $$->fn = FindFunction($1); - if ($$->fn == NULL) { - char buffer[256]; - snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1); - yyerror(root, error_count, buffer); + Function fn = FindFunction($1); + if (fn == nullptr) { + std::string msg = "unknown function \"" + std::string($1) + "\""; + yyerror(root, error_count, msg.c_str()); YYERROR; } - $$->name = $1; - $$->argc = $3.argc; - $$->argv = $3.argv; - $$->start = @$.start; - $$->end = @$.end; + $$ = new Expr(fn, $1, @$.start, @$.end); + $$->argv = std::move(*$3); } ; arglist: /* empty */ { - $$.argc = 0; - $$.argv = NULL; + $$ = new std::vector<std::unique_ptr<Expr>>; } | expr { - $$.argc = 1; - $$.argv = reinterpret_cast<Expr**>(malloc(sizeof(Expr*))); - $$.argv[0] = $1; + $$ = new std::vector<std::unique_ptr<Expr>>; + $$->emplace_back($1); } | arglist ',' expr { - $$.argc = $1.argc + 1; - $$.argv = reinterpret_cast<Expr**>(realloc($$.argv, $$.argc * sizeof(Expr*))); - $$.argv[$$.argc-1] = $3; + $$->push_back(std::unique_ptr<Expr>($3)); } ; %% -void yyerror(Expr** root, int* error_count, const char* s) { +void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s) { if (strlen(s) == 0) { s = "syntax error"; } @@ -133,7 +135,7 @@ void yyerror(Expr** root, int* error_count, const char* s) { ++*error_count; } -int parse_string(const char* str, Expr** root, int* error_count) { +int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count) { yy_switch_to_buffer(yy_scan_string(str)); return yyparse(root, error_count); } diff --git a/error_code.h b/error_code.h index dfea0eb38..cde4ee6de 100644 --- a/error_code.h +++ b/error_code.h @@ -18,52 +18,54 @@ #define _ERROR_CODE_H_ enum ErrorCode { - kNoError = -1, - kLowBattery = 20, - kZipVerificationFailure, - kZipOpenFailure, - kBootreasonInBlacklist + kNoError = -1, + kLowBattery = 20, + kZipVerificationFailure, + kZipOpenFailure, + kBootreasonInBlacklist, + kPackageCompatibilityFailure, }; enum CauseCode { - kNoCause = -1, - kArgsParsingFailure = 100, - kStashCreationFailure, - kFileOpenFailure, - kLseekFailure, - kFreadFailure, - kFwriteFailure, - kFsyncFailure, - kLibfecFailure, - kFileGetPropFailure, - kFileRenameFailure, - kSymlinkFailure, - kSetMetadataFailure, - kTune2FsFailure, - kRebootFailure, - kVendorFailure = 200 + kNoCause = -1, + kArgsParsingFailure = 100, + kStashCreationFailure, + kFileOpenFailure, + kLseekFailure, + kFreadFailure, + kFwriteFailure, + kFsyncFailure, + kLibfecFailure, + kFileGetPropFailure, + kFileRenameFailure, + kSymlinkFailure, + kSetMetadataFailure, + kTune2FsFailure, + kRebootFailure, + kPackageExtractFileFailure, + kVendorFailure = 200 }; enum UncryptErrorCode { - kUncryptNoError = -1, - kUncryptErrorPlaceholder = 50, - kUncryptTimeoutError = 100, - kUncryptFileRemoveError, - kUncryptFileOpenError, - kUncryptSocketOpenError, - kUncryptSocketWriteError, - kUncryptSocketListenError, - kUncryptSocketAcceptError, - kUncryptFstabReadError, - kUncryptFileStatError, - kUncryptBlockOpenError, - kUncryptIoctlError, - kUncryptReadError, - kUncryptWriteError, - kUncryptFileSyncError, - kUncryptFileCloseError, - kUncryptFileRenameError, - kUncryptPackageMissingError, + kUncryptNoError = -1, + kUncryptErrorPlaceholder = 50, + kUncryptTimeoutError = 100, + kUncryptFileRemoveError, + kUncryptFileOpenError, + kUncryptSocketOpenError, + kUncryptSocketWriteError, + kUncryptSocketListenError, + kUncryptSocketAcceptError, + kUncryptFstabReadError, + kUncryptFileStatError, + kUncryptBlockOpenError, + kUncryptIoctlError, + kUncryptReadError, + kUncryptWriteError, + kUncryptFileSyncError, + kUncryptFileCloseError, + kUncryptFileRenameError, + kUncryptPackageMissingError, }; -#endif +#endif // _ERROR_CODE_H_ diff --git a/etc/META-INF/com/google/android/update-script b/etc/META-INF/com/google/android/update-script deleted file mode 100644 index b091b1927..000000000 --- a/etc/META-INF/com/google/android/update-script +++ /dev/null @@ -1,8 +0,0 @@ -assert compatible_with("0.1") == "true" -assert file_contains("SYSTEM:build.prop", "ro.product.device=dream") == "true" || file_contains("SYSTEM:build.prop", "ro.build.product=dream") == "true" -assert file_contains("RECOVERY:default.prop", "ro.product.device=dream") == "true" || file_contains("RECOVERY:default.prop", "ro.build.product=dream") == "true" -assert getprop("ro.product.device") == "dream" -format BOOT: -format SYSTEM: -copy_dir PACKAGE:system SYSTEM: -write_raw_image PACKAGE:boot.img BOOT: diff --git a/etc/init.rc b/etc/init.rc index 5915b8d80..d8121cc4e 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -5,7 +5,6 @@ on early-init restorecon /postinstall start ueventd - start healthd on init export ANDROID_ROOT /system @@ -14,6 +13,9 @@ on init symlink /system/etc /etc + mount cgroup none /acct cpuacct + mkdir /acct/uid + mkdir /sdcard mkdir /system mkdir /data @@ -28,6 +30,7 @@ on init write /proc/sys/vm/max_map_count 1000000 on fs + write /sys/class/android_usb/android0/f_ffs/aliases adb mkdir /dev/usb-ffs 0770 shell shell mkdir /dev/usb-ffs/adb 0770 shell shell mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000 @@ -35,7 +38,6 @@ on fs write /sys/class/android_usb/android0/enable 0 write /sys/class/android_usb/android0/idVendor 18D1 write /sys/class/android_usb/android0/idProduct D001 - write /sys/class/android_usb/android0/f_ffs/aliases adb write /sys/class/android_usb/android0/functions adb write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer} write /sys/class/android_usb/android0/iProduct ${ro.product.model} @@ -73,16 +75,13 @@ on late-init trigger early-boot trigger boot -on property:sys.powerctl=* - powerctl ${sys.powerctl} - service ueventd /sbin/ueventd critical seclabel u:r:ueventd:s0 -service healthd /sbin/healthd -r +service charger /charger -r critical - seclabel u:r:healthd:s0 + seclabel u:r:charger:s0 service recovery /sbin/recovery seclabel u:r:recovery:s0 diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp index df9631272..b0ecf96be 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -23,6 +23,8 @@ #include <unistd.h> #include <fcntl.h> +#include <android-base/file.h> + #include "fuse_sideload.h" struct file_data { @@ -41,14 +43,9 @@ static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32 return -EIO; } - while (fetch_size > 0) { - ssize_t r = TEMP_FAILURE_RETRY(read(fd->fd, buffer, fetch_size)); - if (r == -1) { - fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); - return -EIO; - } - fetch_size -= r; - buffer += r; + if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) { + fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); + return -EIO; } return 0; diff --git a/install.cpp b/install.cpp index e144d9b29..ffeba2e18 100644 --- a/install.cpp +++ b/install.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "install.h" + #include <ctype.h> #include <errno.h> #include <fcntl.h> @@ -24,48 +26,53 @@ #include <sys/wait.h> #include <unistd.h> +#include <algorithm> #include <chrono> +#include <condition_variable> +#include <functional> #include <limits> #include <map> +#include <mutex> #include <string> +#include <thread> #include <vector> #include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parsedouble.h> #include <android-base/parseint.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include <cutils/properties.h> +#include <vintf/VintfObjectRecovery.h> +#include <ziparchive/zip_archive.h> #include "common.h" #include "error_code.h" -#include "install.h" #include "minui/minui.h" -#include "minzip/SysUtil.h" -#include "minzip/Zip.h" -#include "mtdutils/mounts.h" -#include "mtdutils/mtdutils.h" +#include "otautil/SysUtil.h" +#include "otautil/ThermalUtil.h" #include "roots.h" #include "ui.h" #include "verifier.h" -extern RecoveryUI* ui; +using namespace std::chrono_literals; -#define ASSUMED_UPDATE_BINARY_NAME "META-INF/com/google/android/update-binary" -static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt"; -static constexpr const char* AB_OTA_PAYLOAD = "payload.bin"; #define PUBLIC_KEYS_FILE "/res/keys" static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata"; static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; // Default allocation of progress bar segments to operations -static const int VERIFICATION_PROGRESS_TIME = 60; -static const float VERIFICATION_PROGRESS_FRACTION = 0.25; -static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; -static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; +static constexpr int VERIFICATION_PROGRESS_TIME = 60; +static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25; +static constexpr float DEFAULT_FILES_PROGRESS_FRACTION = 0.4; +static constexpr float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1; + +static std::condition_variable finish_log_temperature; // This function parses and returns the build.version.incremental -static int parse_build_number(std::string str) { - size_t pos = str.find("="); +static int parse_build_number(const std::string& str) { + size_t pos = str.find('='); if (pos != std::string::npos) { std::string num_string = android::base::Trim(str.substr(pos+1)); int build_number; @@ -74,27 +81,33 @@ static int parse_build_number(std::string str) { } } - LOGE("Failed to parse build number in %s.\n", str.c_str()); + LOG(ERROR) << "Failed to parse build number in " << str; return -1; } -bool read_metadata_from_package(ZipArchive* zip, std::string* meta_data) { - const ZipEntry* meta_entry = mzFindZipEntry(zip, METADATA_PATH); - if (meta_entry == nullptr) { - LOGE("Failed to find %s in update package.\n", METADATA_PATH); +bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data) { + ZipString metadata_path(METADATA_PATH); + ZipEntry meta_entry; + if (meta_data == nullptr) { + LOG(ERROR) << "string* meta_data can't be nullptr"; + return false; + } + if (FindEntry(zip, metadata_path, &meta_entry) != 0) { + LOG(ERROR) << "Failed to find " << METADATA_PATH << " in update package"; return false; } - meta_data->resize(meta_entry->uncompLen, '\0'); - if (!mzReadZipEntry(zip, meta_entry, &(*meta_data)[0], meta_entry->uncompLen)) { - LOGE("Failed to read metadata in update package.\n"); + meta_data->resize(meta_entry.uncompressed_length, '\0'); + if (ExtractToMemory(zip, &meta_entry, reinterpret_cast<uint8_t*>(&(*meta_data)[0]), + meta_entry.uncompressed_length) != 0) { + LOG(ERROR) << "Failed to read metadata in update package"; return false; } return true; } // Read the build.version.incremental of src/tgt from the metadata and log it to last_install. -static void read_source_target_build(ZipArchive* zip, std::vector<std::string>& log_buffer) { +static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>& log_buffer) { std::string meta_data; if (!read_metadata_from_package(zip, &meta_data)) { return; @@ -121,348 +134,438 @@ static void read_source_target_build(ZipArchive* zip, std::vector<std::string>& } } -// Extract the update binary from the open zip archive |zip| located at |path| -// and store into |cmd| the command line that should be called. The |status_fd| -// is the file descriptor the child process should use to report back the -// progress of the update. -static int -update_binary_command(const char* path, ZipArchive* zip, int retry_count, - int status_fd, std::vector<std::string>* cmd); +// Extract the update binary from the open zip archive |zip| located at |path| and store into |cmd| +// the command line that should be called. The |status_fd| is the file descriptor the child process +// should use to report back the progress of the update. +int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector<std::string>* cmd); #ifdef AB_OTA_UPDATER // Parses the metadata of the OTA package in |zip| and checks whether we are // allowed to accept this A/B package. Downgrading is not allowed unless // explicitly enabled in the package and only for incremental packages. -static int check_newer_ab_build(ZipArchive* zip) -{ - std::string metadata_str; - if (!read_metadata_from_package(zip, &metadata_str)) { - return INSTALL_CORRUPT; - } - std::map<std::string, std::string> metadata; - for (const std::string& line : android::base::Split(metadata_str, "\n")) { - size_t eq = line.find('='); - if (eq != std::string::npos) { - metadata[line.substr(0, eq)] = line.substr(eq + 1); - } - } - char value[PROPERTY_VALUE_MAX]; - - property_get("ro.product.device", value, ""); - const std::string& pkg_device = metadata["pre-device"]; - if (pkg_device != value || pkg_device.empty()) { - LOGE("Package is for product %s but expected %s\n", - pkg_device.c_str(), value); - return INSTALL_ERROR; - } - - // We allow the package to not have any serialno, but if it has a non-empty - // value it should match. - property_get("ro.serialno", value, ""); - const std::string& pkg_serial_no = metadata["serialno"]; - if (!pkg_serial_no.empty() && pkg_serial_no != value) { - LOGE("Package is for serial %s\n", pkg_serial_no.c_str()); - return INSTALL_ERROR; - } - - if (metadata["ota-type"] != "AB") { - LOGE("Package is not A/B\n"); - return INSTALL_ERROR; - } - - // Incremental updates should match the current build. - property_get("ro.build.version.incremental", value, ""); - const std::string& pkg_pre_build = metadata["pre-build-incremental"]; - if (!pkg_pre_build.empty() && pkg_pre_build != value) { - LOGE("Package is for source build %s but expected %s\n", - pkg_pre_build.c_str(), value); - return INSTALL_ERROR; - } - property_get("ro.build.fingerprint", value, ""); - const std::string& pkg_pre_build_fingerprint = metadata["pre-build"]; - if (!pkg_pre_build_fingerprint.empty() && - pkg_pre_build_fingerprint != value) { - LOGE("Package is for source build %s but expected %s\n", - pkg_pre_build_fingerprint.c_str(), value); - return INSTALL_ERROR; - } - - // Check for downgrade version. - int64_t build_timestampt = property_get_int64( - "ro.build.date.utc", std::numeric_limits<int64_t>::max()); - int64_t pkg_post_timespampt = 0; - // We allow to full update to the same version we are running, in case there - // is a problem with the current copy of that version. - if (metadata["post-timestamp"].empty() || - !android::base::ParseInt(metadata["post-timestamp"].c_str(), - &pkg_post_timespampt) || - pkg_post_timespampt < build_timestampt) { - if (metadata["ota-downgrade"] != "yes") { - LOGE("Update package is older than the current build, expected a " - "build newer than timestamp %" PRIu64 " but package has " - "timestamp %" PRIu64 " and downgrade not allowed.\n", - build_timestampt, pkg_post_timespampt); - return INSTALL_ERROR; - } - if (pkg_pre_build_fingerprint.empty()) { - LOGE("Downgrade package must have a pre-build version set, not " - "allowed.\n"); - return INSTALL_ERROR; - } - } - - return 0; +static int check_newer_ab_build(ZipArchiveHandle zip) { + std::string metadata_str; + if (!read_metadata_from_package(zip, &metadata_str)) { + return INSTALL_CORRUPT; + } + std::map<std::string, std::string> metadata; + for (const std::string& line : android::base::Split(metadata_str, "\n")) { + size_t eq = line.find('='); + if (eq != std::string::npos) { + metadata[line.substr(0, eq)] = line.substr(eq + 1); + } + } + + std::string value = android::base::GetProperty("ro.product.device", ""); + const std::string& pkg_device = metadata["pre-device"]; + if (pkg_device != value || pkg_device.empty()) { + LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value; + return INSTALL_ERROR; + } + + // We allow the package to not have any serialno, but if it has a non-empty + // value it should match. + value = android::base::GetProperty("ro.serialno", ""); + const std::string& pkg_serial_no = metadata["serialno"]; + if (!pkg_serial_no.empty() && pkg_serial_no != value) { + LOG(ERROR) << "Package is for serial " << pkg_serial_no; + return INSTALL_ERROR; + } + + if (metadata["ota-type"] != "AB") { + LOG(ERROR) << "Package is not A/B"; + return INSTALL_ERROR; + } + + // Incremental updates should match the current build. + value = android::base::GetProperty("ro.build.version.incremental", ""); + const std::string& pkg_pre_build = metadata["pre-build-incremental"]; + if (!pkg_pre_build.empty() && pkg_pre_build != value) { + LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value; + return INSTALL_ERROR; + } + + value = android::base::GetProperty("ro.build.fingerprint", ""); + const std::string& pkg_pre_build_fingerprint = metadata["pre-build"]; + if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) { + LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected " + << value; + return INSTALL_ERROR; + } + + // Check for downgrade version. + int64_t build_timestamp = + android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max()); + int64_t pkg_post_timestamp = 0; + // We allow to full update to the same version we are running, in case there + // is a problem with the current copy of that version. + if (metadata["post-timestamp"].empty() || + !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) || + pkg_post_timestamp < build_timestamp) { + if (metadata["ota-downgrade"] != "yes") { + LOG(ERROR) << "Update package is older than the current build, expected a build " + "newer than timestamp " + << build_timestamp << " but package has timestamp " << pkg_post_timestamp + << " and downgrade not allowed."; + return INSTALL_ERROR; + } + if (pkg_pre_build_fingerprint.empty()) { + LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed."; + return INSTALL_ERROR; + } + } + + return 0; } -static int -update_binary_command(const char* path, ZipArchive* zip, int retry_count, - int status_fd, std::vector<std::string>* cmd) -{ - int ret = check_newer_ab_build(zip); - if (ret) { - return ret; - } - - // For A/B updates we extract the payload properties to a buffer and obtain - // the RAW payload offset in the zip file. - const ZipEntry* properties_entry = - mzFindZipEntry(zip, AB_OTA_PAYLOAD_PROPERTIES); - if (!properties_entry) { - LOGE("Can't find %s\n", AB_OTA_PAYLOAD_PROPERTIES); - return INSTALL_CORRUPT; - } - std::vector<unsigned char> payload_properties( - mzGetZipEntryUncompLen(properties_entry)); - if (!mzExtractZipEntryToBuffer(zip, properties_entry, - payload_properties.data())) { - LOGE("Can't extract %s\n", AB_OTA_PAYLOAD_PROPERTIES); - return INSTALL_CORRUPT; - } - - const ZipEntry* payload_entry = mzFindZipEntry(zip, AB_OTA_PAYLOAD); - if (!payload_entry) { - LOGE("Can't find %s\n", AB_OTA_PAYLOAD); - return INSTALL_CORRUPT; - } - long payload_offset = mzGetZipEntryOffset(payload_entry); - *cmd = { - "/sbin/update_engine_sideload", - android::base::StringPrintf("--payload=file://%s", path), - android::base::StringPrintf("--offset=%ld", payload_offset), - "--headers=" + std::string(payload_properties.begin(), - payload_properties.end()), - android::base::StringPrintf("--status_fd=%d", status_fd), - }; - return 0; +int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector<std::string>* cmd) { + CHECK(cmd != nullptr); + int ret = check_newer_ab_build(zip); + if (ret != 0) { + return ret; + } + + // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset + // in the zip file. + static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt"; + ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES); + ZipEntry properties_entry; + if (FindEntry(zip, property_name, &properties_entry) != 0) { + LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES; + return INSTALL_CORRUPT; + } + uint32_t properties_entry_length = properties_entry.uncompressed_length; + std::vector<uint8_t> payload_properties(properties_entry_length); + int32_t err = + ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length); + if (err != 0) { + LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err); + return INSTALL_CORRUPT; + } + + static constexpr const char* AB_OTA_PAYLOAD = "payload.bin"; + ZipString payload_name(AB_OTA_PAYLOAD); + ZipEntry payload_entry; + if (FindEntry(zip, payload_name, &payload_entry) != 0) { + LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD; + return INSTALL_CORRUPT; + } + long payload_offset = payload_entry.offset; + *cmd = { + "/sbin/update_engine_sideload", + "--payload=file://" + path, + android::base::StringPrintf("--offset=%ld", payload_offset), + "--headers=" + std::string(payload_properties.begin(), payload_properties.end()), + android::base::StringPrintf("--status_fd=%d", status_fd), + }; + return 0; } #else // !AB_OTA_UPDATER -static int -update_binary_command(const char* path, ZipArchive* zip, int retry_count, - int status_fd, std::vector<std::string>* cmd) -{ - // On traditional updates we extract the update binary from the package. - const ZipEntry* binary_entry = - mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); - if (binary_entry == NULL) { - return INSTALL_CORRUPT; - } - - const char* binary = "/tmp/update_binary"; - unlink(binary); - int fd = creat(binary, 0755); - if (fd < 0) { - LOGE("Can't make %s\n", binary); - return INSTALL_ERROR; - } - bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); - close(fd); - - if (!ok) { - LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME); - return INSTALL_ERROR; - } - - *cmd = { - binary, - EXPAND(RECOVERY_API_VERSION), // defined in Android.mk - std::to_string(status_fd), - path, - }; - if (retry_count > 0) - cmd->push_back("retry"); - return 0; +int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector<std::string>* cmd) { + CHECK(cmd != nullptr); + + // On traditional updates we extract the update binary from the package. + static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary"; + ZipString binary_name(UPDATE_BINARY_NAME); + ZipEntry binary_entry; + if (FindEntry(zip, binary_name, &binary_entry) != 0) { + LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME; + return INSTALL_CORRUPT; + } + + const char* binary = "/tmp/update_binary"; + unlink(binary); + int fd = creat(binary, 0755); + if (fd == -1) { + PLOG(ERROR) << "Failed to create " << binary; + return INSTALL_ERROR; + } + + int32_t error = ExtractEntryToFile(zip, &binary_entry, fd); + close(fd); + if (error != 0) { + LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error); + return INSTALL_ERROR; + } + + *cmd = { + binary, + EXPAND(RECOVERY_API_VERSION), // defined in Android.mk + std::to_string(status_fd), + path, + }; + if (retry_count > 0) { + cmd->push_back("retry"); + } + return 0; } #endif // !AB_OTA_UPDATER -// If the package contains an update binary, extract it and run it. -static int -try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache, - std::vector<std::string>& log_buffer, int retry_count) -{ - read_source_target_build(zip, log_buffer); - - int pipefd[2]; - pipe(pipefd); - - std::vector<std::string> args; - int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args); - mzCloseZipArchive(zip); - if (ret) { - close(pipefd[0]); - close(pipefd[1]); - return ret; - } +static void log_max_temperature(int* max_temperature) { + CHECK(max_temperature != nullptr); + std::mutex mtx; + std::unique_lock<std::mutex> lck(mtx); + while (finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) { + *max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone()); + } +} - // When executing the update binary contained in the package, the - // arguments passed are: - // - // - the version number for this interface - // - // - an fd to which the program can write in order to update the - // progress bar. The program can write single-line commands: - // - // progress <frac> <secs> - // fill up the next <frac> part of of the progress bar - // over <secs> seconds. If <secs> is zero, use - // set_progress commands to manually control the - // progress of this segment of the bar. - // - // set_progress <frac> - // <frac> should be between 0.0 and 1.0; sets the - // progress bar within the segment defined by the most - // recent progress command. - // - // firmware <"hboot"|"radio"> <filename> - // arrange to install the contents of <filename> in the - // given partition on reboot. - // - // (API v2: <filename> may start with "PACKAGE:" to - // indicate taking a file from the OTA package.) - // - // (API v3: this command no longer exists.) - // - // ui_print <string> - // display <string> on the screen. - // - // wipe_cache - // a wipe of cache will be performed following a successful - // installation. - // - // clear_display - // turn off the text display. - // - // enable_reboot - // packages can explicitly request that they want the user - // to be able to reboot during installation (useful for - // debugging packages that don't exit). - // - // - the name of the package zip file. - // - // - an optional argument "retry" if this update is a retry of a failed - // update attempt. - // - - // Convert the vector to a NULL-terminated char* array suitable for execv. - const char* chr_args[args.size() + 1]; - chr_args[args.size()] = NULL; - for (size_t i = 0; i < args.size(); i++) { - chr_args[i] = args[i].c_str(); +// If the package contains an update binary, extract it and run it. +static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache, + std::vector<std::string>& log_buffer, int retry_count, + int* max_temperature) { + read_source_target_build(zip, log_buffer); + + int pipefd[2]; + pipe(pipefd); + + std::vector<std::string> args; + int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args); + if (ret) { + close(pipefd[0]); + close(pipefd[1]); + return ret; + } + + // When executing the update binary contained in the package, the + // arguments passed are: + // + // - the version number for this interface + // + // - an FD to which the program can write in order to update the + // progress bar. The program can write single-line commands: + // + // progress <frac> <secs> + // fill up the next <frac> part of of the progress bar + // over <secs> seconds. If <secs> is zero, use + // set_progress commands to manually control the + // progress of this segment of the bar. + // + // set_progress <frac> + // <frac> should be between 0.0 and 1.0; sets the + // progress bar within the segment defined by the most + // recent progress command. + // + // ui_print <string> + // display <string> on the screen. + // + // wipe_cache + // a wipe of cache will be performed following a successful + // installation. + // + // clear_display + // turn off the text display. + // + // enable_reboot + // packages can explicitly request that they want the user + // to be able to reboot during installation (useful for + // debugging packages that don't exit). + // + // retry_update + // updater encounters some issue during the update. It requests + // a reboot to retry the same package automatically. + // + // log <string> + // updater requests logging the string (e.g. cause of the + // failure). + // + // - the name of the package zip file. + // + // - an optional argument "retry" if this update is a retry of a failed + // update attempt. + // + + // Convert the vector to a NULL-terminated char* array suitable for execv. + const char* chr_args[args.size() + 1]; + chr_args[args.size()] = nullptr; + for (size_t i = 0; i < args.size(); i++) { + chr_args[i] = args[i].c_str(); + } + + pid_t pid = fork(); + + if (pid == -1) { + close(pipefd[0]); + close(pipefd[1]); + PLOG(ERROR) << "Failed to fork update binary"; + return INSTALL_ERROR; + } + + if (pid == 0) { + umask(022); + close(pipefd[0]); + execv(chr_args[0], const_cast<char**>(chr_args)); + // Bug: 34769056 + // We shouldn't use LOG/PLOG in the forked process, since they may cause + // the child process to hang. This deadlock results from an improperly + // copied mutex in the ui functions. + fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno)); + _exit(EXIT_FAILURE); + } + close(pipefd[1]); + + std::thread temperature_logger(log_max_temperature, max_temperature); + + *wipe_cache = false; + bool retry_update = false; + + char buffer[1024]; + FILE* from_child = fdopen(pipefd[0], "r"); + while (fgets(buffer, sizeof(buffer), from_child) != nullptr) { + std::string line(buffer); + size_t space = line.find_first_of(" \n"); + std::string command(line.substr(0, space)); + if (command.empty()) continue; + + // Get rid of the leading and trailing space and/or newline. + std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space)); + + if (command == "progress") { + std::vector<std::string> tokens = android::base::Split(args, " "); + double fraction; + int seconds; + if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) && + android::base::ParseInt(tokens[1], &seconds)) { + ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds); + } else { + LOG(ERROR) << "invalid \"progress\" parameters: " << line; + } + } else if (command == "set_progress") { + std::vector<std::string> tokens = android::base::Split(args, " "); + double fraction; + if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) { + ui->SetProgress(fraction); + } else { + LOG(ERROR) << "invalid \"set_progress\" parameters: " << line; + } + } else if (command == "ui_print") { + ui->PrintOnScreenOnly("%s\n", args.c_str()); + fflush(stdout); + } else if (command == "wipe_cache") { + *wipe_cache = true; + } else if (command == "clear_display") { + ui->SetBackground(RecoveryUI::NONE); + } else if (command == "enable_reboot") { + // packages can explicitly request that they want the user + // to be able to reboot during installation (useful for + // debugging packages that don't exit). + ui->SetEnableReboot(true); + } else if (command == "retry_update") { + retry_update = true; + } else if (command == "log") { + if (!args.empty()) { + // Save the logging request from updater and write to last_install later. + log_buffer.push_back(args); + } else { + LOG(ERROR) << "invalid \"log\" parameters: " << line; + } + } else { + LOG(ERROR) << "unknown command [" << command << "]"; } + } + fclose(from_child); - pid_t pid = fork(); + int status; + waitpid(pid, &status, 0); - if (pid == -1) { - close(pipefd[0]); - close(pipefd[1]); - LOGE("Failed to fork update binary: %s\n", strerror(errno)); - return INSTALL_ERROR; - } + finish_log_temperature.notify_one(); + temperature_logger.join(); - if (pid == 0) { - umask(022); - close(pipefd[0]); - execv(chr_args[0], const_cast<char**>(chr_args)); - fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno)); - _exit(-1); - } - close(pipefd[1]); + if (retry_update) { + return INSTALL_RETRY; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")"; + return INSTALL_ERROR; + } - *wipe_cache = false; - bool retry_update = false; - - char buffer[1024]; - FILE* from_child = fdopen(pipefd[0], "r"); - while (fgets(buffer, sizeof(buffer), from_child) != NULL) { - char* command = strtok(buffer, " \n"); - if (command == NULL) { - continue; - } else if (strcmp(command, "progress") == 0) { - char* fraction_s = strtok(NULL, " \n"); - char* seconds_s = strtok(NULL, " \n"); - - float fraction = strtof(fraction_s, NULL); - int seconds = strtol(seconds_s, NULL, 10); - - ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds); - } else if (strcmp(command, "set_progress") == 0) { - char* fraction_s = strtok(NULL, " \n"); - float fraction = strtof(fraction_s, NULL); - ui->SetProgress(fraction); - } else if (strcmp(command, "ui_print") == 0) { - char* str = strtok(NULL, "\n"); - if (str) { - ui->PrintOnScreenOnly("%s", str); - } else { - ui->PrintOnScreenOnly("\n"); - } - fflush(stdout); - } else if (strcmp(command, "wipe_cache") == 0) { - *wipe_cache = true; - } else if (strcmp(command, "clear_display") == 0) { - ui->SetBackground(RecoveryUI::NONE); - } else if (strcmp(command, "enable_reboot") == 0) { - // packages can explicitly request that they want the user - // to be able to reboot during installation (useful for - // debugging packages that don't exit). - ui->SetEnableReboot(true); - } else if (strcmp(command, "retry_update") == 0) { - retry_update = true; - } else if (strcmp(command, "log") == 0) { - // Save the logging request from updater and write to - // last_install later. - log_buffer.push_back(std::string(strtok(NULL, "\n"))); - } else { - LOGE("unknown command [%s]\n", command); - } - } - fclose(from_child); + return INSTALL_SUCCESS; +} - int status; - waitpid(pid, &status, 0); - if (retry_update) { - return INSTALL_RETRY; - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status)); - return INSTALL_ERROR; - } +// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the +// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA +// package. +bool verify_package_compatibility(ZipArchiveHandle package_zip) { + LOG(INFO) << "Verifying package compatibility..."; + + static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip"; + ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY); + ZipEntry compatibility_entry; + if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) { + LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry"; + return true; + } + + std::string zip_content(compatibility_entry.uncompressed_length, '\0'); + int32_t ret; + if ((ret = ExtractToMemory(package_zip, &compatibility_entry, + reinterpret_cast<uint8_t*>(&zip_content[0]), + compatibility_entry.uncompressed_length)) != 0) { + LOG(ERROR) << "Failed to read " << COMPATIBILITY_ZIP_ENTRY << ": " << ErrorCodeString(ret); + return false; + } + + ZipArchiveHandle zip_handle; + ret = OpenArchiveFromMemory(static_cast<void*>(const_cast<char*>(zip_content.data())), + zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle); + if (ret != 0) { + LOG(ERROR) << "Failed to OpenArchiveFromMemory: " << ErrorCodeString(ret); + return false; + } + + // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents. + void* cookie; + ret = StartIteration(zip_handle, &cookie, nullptr, nullptr); + if (ret != 0) { + LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret); + CloseArchive(zip_handle); + return false; + } + std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration); + + std::vector<std::string> compatibility_info; + ZipEntry info_entry; + ZipString info_name; + while (Next(cookie, &info_entry, &info_name) == 0) { + std::string content(info_entry.uncompressed_length, '\0'); + int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]), + info_entry.uncompressed_length); + if (ret != 0) { + LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret); + CloseArchive(zip_handle); + return false; + } + compatibility_info.emplace_back(std::move(content)); + } + CloseArchive(zip_handle); + + // VintfObjectRecovery::CheckCompatibility returns zero on success. + std::string err; + int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err); + if (result == 0) { + return true; + } - return INSTALL_SUCCESS; + LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err; + return false; } static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, - std::vector<std::string>& log_buffer, int retry_count) + std::vector<std::string>& log_buffer, int retry_count, int* max_temperature) { ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); ui->Print("Finding update package...\n"); // Give verification half the progress bar... ui->SetProgressType(RecoveryUI::DETERMINATE); ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); - LOGI("Update location: %s\n", path); + LOG(INFO) << "Update location: " << path; // Map the update package into memory. ui->Print("Opening update package...\n"); @@ -477,7 +580,7 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, MemMapping map; if (sysMapFile(path, &map) != 0) { - LOGE("failed to map file\n"); + LOG(ERROR) << "failed to map file"; return INSTALL_CORRUPT; } @@ -489,28 +592,37 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, } // Try to open the package. - ZipArchive zip; - int err = mzOpenZipArchive(map.addr, map.length, &zip); + ZipArchiveHandle zip; + int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip); if (err != 0) { - LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad"); + LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err); log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); sysReleaseMap(&map); + CloseArchive(zip); return INSTALL_CORRUPT; } + // Additionally verify the compatibility of the package. + if (!verify_package_compatibility(zip)) { + log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure)); + sysReleaseMap(&map); + CloseArchive(zip); + return INSTALL_CORRUPT; + } + // Verify and install the contents of the package. ui->Print("Installing update...\n"); if (retry_count > 0) { ui->Print("Retry attempt: %d\n", retry_count); } ui->SetEnableReboot(false); - int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count); + int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature); ui->SetEnableReboot(true); ui->Print("\n"); sysReleaseMap(&map); - + CloseArchive(zip); return result; } @@ -521,30 +633,38 @@ install_package(const char* path, bool* wipe_cache, const char* install_file, modified_flash = true; auto start = std::chrono::system_clock::now(); + int start_temperature = GetMaxValueFromThermalZone(); + int max_temperature = start_temperature; + int result; std::vector<std::string> log_buffer; if (setup_install_mounts() != 0) { - LOGE("failed to set up expected mounts for install; aborting\n"); + LOG(ERROR) << "failed to set up expected mounts for install; aborting"; result = INSTALL_ERROR; } else { - result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count); + result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count, + &max_temperature); } // Measure the time spent to apply OTA update in seconds. std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; int time_total = static_cast<int>(duration.count()); - if (ensure_path_mounted(UNCRYPT_STATUS) != 0) { - LOGW("Can't mount %s\n", UNCRYPT_STATUS); - } else { + bool has_cache = volume_for_path("/cache") != nullptr; + // Skip logging the uncrypt_status on devices without /cache. + if (has_cache) { + if (ensure_path_mounted(UNCRYPT_STATUS) != 0) { + LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS; + } else { std::string uncrypt_status; if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) { - LOGW("failed to read uncrypt status: %s\n", strerror(errno)); + PLOG(WARNING) << "failed to read uncrypt status"; } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) { - LOGW("corrupted uncrypt_status: %s: %s\n", uncrypt_status.c_str(), strerror(errno)); + LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status; } else { - log_buffer.push_back(android::base::Trim(uncrypt_status)); + log_buffer.push_back(android::base::Trim(uncrypt_status)); } + } } // The first two lines need to be the package name and install result. @@ -554,36 +674,50 @@ install_package(const char* path, bool* wipe_cache, const char* install_file, "time_total: " + std::to_string(time_total), "retry: " + std::to_string(retry_count), }; + + int end_temperature = GetMaxValueFromThermalZone(); + max_temperature = std::max(end_temperature, max_temperature); + if (start_temperature > 0) { + log_buffer.push_back("temperature_start: " + std::to_string(start_temperature)); + } + if (end_temperature > 0) { + log_buffer.push_back("temperature_end: " + std::to_string(end_temperature)); + } + if (max_temperature > 0) { + log_buffer.push_back("temperature_max: " + std::to_string(max_temperature)); + } + std::string log_content = android::base::Join(log_header, "\n") + "\n" + - android::base::Join(log_buffer, "\n"); + android::base::Join(log_buffer, "\n") + "\n"; if (!android::base::WriteStringToFile(log_content, install_file)) { - LOGE("failed to write %s: %s\n", install_file, strerror(errno)); + PLOG(ERROR) << "failed to write " << install_file; } // Write a copy into last_log. - LOGI("%s\n", log_content.c_str()); + LOG(INFO) << log_content; return result; } bool verify_package(const unsigned char* package_data, size_t package_size) { - std::vector<Certificate> loadedKeys; - if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { - LOGE("Failed to load keys\n"); - return false; - } - LOGI("%zu key(s) loaded from %s\n", loadedKeys.size(), PUBLIC_KEYS_FILE); - - // Verify package. - ui->Print("Verifying update package...\n"); - auto t0 = std::chrono::system_clock::now(); - int err = verify_file(const_cast<unsigned char*>(package_data), package_size, loadedKeys); - std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0; - ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); - if (err != VERIFY_SUCCESS) { - LOGE("Signature verification failed\n"); - LOGE("error: %d\n", kZipVerificationFailure); - return false; - } - return true; + std::vector<Certificate> loadedKeys; + if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) { + LOG(ERROR) << "Failed to load keys"; + return false; + } + LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE; + + // Verify package. + ui->Print("Verifying update package...\n"); + auto t0 = std::chrono::system_clock::now(); + int err = verify_file(package_data, package_size, loadedKeys, + std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); + std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0; + ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err); + if (err != VERIFY_SUCCESS) { + LOG(ERROR) << "Signature verification failed"; + LOG(ERROR) << "error: " << kZipVerificationFailure; + return false; + } + return true; } @@ -18,12 +18,11 @@ #define RECOVERY_INSTALL_H_ #include <string> - -#include "common.h" -#include "minzip/Zip.h" +#include <ziparchive/zip_archive.h> enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED, INSTALL_RETRY }; + // Install the package specified by root_path. If INSTALL_SUCCESS is // returned and *wipe_cache is true on exit, caller should wipe the // cache partition. @@ -36,6 +35,10 @@ bool verify_package(const unsigned char* package_data, size_t package_size); // Read meta data file of the package, write its content in the string pointed by meta_data. // Return true if succeed, otherwise return false. -bool read_metadata_from_package(ZipArchive* zip, std::string* meta_data); +bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data); + +// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the +// entry doesn't exist. +bool verify_package_compatibility(ZipArchiveHandle package_zip); #endif // RECOVERY_INSTALL_H_ diff --git a/minadbd/Android.mk b/minadbd/Android.mk index 3db3b4114..7eef13ee0 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -11,9 +11,9 @@ minadbd_cflags := \ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - adb_main.cpp \ fuse_adb_provider.cpp \ - services.cpp \ + minadbd.cpp \ + minadbd_services.cpp \ LOCAL_CLANG := true LOCAL_MODULE := libminadbd @@ -21,7 +21,7 @@ LOCAL_CFLAGS := $(minadbd_cflags) LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration LOCAL_C_INCLUDES := bootable/recovery system/core/adb LOCAL_WHOLE_STATIC_LIBRARIES := libadbd -LOCAL_STATIC_LIBRARIES := libbase +LOCAL_STATIC_LIBRARIES := libcrypto libbase include $(BUILD_STATIC_LIBRARY) diff --git a/minadbd/README.txt b/minadbd/README.md index e69dc87c6..5a0a067de 100644 --- a/minadbd/README.txt +++ b/minadbd/README.md @@ -3,6 +3,6 @@ minadbd, and services.c has been modified as follows: - all services removed - all host mode support removed - - sideload_service() added; this is the only service supported. It + - `sideload_service()` added; this is the only service supported. It receives a single blob of data, writes it to a fixed filename, and makes the process exit. diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp index d71807dfb..0f4c2563d 100644 --- a/minadbd/fuse_adb_provider.cpp +++ b/minadbd/fuse_adb_provider.cpp @@ -19,8 +19,6 @@ #include <string.h> #include <errno.h> -#include "sysdeps.h" - #include "adb.h" #include "adb_io.h" #include "fuse_adb_provider.h" diff --git a/minadbd/adb_main.cpp b/minadbd/minadbd.cpp index 0694280cb..349189cc7 100644 --- a/minadbd/adb_main.cpp +++ b/minadbd/minadbd.cpp @@ -14,18 +14,18 @@ * limitations under the License. */ +#include "minadbd.h" + #include <errno.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> -#include "sysdeps.h" - #include "adb.h" #include "adb_auth.h" #include "transport.h" -int adb_server_main(int is_daemon, int server_port, int /* reply_fd */) { +int minadbd_main() { adb_device_banner = "sideload"; signal(SIGPIPE, SIG_IGN); diff --git a/minzip/inline_magic.h b/minadbd/minadbd.h index 59c659f77..3570a5da5 100644 --- a/minzip/inline_magic.h +++ b/minadbd/minadbd.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,9 @@ * limitations under the License. */ -#ifndef MINZIP_INLINE_MAGIC_H_ -#define MINZIP_INLINE_MAGIC_H_ +#ifndef MINADBD_H__ +#define MINADBD_H__ -#ifndef MINZIP_GENERATE_INLINES -#define INLINE extern inline __attribute((__gnu_inline__)) -#else -#define INLINE -#endif +int minadbd_main(); -#endif // MINZIP_INLINE_MAGIC_H_ +#endif diff --git a/minadbd/services.cpp b/minadbd/minadbd_services.cpp index 658a43f36..426d982eb 100644 --- a/minadbd/services.cpp +++ b/minadbd/minadbd_services.cpp @@ -21,11 +21,10 @@ #include <string.h> #include <unistd.h> -#include "sysdeps.h" - #include "adb.h" #include "fdevent.h" #include "fuse_adb_provider.h" +#include "sysdeps.h" typedef struct stinfo stinfo; @@ -62,12 +61,12 @@ static void sideload_host_service(int sfd, void* data) { static int create_service_thread(void (*func)(int, void *), void *cookie) { int s[2]; - if(adb_socketpair(s)) { + if (adb_socketpair(s)) { printf("cannot create service socket pair\n"); return -1; } - stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo))); + stinfo* sti = static_cast<stinfo*>(malloc(sizeof(stinfo))); if(sti == 0) fatal("cannot allocate stinfo"); sti->func = func; sti->cookie = cookie; diff --git a/minui/Android.mk b/minui/Android.mk index 3057f452c..4dfc65f8a 100644 --- a/minui/Android.mk +++ b/minui/Android.mk @@ -1,3 +1,17 @@ +# Copyright (C) 2007 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. + LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) @@ -9,13 +23,20 @@ LOCAL_SRC_FILES := \ graphics_fbdev.cpp \ resources.cpp \ -LOCAL_WHOLE_STATIC_LIBRARIES += libadf -LOCAL_WHOLE_STATIC_LIBRARIES += libdrm -LOCAL_STATIC_LIBRARIES += libpng +LOCAL_WHOLE_STATIC_LIBRARIES := \ + libadf \ + libdrm \ + libsync_recovery -LOCAL_MODULE := libminui +LOCAL_STATIC_LIBRARIES := \ + libpng \ + libbase -LOCAL_CLANG := true +LOCAL_CFLAGS := -Werror +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include + +LOCAL_MODULE := libminui # This used to compare against values in double-quotes (which are just # ordinary characters in this context). Strip double-quotes from the @@ -41,8 +62,13 @@ include $(BUILD_STATIC_LIBRARY) # Used by OEMs for factory test images. include $(CLEAR_VARS) -LOCAL_CLANG := true LOCAL_MODULE := libminui LOCAL_WHOLE_STATIC_LIBRARIES += libminui -LOCAL_SHARED_LIBRARIES := libpng +LOCAL_SHARED_LIBRARIES := \ + libpng \ + libbase + +LOCAL_CFLAGS := -Werror +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_SHARED_LIBRARY) diff --git a/minui/events.cpp b/minui/events.cpp index 3b2262a4b..0e1fd44a0 100644 --- a/minui/events.cpp +++ b/minui/events.cpp @@ -15,17 +15,18 @@ */ #include <dirent.h> -#include <errno.h> #include <fcntl.h> +#include <linux/input.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> +#include <sys/ioctl.h> #include <unistd.h> -#include <linux/input.h> +#include <functional> -#include "minui.h" +#include "minui/minui.h" #define MAX_DEVICES 16 #define MAX_MISC_FDS 16 @@ -34,9 +35,8 @@ #define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG) struct fd_info { - int fd; - ev_callback cb; - void* data; + int fd; + ev_callback cb; }; static int g_epoll_fd; @@ -49,92 +49,91 @@ static unsigned ev_count = 0; static unsigned ev_dev_count = 0; static unsigned ev_misc_count = 0; -static bool test_bit(size_t bit, unsigned long* array) { +static bool test_bit(size_t bit, unsigned long* array) { // NOLINT return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0; } -int ev_init(ev_callback input_cb, void* data) { - bool epollctlfail = false; +int ev_init(ev_callback input_cb) { + bool epollctlfail = false; - g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); - if (g_epoll_fd == -1) { - return -1; + g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS); + if (g_epoll_fd == -1) { + return -1; + } + + DIR* dir = opendir("/dev/input"); + if (dir != NULL) { + dirent* de; + while ((de = readdir(dir))) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + + // fprintf(stderr,"/dev/input/%s\n", de->d_name); + if (strncmp(de->d_name, "event", 5)) continue; + int fd = openat(dirfd(dir), de->d_name, O_RDONLY); + if (fd == -1) continue; + + // Read the evbits of the input device. + if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + close(fd); + continue; + } + + // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. + if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { + close(fd); + continue; + } + + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = &ev_fdinfo[ev_count]; + if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + close(fd); + epollctlfail = true; + continue; + } + + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = std::move(input_cb); + ev_count++; + ev_dev_count++; + if (ev_dev_count == MAX_DEVICES) break; } - DIR* dir = opendir("/dev/input"); - if (dir != NULL) { - dirent* de; - while ((de = readdir(dir))) { - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; - -// fprintf(stderr,"/dev/input/%s\n", de->d_name); - if (strncmp(de->d_name, "event", 5)) continue; - int fd = openat(dirfd(dir), de->d_name, O_RDONLY); - if (fd == -1) continue; - - // Read the evbits of the input device. - if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { - close(fd); - continue; - } - - // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. - if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) { - close(fd); - continue; - } - - epoll_event ev; - ev.events = EPOLLIN | EPOLLWAKEUP; - ev.data.ptr = &ev_fdinfo[ev_count]; - if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { - close(fd); - epollctlfail = true; - continue; - } - - ev_fdinfo[ev_count].fd = fd; - ev_fdinfo[ev_count].cb = input_cb; - ev_fdinfo[ev_count].data = data; - ev_count++; - ev_dev_count++; - if (ev_dev_count == MAX_DEVICES) break; - } + closedir(dir); + } - closedir(dir); - } - - if (epollctlfail && !ev_count) { - close(g_epoll_fd); - g_epoll_fd = -1; - return -1; - } + if (epollctlfail && !ev_count) { + close(g_epoll_fd); + g_epoll_fd = -1; + return -1; + } - return 0; + return 0; } int ev_get_epollfd(void) { return g_epoll_fd; } -int ev_add_fd(int fd, ev_callback cb, void* data) { - if (ev_misc_count == MAX_MISC_FDS || cb == NULL) { - return -1; - } - - epoll_event ev; - ev.events = EPOLLIN | EPOLLWAKEUP; - ev.data.ptr = (void *)&ev_fdinfo[ev_count]; - int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev); - if (!ret) { - ev_fdinfo[ev_count].fd = fd; - ev_fdinfo[ev_count].cb = cb; - ev_fdinfo[ev_count].data = data; - ev_count++; - ev_misc_count++; - } - - return ret; +int ev_add_fd(int fd, ev_callback cb) { + if (ev_misc_count == MAX_MISC_FDS || cb == NULL) { + return -1; + } + + epoll_event ev; + ev.events = EPOLLIN | EPOLLWAKEUP; + ev.data.ptr = static_cast<void*>(&ev_fdinfo[ev_count]); + int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (!ret) { + ev_fdinfo[ev_count].fd = fd; + ev_fdinfo[ev_count].cb = std::move(cb); + ev_count++; + ev_misc_count++; + } + + return ret; } void ev_exit(void) { @@ -155,13 +154,13 @@ int ev_wait(int timeout) { } void ev_dispatch(void) { - for (int n = 0; n < npolledevents; n++) { - fd_info* fdi = reinterpret_cast<fd_info*>(polledevents[n].data.ptr); - ev_callback cb = fdi->cb; - if (cb) { - cb(fdi->fd, polledevents[n].events, fdi->data); - } + for (int n = 0; n < npolledevents; n++) { + fd_info* fdi = static_cast<fd_info*>(polledevents[n].data.ptr); + const ev_callback& cb = fdi->cb; + if (cb) { + cb(fdi->fd, polledevents[n].events); } + } } int ev_get_input(int fd, uint32_t epevents, input_event* ev) { @@ -174,37 +173,39 @@ int ev_get_input(int fd, uint32_t epevents, input_event* ev) { return -1; } -int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data) { - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; - unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; +int ev_sync_key_state(const ev_set_key_callback& set_key_cb) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT - for (size_t i = 0; i < ev_dev_count; ++i) { - memset(ev_bits, 0, sizeof(ev_bits)); - memset(key_bits, 0, sizeof(key_bits)); + for (size_t i = 0; i < ev_dev_count; ++i) { + memset(ev_bits, 0, sizeof(ev_bits)); + memset(key_bits, 0, sizeof(key_bits)); - if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { - continue; - } - if (!test_bit(EV_KEY, ev_bits)) { - continue; - } - if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) { - continue; - } + if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) { + continue; + } + if (!test_bit(EV_KEY, ev_bits)) { + continue; + } + if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) { + continue; + } - for (int code = 0; code <= KEY_MAX; code++) { - if (test_bit(code, key_bits)) { - set_key_cb(code, 1, data); - } - } + for (int code = 0; code <= KEY_MAX; code++) { + if (test_bit(code, key_bits)) { + set_key_cb(code, 1); + } } + } - return 0; + return 0; } -void ev_iterate_available_keys(std::function<void(int)> f) { - unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; - unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; +void ev_iterate_available_keys(const std::function<void(int)>& f) { + // Use unsigned long to match ioctl's parameter type. + unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT + unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT for (size_t i = 0; i < ev_dev_count; ++i) { memset(ev_bits, 0, sizeof(ev_bits)); diff --git a/minui/graphics.cpp b/minui/graphics.cpp index ab56a6fd6..3bfce11d8 100644 --- a/minui/graphics.cpp +++ b/minui/graphics.cpp @@ -14,29 +14,22 @@ * limitations under the License. */ -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> +#include "graphics.h" -#include <fcntl.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/types.h> - -#include <linux/fb.h> -#include <linux/kd.h> - -#include <time.h> +#include <memory> #include "font_10x18.h" -#include "minui.h" -#include "graphics.h" +#include "graphics_adf.h" +#include "graphics_drm.h" +#include "graphics_fbdev.h" +#include "minui/minui.h" static GRFont* gr_font = NULL; -static minui_backend* gr_backend = NULL; +static MinuiBackend* gr_backend = nullptr; static int overscan_percent = OVERSCAN_PERCENT; static int overscan_offset_x = 0; @@ -265,7 +258,7 @@ unsigned int gr_get_height(GRSurface* surface) { } int gr_init_font(const char* name, GRFont** dest) { - GRFont* font = reinterpret_cast<GRFont*>(calloc(1, sizeof(*gr_font))); + GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font))); if (font == nullptr) { return -1; } @@ -298,15 +291,15 @@ static void gr_init_font(void) // fall back to the compiled-in font. - gr_font = reinterpret_cast<GRFont*>(calloc(1, sizeof(*gr_font))); - gr_font->texture = reinterpret_cast<GRSurface*>(malloc(sizeof(*gr_font->texture))); + gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font))); + gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture))); gr_font->texture->width = font.width; gr_font->texture->height = font.height; gr_font->texture->row_bytes = font.width; gr_font->texture->pixel_bytes = 1; - unsigned char* bits = reinterpret_cast<unsigned char*>(malloc(font.width * font.height)); - gr_font->texture->data = reinterpret_cast<unsigned char*>(bits); + unsigned char* bits = static_cast<unsigned char*>(malloc(font.width * font.height)); + gr_font->texture->data = bits; unsigned char data; unsigned char* in = font.rundata; @@ -319,109 +312,53 @@ static void gr_init_font(void) gr_font->char_height = font.char_height; } -#if 0 -// Exercises many of the gr_*() functions; useful for testing. -static void gr_test() { - GRSurface** images; - int frames; - int result = res_create_multi_surface("icon_installing", &frames, &images); - if (result < 0) { - printf("create surface %d\n", result); - gr_exit(); - return; - } - - time_t start = time(NULL); - int x; - for (x = 0; x <= 1200; ++x) { - if (x < 400) { - gr_color(0, 0, 0, 255); - } else { - gr_color(0, (x-400)%128, 0, 255); - } - gr_clear(); - - gr_color(255, 0, 0, 255); - GRSurface* frame = images[x%frames]; - gr_blit(frame, 0, 0, frame->width, frame->height, x, 0); - - gr_color(255, 0, 0, 128); - gr_fill(400, 150, 600, 350); - - gr_color(255, 255, 255, 255); - gr_text(500, 225, "hello, world!", 0); - gr_color(255, 255, 0, 128); - gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1); - - gr_color(0, 0, 255, 128); - gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500); - - gr_draw = gr_backend->flip(gr_backend); - } - printf("getting end time\n"); - time_t end = time(NULL); - printf("got end time\n"); - printf("start %ld end %ld\n", (long)start, (long)end); - if (end > start) { - printf("%.2f fps\n", ((double)x) / (end-start)); - } -} -#endif - void gr_flip() { - gr_draw = gr_backend->flip(gr_backend); + gr_draw = gr_backend->Flip(); } -int gr_init(void) -{ - gr_init_font(); +int gr_init() { + gr_init_font(); - gr_backend = open_adf(); - if (gr_backend) { - gr_draw = gr_backend->init(gr_backend); - if (!gr_draw) { - gr_backend->exit(gr_backend); - } - } + auto backend = std::unique_ptr<MinuiBackend>{ std::make_unique<MinuiBackendAdf>() }; + gr_draw = backend->Init(); - if (!gr_draw) { - gr_backend = open_drm(); - gr_draw = gr_backend->init(gr_backend); - } + if (!gr_draw) { + backend = std::make_unique<MinuiBackendDrm>(); + gr_draw = backend->Init(); + } - if (!gr_draw) { - gr_backend = open_fbdev(); - gr_draw = gr_backend->init(gr_backend); - if (gr_draw == NULL) { - return -1; - } - } + if (!gr_draw) { + backend = std::make_unique<MinuiBackendFbdev>(); + gr_draw = backend->Init(); + } - overscan_offset_x = gr_draw->width * overscan_percent / 100; - overscan_offset_y = gr_draw->height * overscan_percent / 100; + if (!gr_draw) { + return -1; + } - gr_flip(); - gr_flip(); + gr_backend = backend.release(); - return 0; + overscan_offset_x = gr_draw->width * overscan_percent / 100; + overscan_offset_y = gr_draw->height * overscan_percent / 100; + + gr_flip(); + gr_flip(); + + return 0; } -void gr_exit(void) -{ - gr_backend->exit(gr_backend); +void gr_exit() { + delete gr_backend; } -int gr_fb_width(void) -{ - return gr_draw->width - 2*overscan_offset_x; +int gr_fb_width() { + return gr_draw->width - 2 * overscan_offset_x; } -int gr_fb_height(void) -{ - return gr_draw->height - 2*overscan_offset_y; +int gr_fb_height() { + return gr_draw->height - 2 * overscan_offset_y; } -void gr_fb_blank(bool blank) -{ - gr_backend->blank(gr_backend, blank); +void gr_fb_blank(bool blank) { + gr_backend->Blank(blank); } diff --git a/minui/graphics.h b/minui/graphics.h index 52968eb10..3c45a406b 100644 --- a/minui/graphics.h +++ b/minui/graphics.h @@ -17,27 +17,22 @@ #ifndef _GRAPHICS_H_ #define _GRAPHICS_H_ -#include "minui.h" +#include "minui/minui.h" -// TODO: lose the function pointers. -struct minui_backend { - // Initializes the backend and returns a GRSurface* to draw into. - GRSurface* (*init)(minui_backend*); +class MinuiBackend { + public: + // Initializes the backend and returns a GRSurface* to draw into. + virtual GRSurface* Init() = 0; - // Causes the current drawing surface (returned by the most recent - // call to flip() or init()) to be displayed, and returns a new - // drawing surface. - GRSurface* (*flip)(minui_backend*); + // Causes the current drawing surface (returned by the most recent call to Flip() or Init()) to + // be displayed, and returns a new drawing surface. + virtual GRSurface* Flip() = 0; - // Blank (or unblank) the screen. - void (*blank)(minui_backend*, bool); + // Blank (or unblank) the screen. + virtual void Blank(bool) = 0; - // Device cleanup when drawing is done. - void (*exit)(minui_backend*); + // Device cleanup when drawing is done. + virtual ~MinuiBackend() {}; }; -minui_backend* open_fbdev(); -minui_backend* open_adf(); -minui_backend* open_drm(); - -#endif +#endif // _GRAPHICS_H_ diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp index 5d0867f58..1b15a04fb 100644 --- a/minui/graphics_adf.cpp +++ b/minui/graphics_adf.cpp @@ -14,236 +14,184 @@ * limitations under the License. */ +#include "graphics_adf.h" + #include <errno.h> #include <fcntl.h> -#include <stdbool.h> #include <stdio.h> #include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <sys/cdefs.h> #include <sys/mman.h> +#include <unistd.h> #include <adf/adf.h> +#include <sync/sync.h> + +#include "minui/minui.h" + +MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), n_surfaces(0), surfaces() {} + +int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) { + *surf = {}; + surf->fence_fd = -1; + surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format, + &surf->offset, &surf->pitch); + if (surf->fd < 0) { + return surf->fd; + } + + surf->width = mode->hdisplay; + surf->height = mode->vdisplay; + surf->row_bytes = surf->pitch; + surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4; + + surf->data = static_cast<uint8_t*>( + mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset)); + if (surf->data == MAP_FAILED) { + int saved_errno = errno; + close(surf->fd); + return -saved_errno; + } -#include "graphics.h" - -struct adf_surface_pdata { - GRSurface base; - int fd; - __u32 offset; - __u32 pitch; -}; - -struct adf_pdata { - minui_backend base; - int intf_fd; - adf_id_t eng_id; - __u32 format; - - unsigned int current_surface; - unsigned int n_surfaces; - adf_surface_pdata surfaces[2]; -}; - -static GRSurface* adf_flip(minui_backend *backend); -static void adf_blank(minui_backend *backend, bool blank); - -static int adf_surface_init(adf_pdata *pdata, drm_mode_modeinfo *mode, adf_surface_pdata *surf) { - memset(surf, 0, sizeof(*surf)); - - surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay, - mode->vdisplay, pdata->format, &surf->offset, &surf->pitch); - if (surf->fd < 0) - return surf->fd; - - surf->base.width = mode->hdisplay; - surf->base.height = mode->vdisplay; - surf->base.row_bytes = surf->pitch; - surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4; - - surf->base.data = reinterpret_cast<uint8_t*>(mmap(NULL, - surf->pitch * surf->base.height, PROT_WRITE, - MAP_SHARED, surf->fd, surf->offset)); - if (surf->base.data == MAP_FAILED) { - close(surf->fd); - return -errno; - } - - return 0; + return 0; } -static int adf_interface_init(adf_pdata *pdata) -{ - adf_interface_data intf_data; - int ret = 0; - int err; - - err = adf_get_interface_data(pdata->intf_fd, &intf_data); - if (err < 0) - return err; - - err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]); - if (err < 0) { - fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err)); - ret = err; - goto done; - } - - err = adf_surface_init(pdata, &intf_data.current_mode, - &pdata->surfaces[1]); - if (err < 0) { - fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err)); - memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1])); - pdata->n_surfaces = 1; - } else { - pdata->n_surfaces = 2; - } +int MinuiBackendAdf::InterfaceInit() { + adf_interface_data intf_data; + int err = adf_get_interface_data(intf_fd, &intf_data); + if (err < 0) return err; + + int ret = 0; + err = SurfaceInit(&intf_data.current_mode, &surfaces[0]); + if (err < 0) { + fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err)); + ret = err; + goto done; + } + + err = SurfaceInit(&intf_data.current_mode, &surfaces[1]); + if (err < 0) { + fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err)); + surfaces[1] = {}; + n_surfaces = 1; + } else { + n_surfaces = 2; + } done: - adf_free_interface_data(&intf_data); - return ret; + adf_free_interface_data(&intf_data); + return ret; } -static int adf_device_init(adf_pdata *pdata, adf_device *dev) -{ - adf_id_t intf_id; - int intf_fd; - int err; - - err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id, - &pdata->eng_id); - if (err < 0) - return err; +int MinuiBackendAdf::DeviceInit(adf_device* dev) { + adf_id_t intf_id; + int err = adf_find_simple_post_configuration(dev, &format, 1, &intf_id, &eng_id); + if (err < 0) return err; - err = adf_device_attach(dev, pdata->eng_id, intf_id); - if (err < 0 && err != -EALREADY) - return err; + err = adf_device_attach(dev, eng_id, intf_id); + if (err < 0 && err != -EALREADY) return err; - pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR); - if (pdata->intf_fd < 0) - return pdata->intf_fd; + intf_fd = adf_interface_open(dev, intf_id, O_RDWR); + if (intf_fd < 0) return intf_fd; - err = adf_interface_init(pdata); - if (err < 0) { - close(pdata->intf_fd); - pdata->intf_fd = -1; - } + err = InterfaceInit(); + if (err < 0) { + close(intf_fd); + intf_fd = -1; + } - return err; + return err; } -static GRSurface* adf_init(minui_backend *backend) -{ - adf_pdata *pdata = (adf_pdata *)backend; - adf_id_t *dev_ids = NULL; - ssize_t n_dev_ids, i; - GRSurface* ret; - +GRSurface* MinuiBackendAdf::Init() { #if defined(RECOVERY_ABGR) - pdata->format = DRM_FORMAT_ABGR8888; + format = DRM_FORMAT_ABGR8888; #elif defined(RECOVERY_BGRA) - pdata->format = DRM_FORMAT_BGRA8888; + format = DRM_FORMAT_BGRA8888; #elif defined(RECOVERY_RGBX) - pdata->format = DRM_FORMAT_RGBX8888; + format = DRM_FORMAT_RGBX8888; #else - pdata->format = DRM_FORMAT_RGB565; + format = DRM_FORMAT_RGB565; #endif - n_dev_ids = adf_devices(&dev_ids); - if (n_dev_ids == 0) { - return NULL; - } else if (n_dev_ids < 0) { - fprintf(stderr, "enumerating adf devices failed: %s\n", - strerror(-n_dev_ids)); - return NULL; - } - - pdata->intf_fd = -1; - - for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) { - adf_device dev; + adf_id_t* dev_ids = nullptr; + ssize_t n_dev_ids = adf_devices(&dev_ids); + if (n_dev_ids == 0) { + return nullptr; + } else if (n_dev_ids < 0) { + fprintf(stderr, "enumerating adf devices failed: %s\n", strerror(-n_dev_ids)); + return nullptr; + } - int err = adf_device_open(dev_ids[i], O_RDWR, &dev); - if (err < 0) { - fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i], - strerror(-err)); - continue; - } + intf_fd = -1; - err = adf_device_init(pdata, &dev); - if (err < 0) - fprintf(stderr, "initializing adf device %u failed: %s\n", - dev_ids[i], strerror(-err)); + for (ssize_t i = 0; i < n_dev_ids && intf_fd < 0; i++) { + int err = adf_device_open(dev_ids[i], O_RDWR, &dev); + if (err < 0) { + fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i], strerror(-err)); + continue; + } - adf_device_close(&dev); + err = DeviceInit(&dev); + if (err < 0) { + fprintf(stderr, "initializing adf device %u failed: %s\n", dev_ids[i], strerror(-err)); + adf_device_close(&dev); } + } - free(dev_ids); + free(dev_ids); - if (pdata->intf_fd < 0) - return NULL; + if (intf_fd < 0) return nullptr; - ret = adf_flip(backend); + GRSurface* ret = Flip(); - adf_blank(backend, true); - adf_blank(backend, false); + Blank(true); + Blank(false); - return ret; + return ret; } -static GRSurface* adf_flip(minui_backend *backend) -{ - adf_pdata *pdata = (adf_pdata *)backend; - adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface]; +void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) { + static constexpr unsigned int warningTimeout = 3000; - int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id, - surf->base.width, surf->base.height, pdata->format, surf->fd, - surf->offset, surf->pitch, -1); - if (fence_fd >= 0) - close(fence_fd); + if (surf == nullptr) return; - pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces; - return &pdata->surfaces[pdata->current_surface].base; -} + if (surf->fence_fd >= 0) { + int err = sync_wait(surf->fence_fd, warningTimeout); + if (err < 0) { + perror("adf sync fence wait error\n"); + } -static void adf_blank(minui_backend *backend, bool blank) -{ - adf_pdata *pdata = (adf_pdata *)backend; - adf_interface_blank(pdata->intf_fd, - blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); + close(surf->fence_fd); + surf->fence_fd = -1; + } } -static void adf_surface_destroy(adf_surface_pdata *surf) -{ - munmap(surf->base.data, surf->pitch * surf->base.height); - close(surf->fd); -} +GRSurface* MinuiBackendAdf::Flip() { + GRSurfaceAdf* surf = &surfaces[current_surface]; -static void adf_exit(minui_backend *backend) -{ - adf_pdata *pdata = (adf_pdata *)backend; - unsigned int i; + int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format, + surf->fd, surf->offset, surf->pitch, -1); + if (fence_fd >= 0) surf->fence_fd = fence_fd; - for (i = 0; i < pdata->n_surfaces; i++) - adf_surface_destroy(&pdata->surfaces[i]); - if (pdata->intf_fd >= 0) - close(pdata->intf_fd); - free(pdata); + current_surface = (current_surface + 1) % n_surfaces; + Sync(&surfaces[current_surface]); + return &surfaces[current_surface]; } -minui_backend *open_adf() -{ - adf_pdata* pdata = reinterpret_cast<adf_pdata*>(calloc(1, sizeof(*pdata))); - if (!pdata) { - perror("allocating adf backend failed"); - return NULL; - } +void MinuiBackendAdf::Blank(bool blank) { + adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); +} + +void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) { + munmap(surf->data, surf->pitch * surf->height); + close(surf->fence_fd); + close(surf->fd); +} - pdata->base.init = adf_init; - pdata->base.flip = adf_flip; - pdata->base.blank = adf_blank; - pdata->base.exit = adf_exit; - return &pdata->base; +MinuiBackendAdf::~MinuiBackendAdf() { + adf_device_close(&dev); + for (unsigned int i = 0; i < n_surfaces; i++) { + SurfaceDestroy(&surfaces[i]); + } + if (intf_fd >= 0) close(intf_fd); } diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h new file mode 100644 index 000000000..2f019ed0b --- /dev/null +++ b/minui/graphics_adf.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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 _GRAPHICS_ADF_H_ +#define _GRAPHICS_ADF_H_ + +#include <adf/adf.h> + +#include "graphics.h" + +class GRSurfaceAdf : public GRSurface { + private: + int fence_fd; + int fd; + __u32 offset; + __u32 pitch; + + friend class MinuiBackendAdf; +}; + +class MinuiBackendAdf : public MinuiBackend { + public: + GRSurface* Init() override; + GRSurface* Flip() override; + void Blank(bool) override; + ~MinuiBackendAdf() override; + MinuiBackendAdf(); + + private: + int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf); + int InterfaceInit(); + int DeviceInit(adf_device* dev); + void SurfaceDestroy(GRSurfaceAdf* surf); + void Sync(GRSurfaceAdf* surf); + + int intf_fd; + adf_id_t eng_id; + __u32 format; + adf_device dev; + unsigned int current_surface; + unsigned int n_surfaces; + GRSurfaceAdf surfaces[2]; +}; + +#endif // _GRAPHICS_ADF_H_ diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp index 03e33b775..e7d4b38ef 100644 --- a/minui/graphics_drm.cpp +++ b/minui/graphics_drm.cpp @@ -14,463 +14,382 @@ * limitations under the License. */ -#include <drm_fourcc.h> +#include "graphics_drm.h" + #include <fcntl.h> -#include <stdbool.h> #include <stdio.h> #include <stdlib.h> -#include <string.h> -#include <sys/cdefs.h> -#include <sys/ioctl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> + +#include <drm_fourcc.h> #include <xf86drm.h> #include <xf86drmMode.h> -#include "minui.h" -#include "graphics.h" +#include "minui/minui.h" #define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A))) -struct drm_surface { - GRSurface base; - uint32_t fb_id; - uint32_t handle; -}; - -static drm_surface *drm_surfaces[2]; -static int current_buffer; - -static drmModeCrtc *main_monitor_crtc; -static drmModeConnector *main_monitor_connector; - -static int drm_fd = -1; - -static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) { - if (crtc) { - drmModeSetCrtc(drm_fd, crtc->crtc_id, - 0, // fb_id - 0, 0, // x,y - NULL, // connectors - 0, // connector_count - NULL); // mode - } +MinuiBackendDrm::MinuiBackendDrm() + : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {} + +void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) { + if (crtc) { + drmModeSetCrtc(drm_fd, crtc->crtc_id, + 0, // fb_id + 0, 0, // x,y + nullptr, // connectors + 0, // connector_count + nullptr); // mode + } } -static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc, - struct drm_surface *surface) { - int32_t ret; +void MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) { + int32_t ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y + &main_monitor_connector->connector_id, + 1, // connector_count + &main_monitor_crtc->mode); - ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, - surface->fb_id, - 0, 0, // x,y - &main_monitor_connector->connector_id, - 1, // connector_count - &main_monitor_crtc->mode); - - if (ret) - printf("drmModeSetCrtc failed ret=%d\n", ret); + if (ret) { + printf("drmModeSetCrtc failed ret=%d\n", ret); + } } -static void drm_blank(minui_backend* backend __unused, bool blank) { - if (blank) - drm_disable_crtc(drm_fd, main_monitor_crtc); - else - drm_enable_crtc(drm_fd, main_monitor_crtc, - drm_surfaces[current_buffer]); +void MinuiBackendDrm::Blank(bool blank) { + if (blank) { + DrmDisableCrtc(drm_fd, main_monitor_crtc); + } else { + DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]); + } } -static void drm_destroy_surface(struct drm_surface *surface) { - struct drm_gem_close gem_close; - int ret; - - if(!surface) - return; +void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) { + if (!surface) return; - if (surface->base.data) - munmap(surface->base.data, - surface->base.row_bytes * surface->base.height); + if (surface->data) { + munmap(surface->data, surface->row_bytes * surface->height); + } - if (surface->fb_id) { - ret = drmModeRmFB(drm_fd, surface->fb_id); - if (ret) - printf("drmModeRmFB failed ret=%d\n", ret); + if (surface->fb_id) { + int ret = drmModeRmFB(drm_fd, surface->fb_id); + if (ret) { + printf("drmModeRmFB failed ret=%d\n", ret); } + } - if (surface->handle) { - memset(&gem_close, 0, sizeof(gem_close)); - gem_close.handle = surface->handle; + if (surface->handle) { + drm_gem_close gem_close = {}; + gem_close.handle = surface->handle; - ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); - if (ret) - printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret); + int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); + if (ret) { + printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret); } + } - free(surface); + delete surface; } static int drm_format_to_bpp(uint32_t format) { - switch(format) { - case DRM_FORMAT_ABGR8888: - case DRM_FORMAT_BGRA8888: - case DRM_FORMAT_RGBX8888: - case DRM_FORMAT_BGRX8888: - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_XRGB8888: - return 32; - case DRM_FORMAT_RGB565: - return 16; - default: - printf("Unknown format %d\n", format); - return 32; - } + switch (format) { + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_XRGB8888: + return 32; + case DRM_FORMAT_RGB565: + return 16; + default: + printf("Unknown format %d\n", format); + return 32; + } } -static drm_surface *drm_create_surface(int width, int height) { - struct drm_surface *surface; - struct drm_mode_create_dumb create_dumb; - uint32_t format; - int ret; - - surface = (struct drm_surface*)calloc(1, sizeof(*surface)); - if (!surface) { - printf("Can't allocate memory\n"); - return NULL; - } +GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) { + GRSurfaceDrm* surface = new GRSurfaceDrm; + *surface = {}; + uint32_t format; #if defined(RECOVERY_ABGR) - format = DRM_FORMAT_RGBA8888; + format = DRM_FORMAT_RGBA8888; #elif defined(RECOVERY_BGRA) - format = DRM_FORMAT_ARGB8888; + format = DRM_FORMAT_ARGB8888; #elif defined(RECOVERY_RGBX) - format = DRM_FORMAT_XBGR8888; + format = DRM_FORMAT_XBGR8888; #else - format = DRM_FORMAT_RGB565; + format = DRM_FORMAT_RGB565; #endif - memset(&create_dumb, 0, sizeof(create_dumb)); - create_dumb.height = height; - create_dumb.width = width; - create_dumb.bpp = drm_format_to_bpp(format); - create_dumb.flags = 0; - - ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); - if (ret) { - printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret); - drm_destroy_surface(surface); - return NULL; - } - surface->handle = create_dumb.handle; - - uint32_t handles[4], pitches[4], offsets[4]; - - handles[0] = surface->handle; - pitches[0] = create_dumb.pitch; - offsets[0] = 0; - - ret = drmModeAddFB2(drm_fd, width, height, - format, handles, pitches, offsets, - &(surface->fb_id), 0); - if (ret) { - printf("drmModeAddFB2 failed ret=%d\n", ret); - drm_destroy_surface(surface); - return NULL; - } - - struct drm_mode_map_dumb map_dumb; - memset(&map_dumb, 0, sizeof(map_dumb)); - map_dumb.handle = create_dumb.handle; - ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); - if (ret) { - printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret); - drm_destroy_surface(surface); - return NULL;; - } - - surface->base.height = height; - surface->base.width = width; - surface->base.row_bytes = create_dumb.pitch; - surface->base.pixel_bytes = create_dumb.bpp / 8; - surface->base.data = (unsigned char*) - mmap(NULL, - surface->base.height * surface->base.row_bytes, - PROT_READ | PROT_WRITE, MAP_SHARED, - drm_fd, map_dumb.offset); - if (surface->base.data == MAP_FAILED) { - perror("mmap() failed"); - drm_destroy_surface(surface); - return NULL; - } - - return surface; + drm_mode_create_dumb create_dumb = {}; + create_dumb.height = height; + create_dumb.width = width; + create_dumb.bpp = drm_format_to_bpp(format); + create_dumb.flags = 0; + + int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); + if (ret) { + printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret); + DrmDestroySurface(surface); + return nullptr; + } + surface->handle = create_dumb.handle; + + uint32_t handles[4], pitches[4], offsets[4]; + + handles[0] = surface->handle; + pitches[0] = create_dumb.pitch; + offsets[0] = 0; + + ret = + drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0); + if (ret) { + printf("drmModeAddFB2 failed ret=%d\n", ret); + DrmDestroySurface(surface); + return nullptr; + } + + drm_mode_map_dumb map_dumb = {}; + map_dumb.handle = create_dumb.handle; + ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); + if (ret) { + printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret); + DrmDestroySurface(surface); + return nullptr; + } + + surface->height = height; + surface->width = width; + surface->row_bytes = create_dumb.pitch; + surface->pixel_bytes = create_dumb.bpp / 8; + surface->data = static_cast<unsigned char*>(mmap(nullptr, surface->height * surface->row_bytes, + PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, + map_dumb.offset)); + if (surface->data == MAP_FAILED) { + perror("mmap() failed"); + DrmDestroySurface(surface); + return nullptr; + } + + return surface; } -static drmModeCrtc *find_crtc_for_connector(int fd, - drmModeRes *resources, - drmModeConnector *connector) { - int i, j; - drmModeEncoder *encoder; - int32_t crtc; - - /* - * Find the encoder. If we already have one, just use it. - */ - if (connector->encoder_id) - encoder = drmModeGetEncoder(fd, connector->encoder_id); - else - encoder = NULL; - - if (encoder && encoder->crtc_id) { - crtc = encoder->crtc_id; +static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources, + drmModeConnector* connector) { + // Find the encoder. If we already have one, just use it. + drmModeEncoder* encoder; + if (connector->encoder_id) { + encoder = drmModeGetEncoder(fd, connector->encoder_id); + } else { + encoder = nullptr; + } + + int32_t crtc; + if (encoder && encoder->crtc_id) { + crtc = encoder->crtc_id; + drmModeFreeEncoder(encoder); + return drmModeGetCrtc(fd, crtc); + } + + // Didn't find anything, try to find a crtc and encoder combo. + crtc = -1; + for (int i = 0; i < connector->count_encoders; i++) { + encoder = drmModeGetEncoder(fd, connector->encoders[i]); + + if (encoder) { + for (int j = 0; j < resources->count_crtcs; j++) { + if (!(encoder->possible_crtcs & (1 << j))) continue; + crtc = resources->crtcs[j]; + break; + } + if (crtc >= 0) { drmModeFreeEncoder(encoder); return drmModeGetCrtc(fd, crtc); + } } + } - /* - * Didn't find anything, try to find a crtc and encoder combo. - */ - crtc = -1; - for (i = 0; i < connector->count_encoders; i++) { - encoder = drmModeGetEncoder(fd, connector->encoders[i]); - - if (encoder) { - for (j = 0; j < resources->count_crtcs; j++) { - if (!(encoder->possible_crtcs & (1 << j))) - continue; - crtc = resources->crtcs[j]; - break; - } - if (crtc >= 0) { - drmModeFreeEncoder(encoder); - return drmModeGetCrtc(fd, crtc); - } - } - } - - return NULL; + return nullptr; } -static drmModeConnector *find_used_connector_by_type(int fd, - drmModeRes *resources, - unsigned type) { - int i; - for (i = 0; i < resources->count_connectors; i++) { - drmModeConnector *connector; - - connector = drmModeGetConnector(fd, resources->connectors[i]); - if (connector) { - if ((connector->connector_type == type) && - (connector->connection == DRM_MODE_CONNECTED) && - (connector->count_modes > 0)) - return connector; - - drmModeFreeConnector(connector); - } +static drmModeConnector* find_used_connector_by_type(int fd, drmModeRes* resources, unsigned type) { + for (int i = 0; i < resources->count_connectors; i++) { + drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]); + if (connector) { + if ((connector->connector_type == type) && (connector->connection == DRM_MODE_CONNECTED) && + (connector->count_modes > 0)) { + return connector; + } + drmModeFreeConnector(connector); } - return NULL; + } + return nullptr; } -static drmModeConnector *find_first_connected_connector(int fd, - drmModeRes *resources) { - int i; - for (i = 0; i < resources->count_connectors; i++) { - drmModeConnector *connector; +static drmModeConnector* find_first_connected_connector(int fd, drmModeRes* resources) { + for (int i = 0; i < resources->count_connectors; i++) { + drmModeConnector* connector; - connector = drmModeGetConnector(fd, resources->connectors[i]); - if (connector) { - if ((connector->count_modes > 0) && - (connector->connection == DRM_MODE_CONNECTED)) - return connector; + connector = drmModeGetConnector(fd, resources->connectors[i]); + if (connector) { + if ((connector->count_modes > 0) && (connector->connection == DRM_MODE_CONNECTED)) + return connector; - drmModeFreeConnector(connector); - } + drmModeFreeConnector(connector); } - return NULL; + } + return nullptr; } -static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources, - uint32_t *mode_index) { - unsigned i = 0; - int modes; - /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */ - unsigned kConnectorPriority[] = { - DRM_MODE_CONNECTOR_LVDS, - DRM_MODE_CONNECTOR_eDP, - DRM_MODE_CONNECTOR_DSI, - }; - - drmModeConnector *main_monitor_connector = NULL; - do { - main_monitor_connector = find_used_connector_by_type(fd, - resources, - kConnectorPriority[i]); - i++; - } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority)); - - /* If we didn't find a connector, grab the first one that is connected. */ - if (!main_monitor_connector) - main_monitor_connector = - find_first_connected_connector(fd, resources); - - /* If we still didn't find a connector, give up and return. */ - if (!main_monitor_connector) - return NULL; - - *mode_index = 0; - for (modes = 0; modes < main_monitor_connector->count_modes; modes++) { - if (main_monitor_connector->modes[modes].type & - DRM_MODE_TYPE_PREFERRED) { - *mode_index = modes; - break; - } +drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources, + uint32_t* mode_index) { + /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */ + static constexpr unsigned kConnectorPriority[] = { + DRM_MODE_CONNECTOR_LVDS, + DRM_MODE_CONNECTOR_eDP, + DRM_MODE_CONNECTOR_DSI, + }; + + drmModeConnector* main_monitor_connector = nullptr; + unsigned i = 0; + do { + main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]); + i++; + } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority)); + + /* If we didn't find a connector, grab the first one that is connected. */ + if (!main_monitor_connector) { + main_monitor_connector = find_first_connected_connector(fd, resources); + } + + /* If we still didn't find a connector, give up and return. */ + if (!main_monitor_connector) return nullptr; + + *mode_index = 0; + for (int modes = 0; modes < main_monitor_connector->count_modes; modes++) { + if (main_monitor_connector->modes[modes].type & DRM_MODE_TYPE_PREFERRED) { + *mode_index = modes; + break; } + } - return main_monitor_connector; + return main_monitor_connector; } -static void disable_non_main_crtcs(int fd, - drmModeRes *resources, - drmModeCrtc* main_crtc) { - int i; - drmModeCrtc* crtc; - - for (i = 0; i < resources->count_connectors; i++) { - drmModeConnector *connector; - - connector = drmModeGetConnector(fd, resources->connectors[i]); - crtc = find_crtc_for_connector(fd, resources, connector); - if (crtc->crtc_id != main_crtc->crtc_id) - drm_disable_crtc(fd, crtc); - drmModeFreeCrtc(crtc); +void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc) { + for (int i = 0; i < resources->count_connectors; i++) { + drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]); + drmModeCrtc* crtc = find_crtc_for_connector(fd, resources, connector); + if (crtc->crtc_id != main_crtc->crtc_id) { + DrmDisableCrtc(fd, crtc); } + drmModeFreeCrtc(crtc); + } } -static GRSurface* drm_init(minui_backend* backend __unused) { - drmModeRes *res = NULL; - uint32_t selected_mode; - char *dev_name; - int width, height; - int ret, i; - - /* Consider DRM devices in order. */ - for (i = 0; i < DRM_MAX_MINOR; i++) { - uint64_t cap = 0; - - ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i); - if (ret < 0) - continue; - - drm_fd = open(dev_name, O_RDWR, 0); - free(dev_name); - if (drm_fd < 0) - continue; - - /* We need dumb buffers. */ - ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap); - if (ret || cap == 0) { - close(drm_fd); - continue; - } - - res = drmModeGetResources(drm_fd); - if (!res) { - close(drm_fd); - continue; - } - - /* Use this device if it has at least one connected monitor. */ - if (res->count_crtcs > 0 && res->count_connectors > 0) - if (find_first_connected_connector(drm_fd, res)) - break; - - drmModeFreeResources(res); - close(drm_fd); - res = NULL; +GRSurface* MinuiBackendDrm::Init() { + drmModeRes* res = nullptr; + + /* Consider DRM devices in order. */ + for (int i = 0; i < DRM_MAX_MINOR; i++) { + char* dev_name; + int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i); + if (ret < 0) continue; + + drm_fd = open(dev_name, O_RDWR, 0); + free(dev_name); + if (drm_fd < 0) continue; + + uint64_t cap = 0; + /* We need dumb buffers. */ + ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ret || cap == 0) { + close(drm_fd); + continue; } - if (drm_fd < 0 || res == NULL) { - perror("cannot find/open a drm device"); - return NULL; + res = drmModeGetResources(drm_fd); + if (!res) { + close(drm_fd); + continue; } - main_monitor_connector = find_main_monitor(drm_fd, - res, &selected_mode); - - if (!main_monitor_connector) { - printf("main_monitor_connector not found\n"); - drmModeFreeResources(res); - close(drm_fd); - return NULL; + /* Use this device if it has at least one connected monitor. */ + if (res->count_crtcs > 0 && res->count_connectors > 0) { + if (find_first_connected_connector(drm_fd, res)) break; } - main_monitor_crtc = find_crtc_for_connector(drm_fd, res, - main_monitor_connector); + drmModeFreeResources(res); + close(drm_fd); + res = nullptr; + } - if (!main_monitor_crtc) { - printf("main_monitor_crtc not found\n"); - drmModeFreeResources(res); - close(drm_fd); - return NULL; - } + if (drm_fd < 0 || res == nullptr) { + perror("cannot find/open a drm device"); + return nullptr; + } - disable_non_main_crtcs(drm_fd, - res, main_monitor_crtc); + uint32_t selected_mode; + main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode); - main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode]; + if (!main_monitor_connector) { + printf("main_monitor_connector not found\n"); + drmModeFreeResources(res); + close(drm_fd); + return nullptr; + } - width = main_monitor_crtc->mode.hdisplay; - height = main_monitor_crtc->mode.vdisplay; + main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector); + if (!main_monitor_crtc) { + printf("main_monitor_crtc not found\n"); drmModeFreeResources(res); + close(drm_fd); + return nullptr; + } - drm_surfaces[0] = drm_create_surface(width, height); - drm_surfaces[1] = drm_create_surface(width, height); - if (!drm_surfaces[0] || !drm_surfaces[1]) { - drm_destroy_surface(drm_surfaces[0]); - drm_destroy_surface(drm_surfaces[1]); - drmModeFreeResources(res); - close(drm_fd); - return NULL; - } + DisableNonMainCrtcs(drm_fd, res, main_monitor_crtc); - current_buffer = 0; + main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode]; - drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]); + int width = main_monitor_crtc->mode.hdisplay; + int height = main_monitor_crtc->mode.vdisplay; - return &(drm_surfaces[0]->base); -} + drmModeFreeResources(res); -static GRSurface* drm_flip(minui_backend* backend __unused) { - int ret; + GRSurfaceDrms[0] = DrmCreateSurface(width, height); + GRSurfaceDrms[1] = DrmCreateSurface(width, height); + if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) { + // GRSurfaceDrms and drm_fd should be freed in d'tor. + return nullptr; + } - ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, - drm_surfaces[current_buffer]->fb_id, 0, NULL); - if (ret < 0) { - printf("drmModePageFlip failed ret=%d\n", ret); - return NULL; - } - current_buffer = 1 - current_buffer; - return &(drm_surfaces[current_buffer]->base); -} + current_buffer = 0; -static void drm_exit(minui_backend* backend __unused) { - drm_disable_crtc(drm_fd, main_monitor_crtc); - drm_destroy_surface(drm_surfaces[0]); - drm_destroy_surface(drm_surfaces[1]); - drmModeFreeCrtc(main_monitor_crtc); - drmModeFreeConnector(main_monitor_connector); - close(drm_fd); - drm_fd = -1; + DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]); + + return GRSurfaceDrms[0]; } -static minui_backend drm_backend = { - .init = drm_init, - .flip = drm_flip, - .blank = drm_blank, - .exit = drm_exit, -}; +GRSurface* MinuiBackendDrm::Flip() { + int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, + GRSurfaceDrms[current_buffer]->fb_id, 0, nullptr); + if (ret < 0) { + printf("drmModePageFlip failed ret=%d\n", ret); + return nullptr; + } + current_buffer = 1 - current_buffer; + return GRSurfaceDrms[current_buffer]; +} -minui_backend* open_drm() { - return &drm_backend; +MinuiBackendDrm::~MinuiBackendDrm() { + DrmDisableCrtc(drm_fd, main_monitor_crtc); + DrmDestroySurface(GRSurfaceDrms[0]); + DrmDestroySurface(GRSurfaceDrms[1]); + drmModeFreeCrtc(main_monitor_crtc); + drmModeFreeConnector(main_monitor_connector); + close(drm_fd); + drm_fd = -1; } diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h new file mode 100644 index 000000000..de9621205 --- /dev/null +++ b/minui/graphics_drm.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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 _GRAPHICS_DRM_H_ +#define _GRAPHICS_DRM_H_ + +#include <stdint.h> + +#include <xf86drmMode.h> + +#include "graphics.h" +#include "minui/minui.h" + +class GRSurfaceDrm : public GRSurface { + private: + uint32_t fb_id; + uint32_t handle; + + friend class MinuiBackendDrm; +}; + +class MinuiBackendDrm : public MinuiBackend { + public: + GRSurface* Init() override; + GRSurface* Flip() override; + void Blank(bool) override; + ~MinuiBackendDrm() override; + MinuiBackendDrm(); + + private: + void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc); + void DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface); + GRSurfaceDrm* DrmCreateSurface(int width, int height); + void DrmDestroySurface(GRSurfaceDrm* surface); + void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc); + drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index); + + GRSurfaceDrm* GRSurfaceDrms[2]; + int current_buffer; + drmModeCrtc* main_monitor_crtc; + drmModeConnector* main_monitor_connector; + int drm_fd; +}; + +#endif // _GRAPHICS_DRM_H_ diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp index 0788f7552..746f42aaa 100644 --- a/minui/graphics_fbdev.cpp +++ b/minui/graphics_fbdev.cpp @@ -14,188 +14,154 @@ * limitations under the License. */ -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> +#include "graphics_fbdev.h" #include <fcntl.h> +#include <linux/fb.h> #include <stdio.h> - -#include <sys/cdefs.h> +#include <stdlib.h> +#include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/types.h> +#include <unistd.h> -#include <linux/fb.h> -#include <linux/kd.h> - -#include "minui.h" -#include "graphics.h" - -static GRSurface* fbdev_init(minui_backend*); -static GRSurface* fbdev_flip(minui_backend*); -static void fbdev_blank(minui_backend*, bool); -static void fbdev_exit(minui_backend*); - -static GRSurface gr_framebuffer[2]; -static bool double_buffered; -static GRSurface* gr_draw = NULL; -static int displayed_buffer; - -static fb_var_screeninfo vi; -static int fb_fd = -1; - -static minui_backend my_backend = { - .init = fbdev_init, - .flip = fbdev_flip, - .blank = fbdev_blank, - .exit = fbdev_exit, -}; - -minui_backend* open_fbdev() { - return &my_backend; -} +#include "minui/minui.h" -static void fbdev_blank(minui_backend* backend __unused, bool blank) -{ - int ret; +MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {} - ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); - if (ret < 0) - perror("ioctl(): blank"); +void MinuiBackendFbdev::Blank(bool blank) { + int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); + if (ret < 0) perror("ioctl(): blank"); } -static void set_displayed_framebuffer(unsigned n) -{ - if (n > 1 || !double_buffered) return; +void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) { + if (n > 1 || !double_buffered) return; - vi.yres_virtual = gr_framebuffer[0].height * 2; - vi.yoffset = n * gr_framebuffer[0].height; - vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; - if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { - perror("active fb swap failed"); - } - displayed_buffer = n; + vi.yres_virtual = gr_framebuffer[0].height * 2; + vi.yoffset = n * gr_framebuffer[0].height; + vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; + if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { + perror("active fb swap failed"); + } + displayed_buffer = n; } -static GRSurface* fbdev_init(minui_backend* backend) { - int fd = open("/dev/graphics/fb0", O_RDWR); - if (fd == -1) { - perror("cannot open fb0"); - return NULL; +GRSurface* MinuiBackendFbdev::Init() { + int fd = open("/dev/graphics/fb0", O_RDWR); + if (fd == -1) { + perror("cannot open fb0"); + return nullptr; + } + + fb_fix_screeninfo fi; + if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return nullptr; + } + + if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) { + perror("failed to get fb0 info"); + close(fd); + return nullptr; + } + + // We print this out for informational purposes only, but + // throughout we assume that the framebuffer device uses an RGBX + // pixel format. This is the case for every development device I + // have access to. For some of those devices (eg, hammerhead aka + // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a + // different format (XBGR) but actually produces the correct + // results on the display when you write RGBX. + // + // If you have a device that actually *needs* another pixel format + // (ie, BGRX, or 565), patches welcome... + + printf( + "fb0 reports (possibly inaccurate):\n" + " vi.bits_per_pixel = %d\n" + " vi.red.offset = %3d .length = %3d\n" + " vi.green.offset = %3d .length = %3d\n" + " vi.blue.offset = %3d .length = %3d\n", + vi.bits_per_pixel, vi.red.offset, vi.red.length, vi.green.offset, vi.green.length, + vi.blue.offset, vi.blue.length); + + void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (bits == MAP_FAILED) { + perror("failed to mmap framebuffer"); + close(fd); + return nullptr; + } + + memset(bits, 0, fi.smem_len); + + gr_framebuffer[0].width = vi.xres; + gr_framebuffer[0].height = vi.yres; + gr_framebuffer[0].row_bytes = fi.line_length; + gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; + gr_framebuffer[0].data = static_cast<uint8_t*>(bits); + memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); + + /* check if we can use double buffering */ + if (vi.yres * fi.line_length * 2 <= fi.smem_len) { + double_buffered = true; + + memcpy(gr_framebuffer + 1, gr_framebuffer, sizeof(GRSurface)); + gr_framebuffer[1].data = + gr_framebuffer[0].data + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes; + + gr_draw = gr_framebuffer + 1; + + } else { + double_buffered = false; + + // Without double-buffering, we allocate RAM for a buffer to + // draw in, and then "flipping" the buffer consists of a + // memcpy from the buffer we allocated to the framebuffer. + + gr_draw = static_cast<GRSurface*>(malloc(sizeof(GRSurface))); + memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface)); + gr_draw->data = static_cast<unsigned char*>(malloc(gr_draw->height * gr_draw->row_bytes)); + if (!gr_draw->data) { + perror("failed to allocate in-memory surface"); + return nullptr; } + } - fb_fix_screeninfo fi; - if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { - perror("failed to get fb0 info"); - close(fd); - return NULL; - } - - if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) { - perror("failed to get fb0 info"); - close(fd); - return NULL; - } - - // We print this out for informational purposes only, but - // throughout we assume that the framebuffer device uses an RGBX - // pixel format. This is the case for every development device I - // have access to. For some of those devices (eg, hammerhead aka - // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a - // different format (XBGR) but actually produces the correct - // results on the display when you write RGBX. - // - // If you have a device that actually *needs* another pixel format - // (ie, BGRX, or 565), patches welcome... - - printf("fb0 reports (possibly inaccurate):\n" - " vi.bits_per_pixel = %d\n" - " vi.red.offset = %3d .length = %3d\n" - " vi.green.offset = %3d .length = %3d\n" - " vi.blue.offset = %3d .length = %3d\n", - vi.bits_per_pixel, - vi.red.offset, vi.red.length, - vi.green.offset, vi.green.length, - vi.blue.offset, vi.blue.length); - - void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (bits == MAP_FAILED) { - perror("failed to mmap framebuffer"); - close(fd); - return NULL; - } - - memset(bits, 0, fi.smem_len); - - gr_framebuffer[0].width = vi.xres; - gr_framebuffer[0].height = vi.yres; - gr_framebuffer[0].row_bytes = fi.line_length; - gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; - gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits); - memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); - - /* check if we can use double buffering */ - if (vi.yres * fi.line_length * 2 <= fi.smem_len) { - double_buffered = true; - - memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface)); - gr_framebuffer[1].data = gr_framebuffer[0].data + - gr_framebuffer[0].height * gr_framebuffer[0].row_bytes; + memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); + fb_fd = fd; + SetDisplayedFramebuffer(0); - gr_draw = gr_framebuffer+1; + printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); - } else { - double_buffered = false; + Blank(true); + Blank(false); - // Without double-buffering, we allocate RAM for a buffer to - // draw in, and then "flipping" the buffer consists of a - // memcpy from the buffer we allocated to the framebuffer. - - gr_draw = (GRSurface*) malloc(sizeof(GRSurface)); - memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface)); - gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes); - if (!gr_draw->data) { - perror("failed to allocate in-memory surface"); - return NULL; - } - } - - memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); - fb_fd = fd; - set_displayed_framebuffer(0); - - printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); - - fbdev_blank(backend, true); - fbdev_blank(backend, false); - - return gr_draw; + return gr_draw; } -static GRSurface* fbdev_flip(minui_backend* backend __unused) { - if (double_buffered) { - // Change gr_draw to point to the buffer currently displayed, - // then flip the driver so we're displaying the other buffer - // instead. - gr_draw = gr_framebuffer + displayed_buffer; - set_displayed_framebuffer(1-displayed_buffer); - } else { - // Copy from the in-memory surface to the framebuffer. - memcpy(gr_framebuffer[0].data, gr_draw->data, - gr_draw->height * gr_draw->row_bytes); - } - return gr_draw; +GRSurface* MinuiBackendFbdev::Flip() { + if (double_buffered) { + // Change gr_draw to point to the buffer currently displayed, + // then flip the driver so we're displaying the other buffer + // instead. + gr_draw = gr_framebuffer + displayed_buffer; + SetDisplayedFramebuffer(1 - displayed_buffer); + } else { + // Copy from the in-memory surface to the framebuffer. + memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes); + } + return gr_draw; } -static void fbdev_exit(minui_backend* backend __unused) { - close(fb_fd); - fb_fd = -1; +MinuiBackendFbdev::~MinuiBackendFbdev() { + close(fb_fd); + fb_fd = -1; - if (!double_buffered && gr_draw) { - free(gr_draw->data); - free(gr_draw); - } - gr_draw = NULL; + if (!double_buffered && gr_draw) { + free(gr_draw->data); + free(gr_draw); + } + gr_draw = nullptr; } diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h new file mode 100644 index 000000000..107e19567 --- /dev/null +++ b/minui/graphics_fbdev.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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 _GRAPHICS_FBDEV_H_ +#define _GRAPHICS_FBDEV_H_ + +#include <linux/fb.h> + +#include "graphics.h" +#include "minui/minui.h" + +class MinuiBackendFbdev : public MinuiBackend { + public: + GRSurface* Init() override; + GRSurface* Flip() override; + void Blank(bool) override; + ~MinuiBackendFbdev() override; + MinuiBackendFbdev(); + + private: + void SetDisplayedFramebuffer(unsigned n); + + GRSurface gr_framebuffer[2]; + bool double_buffered; + GRSurface* gr_draw; + int displayed_buffer; + fb_var_screeninfo vi; + int fb_fd; +}; + +#endif // _GRAPHICS_FBDEV_H_ diff --git a/minui/minui.h b/minui/include/minui/minui.h index d30426dc8..78dd4cb98 100644 --- a/minui/minui.h +++ b/minui/include/minui/minui.h @@ -20,6 +20,7 @@ #include <sys/types.h> #include <functional> +#include <string> // // Graphics. @@ -70,15 +71,14 @@ unsigned int gr_get_height(GRSurface* surface); struct input_event; -// TODO: move these over to std::function. -typedef int (*ev_callback)(int fd, uint32_t epevents, void* data); -typedef int (*ev_set_key_callback)(int code, int value, void* data); +using ev_callback = std::function<int(int fd, uint32_t epevents)>; +using ev_set_key_callback = std::function<int(int code, int value)>; -int ev_init(ev_callback input_cb, void* data); +int ev_init(ev_callback input_cb); void ev_exit(); -int ev_add_fd(int fd, ev_callback cb, void* data); -void ev_iterate_available_keys(std::function<void(int)> f); -int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data); +int ev_add_fd(int fd, ev_callback cb); +void ev_iterate_available_keys(const std::function<void(int)>& f); +int ev_sync_key_state(const ev_set_key_callback& set_key_cb); // 'timeout' has the same semantics as poll(2). // 0 : don't block @@ -94,7 +94,7 @@ int ev_get_epollfd(); // Resources // -bool matches_locale(const char* prefix, const char* locale); +bool matches_locale(const std::string& prefix, const std::string& locale); // res_create_*_surface() functions return 0 if no error, else // negative. @@ -123,8 +123,8 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface); // given locale. The image is expected to be a composite of multiple // translations of the same text, with special added rows that encode // the subimages' size and intended locale in the pixel data. See -// development/tools/recovery_l10n for an app that will generate these -// specialized images from Android resources. +// bootable/recovery/tools/recovery_l10n for an app that will generate +// these specialized images from Android resources. int res_create_localized_alpha_surface(const char* name, const char* locale, GRSurface** pSurface); diff --git a/minui/resources.cpp b/minui/resources.cpp index 40d3c2c88..86c731b02 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -14,29 +14,31 @@ * limitations under the License. */ -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - #include <fcntl.h> +#include <linux/fb.h> +#include <linux/kd.h> #include <stdio.h> - +#include <stdlib.h> +#include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/types.h> +#include <unistd.h> -#include <linux/fb.h> -#include <linux/kd.h> +#include <regex> +#include <string> +#include <vector> +#include <android-base/strings.h> #include <png.h> -#include "minui.h" +#include "minui/minui.h" #define SURFACE_DATA_ALIGNMENT 8 static GRSurface* malloc_surface(size_t data_size) { size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT; - unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size)); + unsigned char* temp = static_cast<unsigned char*>(malloc(size)); if (temp == NULL) return NULL; GRSurface* surface = reinterpret_cast<GRSurface*>(temp); surface->data = temp + sizeof(GRSurface) + @@ -220,7 +222,7 @@ int res_create_display_surface(const char* name, GRSurface** pSurface) { png_set_bgr(png_ptr); #endif - p_row = reinterpret_cast<unsigned char*>(malloc(width * 4)); + p_row = static_cast<unsigned char*>(malloc(width * 4)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, p_row, NULL); transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width); @@ -268,7 +270,7 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, printf(" found fps = %d\n", *fps); } - if (frames <= 0 || fps <= 0) { + if (*frames <= 0 || *fps <= 0) { printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps); result = -10; goto exit; @@ -280,7 +282,7 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, goto exit; } - surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*))); + surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*))); if (surface == NULL) { result = -8; goto exit; @@ -297,7 +299,7 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, png_set_bgr(png_ptr); #endif - p_row = reinterpret_cast<unsigned char*>(malloc(width * 4)); + p_row = static_cast<unsigned char*>(malloc(width * 4)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, p_row, NULL); int frame = y % *frames; @@ -307,7 +309,7 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, } free(p_row); - *pSurface = reinterpret_cast<GRSurface**>(surface); + *pSurface = surface; exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); @@ -315,7 +317,7 @@ exit: if (result < 0) { if (surface) { for (int i = 0; i < *frames; ++i) { - if (surface[i]) free(surface[i]); + free(surface[i]); } free(surface); } @@ -372,14 +374,26 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) { // This function tests if a locale string stored in PNG (prefix) matches // the locale string provided by the system (locale). -bool matches_locale(const char* prefix, const char* locale) { - if (locale == NULL) return false; - - // Return true if the whole string of prefix matches the top part of - // locale. For instance, prefix == "en" matches locale == "en_US"; - // and prefix == "zh_CN" matches locale == "zh_CN_#Hans". - - return (strncmp(prefix, locale, strlen(prefix)) == 0); +bool matches_locale(const std::string& prefix, const std::string& locale) { + // According to the BCP 47 format, A locale string may consists of: + // language-{extlang}-{script}-{region}-{variant} + // The locale headers in PNG mostly consist of language-{region} except for sr-Latn, and some + // android's system locale can have the format language-{script}-{region}. + + // Return true if the whole string of prefix matches the top part of locale. Otherwise try to + // match the locale string without the {script} section. + // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale + // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN". + if (android::base::StartsWith(locale, prefix.c_str())) { + return true; + } + + size_t separator = prefix.find('-'); + if (separator == std::string::npos) { + return false; + } + std::regex loc_regex(prefix.substr(0, separator) + "-[A-Za-z]*" + prefix.substr(separator)); + return std::regex_match(locale, loc_regex); } int res_create_localized_alpha_surface(const char* name, @@ -391,18 +405,13 @@ int res_create_localized_alpha_surface(const char* name, png_infop info_ptr = NULL; png_uint_32 width, height; png_byte channels; - unsigned char* row; png_uint_32 y; + std::vector<unsigned char> row; *pSurface = NULL; if (locale == NULL) { - surface = malloc_surface(0); - surface->width = 0; - surface->height = 0; - surface->row_bytes = 0; - surface->pixel_bytes = 1; - goto exit; + return result; } result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); @@ -413,13 +422,13 @@ int res_create_localized_alpha_surface(const char* name, goto exit; } - row = reinterpret_cast<unsigned char*>(malloc(width)); + row.resize(width); for (y = 0; y < height; ++y) { - png_read_row(png_ptr, row, NULL); + png_read_row(png_ptr, row.data(), NULL); int w = (row[1] << 8) | row[0]; int h = (row[3] << 8) | row[2]; - int len = row[4]; - char* loc = (char*)row+5; + __unused int len = row[4]; + char* loc = reinterpret_cast<char*>(&row[5]); if (y+1+h >= height || matches_locale(loc, locale)) { printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y); @@ -436,16 +445,16 @@ int res_create_localized_alpha_surface(const char* name, int i; for (i = 0; i < h; ++i, ++y) { - png_read_row(png_ptr, row, NULL); - memcpy(surface->data + i*w, row, w); + png_read_row(png_ptr, row.data(), NULL); + memcpy(surface->data + i*w, row.data(), w); } - *pSurface = reinterpret_cast<GRSurface*>(surface); + *pSurface = surface; break; } else { int i; for (i = 0; i < h; ++i, ++y) { - png_read_row(png_ptr, row, NULL); + png_read_row(png_ptr, row.data(), NULL); } } } diff --git a/minzip/Android.mk b/minzip/Android.mk deleted file mode 100644 index 22eabfbb1..000000000 --- a/minzip/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - Hash.c \ - SysUtil.c \ - DirUtil.c \ - Inlines.c \ - Zip.c - -LOCAL_C_INCLUDES := \ - external/zlib \ - external/safe-iop/include - -LOCAL_STATIC_LIBRARIES := libselinux - -LOCAL_MODULE := libminzip - -LOCAL_CLANG := true - -LOCAL_CFLAGS += -Werror -Wall - -include $(BUILD_STATIC_LIBRARY) diff --git a/minzip/Bits.h b/minzip/Bits.h deleted file mode 100644 index f96e6c443..000000000 --- a/minzip/Bits.h +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * Some handy functions for manipulating bits and bytes. - */ -#ifndef _MINZIP_BITS -#define _MINZIP_BITS - -#include "inline_magic.h" - -#include <stdlib.h> -#include <string.h> - -/* - * Get 1 byte. (Included to make the code more legible.) - */ -INLINE unsigned char get1(unsigned const char* pSrc) -{ - return *pSrc; -} - -/* - * Get 2 big-endian bytes. - */ -INLINE unsigned short get2BE(unsigned char const* pSrc) -{ - unsigned short result; - - result = *pSrc++ << 8; - result |= *pSrc++; - - return result; -} - -/* - * Get 4 big-endian bytes. - */ -INLINE unsigned int get4BE(unsigned char const* pSrc) -{ - unsigned int result; - - result = *pSrc++ << 24; - result |= *pSrc++ << 16; - result |= *pSrc++ << 8; - result |= *pSrc++; - - return result; -} - -/* - * Get 8 big-endian bytes. - */ -INLINE unsigned long long get8BE(unsigned char const* pSrc) -{ - unsigned long long result; - - result = (unsigned long long) *pSrc++ << 56; - result |= (unsigned long long) *pSrc++ << 48; - result |= (unsigned long long) *pSrc++ << 40; - result |= (unsigned long long) *pSrc++ << 32; - result |= (unsigned long long) *pSrc++ << 24; - result |= (unsigned long long) *pSrc++ << 16; - result |= (unsigned long long) *pSrc++ << 8; - result |= (unsigned long long) *pSrc++; - - return result; -} - -/* - * Get 2 little-endian bytes. - */ -INLINE unsigned short get2LE(unsigned char const* pSrc) -{ - unsigned short result; - - result = *pSrc++; - result |= *pSrc++ << 8; - - return result; -} - -/* - * Get 4 little-endian bytes. - */ -INLINE unsigned int get4LE(unsigned char const* pSrc) -{ - unsigned int result; - - result = *pSrc++; - result |= *pSrc++ << 8; - result |= *pSrc++ << 16; - result |= *pSrc++ << 24; - - return result; -} - -/* - * Get 8 little-endian bytes. - */ -INLINE unsigned long long get8LE(unsigned char const* pSrc) -{ - unsigned long long result; - - result = (unsigned long long) *pSrc++; - result |= (unsigned long long) *pSrc++ << 8; - result |= (unsigned long long) *pSrc++ << 16; - result |= (unsigned long long) *pSrc++ << 24; - result |= (unsigned long long) *pSrc++ << 32; - result |= (unsigned long long) *pSrc++ << 40; - result |= (unsigned long long) *pSrc++ << 48; - result |= (unsigned long long) *pSrc++ << 56; - - return result; -} - -/* - * Grab 1 byte and advance the data pointer. - */ -INLINE unsigned char read1(unsigned const char** ppSrc) -{ - return *(*ppSrc)++; -} - -/* - * Grab 2 big-endian bytes and advance the data pointer. - */ -INLINE unsigned short read2BE(unsigned char const** ppSrc) -{ - unsigned short result; - - result = *(*ppSrc)++ << 8; - result |= *(*ppSrc)++; - - return result; -} - -/* - * Grab 4 big-endian bytes and advance the data pointer. - */ -INLINE unsigned int read4BE(unsigned char const** ppSrc) -{ - unsigned int result; - - result = *(*ppSrc)++ << 24; - result |= *(*ppSrc)++ << 16; - result |= *(*ppSrc)++ << 8; - result |= *(*ppSrc)++; - - return result; -} - -/* - * Get 8 big-endian bytes. - */ -INLINE unsigned long long read8BE(unsigned char const** ppSrc) -{ - unsigned long long result; - - result = (unsigned long long) *(*ppSrc)++ << 56; - result |= (unsigned long long) *(*ppSrc)++ << 48; - result |= (unsigned long long) *(*ppSrc)++ << 40; - result |= (unsigned long long) *(*ppSrc)++ << 32; - result |= (unsigned long long) *(*ppSrc)++ << 24; - result |= (unsigned long long) *(*ppSrc)++ << 16; - result |= (unsigned long long) *(*ppSrc)++ << 8; - result |= (unsigned long long) *(*ppSrc)++; - - return result; -} - -/* - * Grab 2 little-endian bytes and advance the data pointer. - */ -INLINE unsigned short read2LE(unsigned char const** ppSrc) -{ - unsigned short result; - - result = *(*ppSrc)++; - result |= *(*ppSrc)++ << 8; - - return result; -} - -/* - * Grab 4 little-endian bytes and advance the data pointer. - */ -INLINE unsigned int read4LE(unsigned char const** ppSrc) -{ - unsigned int result; - - result = *(*ppSrc)++; - result |= *(*ppSrc)++ << 8; - result |= *(*ppSrc)++ << 16; - result |= *(*ppSrc)++ << 24; - - return result; -} - -/* - * Get 8 little-endian bytes. - */ -INLINE unsigned long long read8LE(unsigned char const** ppSrc) -{ - unsigned long long result; - - result = (unsigned long long) *(*ppSrc)++; - result |= (unsigned long long) *(*ppSrc)++ << 8; - result |= (unsigned long long) *(*ppSrc)++ << 16; - result |= (unsigned long long) *(*ppSrc)++ << 24; - result |= (unsigned long long) *(*ppSrc)++ << 32; - result |= (unsigned long long) *(*ppSrc)++ << 40; - result |= (unsigned long long) *(*ppSrc)++ << 48; - result |= (unsigned long long) *(*ppSrc)++ << 56; - - return result; -} - -/* - * Skip over a UTF-8 string. - */ -INLINE void skipUtf8String(unsigned char const** ppSrc) -{ - unsigned int length = read4BE(ppSrc); - - (*ppSrc) += length; -} - -/* - * Read a UTF-8 string into a fixed-size buffer, and null-terminate it. - * - * Returns the length of the original string. - */ -INLINE int readUtf8String(unsigned char const** ppSrc, char* buf, size_t bufLen) -{ - unsigned int length = read4BE(ppSrc); - size_t copyLen = (length < bufLen) ? length : bufLen-1; - - memcpy(buf, *ppSrc, copyLen); - buf[copyLen] = '\0'; - - (*ppSrc) += length; - return length; -} - -/* - * Read a UTF-8 string into newly-allocated storage, and null-terminate it. - * - * Returns the string and its length. (The latter is probably unnecessary - * for the way we're using UTF8.) - */ -INLINE char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength) -{ - unsigned int length = read4BE(ppSrc); - char* buf; - - buf = (char*) malloc(length+1); - - memcpy(buf, *ppSrc, length); - buf[length] = '\0'; - - (*ppSrc) += length; - - *pLength = length; - return buf; -} - - -/* - * Set 1 byte. (Included to make the code more legible.) - */ -INLINE void set1(unsigned char* buf, unsigned char val) -{ - *buf = (unsigned char)(val); -} - -/* - * Set 2 big-endian bytes. - */ -INLINE void set2BE(unsigned char* buf, unsigned short val) -{ - *buf++ = (unsigned char)(val >> 8); - *buf = (unsigned char)(val); -} - -/* - * Set 4 big-endian bytes. - */ -INLINE void set4BE(unsigned char* buf, unsigned int val) -{ - *buf++ = (unsigned char)(val >> 24); - *buf++ = (unsigned char)(val >> 16); - *buf++ = (unsigned char)(val >> 8); - *buf = (unsigned char)(val); -} - -/* - * Set 8 big-endian bytes. - */ -INLINE void set8BE(unsigned char* buf, unsigned long long val) -{ - *buf++ = (unsigned char)(val >> 56); - *buf++ = (unsigned char)(val >> 48); - *buf++ = (unsigned char)(val >> 40); - *buf++ = (unsigned char)(val >> 32); - *buf++ = (unsigned char)(val >> 24); - *buf++ = (unsigned char)(val >> 16); - *buf++ = (unsigned char)(val >> 8); - *buf = (unsigned char)(val); -} - -/* - * Set 2 little-endian bytes. - */ -INLINE void set2LE(unsigned char* buf, unsigned short val) -{ - *buf++ = (unsigned char)(val); - *buf = (unsigned char)(val >> 8); -} - -/* - * Set 4 little-endian bytes. - */ -INLINE void set4LE(unsigned char* buf, unsigned int val) -{ - *buf++ = (unsigned char)(val); - *buf++ = (unsigned char)(val >> 8); - *buf++ = (unsigned char)(val >> 16); - *buf = (unsigned char)(val >> 24); -} - -/* - * Set 8 little-endian bytes. - */ -INLINE void set8LE(unsigned char* buf, unsigned long long val) -{ - *buf++ = (unsigned char)(val); - *buf++ = (unsigned char)(val >> 8); - *buf++ = (unsigned char)(val >> 16); - *buf++ = (unsigned char)(val >> 24); - *buf++ = (unsigned char)(val >> 32); - *buf++ = (unsigned char)(val >> 40); - *buf++ = (unsigned char)(val >> 48); - *buf = (unsigned char)(val >> 56); -} - -/* - * Stuff a UTF-8 string into the buffer. - */ -INLINE void setUtf8String(unsigned char* buf, const unsigned char* str) -{ - unsigned int strLen = strlen((const char*)str); - - set4BE(buf, strLen); - memcpy(buf + sizeof(unsigned int), str, strLen); -} - -#endif /*_MINZIP_BITS*/ diff --git a/minzip/Hash.c b/minzip/Hash.c deleted file mode 100644 index 49bcb3161..000000000 --- a/minzip/Hash.c +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * Hash table. The dominant calls are add and lookup, with removals - * happening very infrequently. We use probing, and don't worry much - * about tombstone removal. - */ -#include <stdlib.h> -#include <assert.h> - -#define LOG_TAG "minzip" -#include "Log.h" -#include "Hash.h" - -/* table load factor, i.e. how full can it get before we resize */ -//#define LOAD_NUMER 3 // 75% -//#define LOAD_DENOM 4 -#define LOAD_NUMER 5 // 62.5% -#define LOAD_DENOM 8 -//#define LOAD_NUMER 1 // 50% -//#define LOAD_DENOM 2 - -/* - * Compute the capacity needed for a table to hold "size" elements. - */ -size_t mzHashSize(size_t size) { - return (size * LOAD_DENOM) / LOAD_NUMER +1; -} - -/* - * Round up to the next highest power of 2. - * - * Found on http://graphics.stanford.edu/~seander/bithacks.html. - */ -unsigned int roundUpPower2(unsigned int val) -{ - val--; - val |= val >> 1; - val |= val >> 2; - val |= val >> 4; - val |= val >> 8; - val |= val >> 16; - val++; - - return val; -} - -/* - * Create and initialize a hash table. - */ -HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc) -{ - HashTable* pHashTable; - - assert(initialSize > 0); - - pHashTable = (HashTable*) malloc(sizeof(*pHashTable)); - if (pHashTable == NULL) - return NULL; - - pHashTable->tableSize = roundUpPower2(initialSize); - pHashTable->numEntries = pHashTable->numDeadEntries = 0; - pHashTable->freeFunc = freeFunc; - pHashTable->pEntries = - (HashEntry*) calloc((size_t)pHashTable->tableSize, sizeof(HashTable)); - if (pHashTable->pEntries == NULL) { - free(pHashTable); - return NULL; - } - - return pHashTable; -} - -/* - * Clear out all entries. - */ -void mzHashTableClear(HashTable* pHashTable) -{ - HashEntry* pEnt; - int i; - - pEnt = pHashTable->pEntries; - for (i = 0; i < pHashTable->tableSize; i++, pEnt++) { - if (pEnt->data == HASH_TOMBSTONE) { - // nuke entry - pEnt->data = NULL; - } else if (pEnt->data != NULL) { - // call free func then nuke entry - if (pHashTable->freeFunc != NULL) - (*pHashTable->freeFunc)(pEnt->data); - pEnt->data = NULL; - } - } - - pHashTable->numEntries = 0; - pHashTable->numDeadEntries = 0; -} - -/* - * Free the table. - */ -void mzHashTableFree(HashTable* pHashTable) -{ - if (pHashTable == NULL) - return; - mzHashTableClear(pHashTable); - free(pHashTable->pEntries); - free(pHashTable); -} - -#ifndef NDEBUG -/* - * Count up the number of tombstone entries in the hash table. - */ -static int countTombStones(HashTable* pHashTable) -{ - int i, count; - - for (count = i = 0; i < pHashTable->tableSize; i++) { - if (pHashTable->pEntries[i].data == HASH_TOMBSTONE) - count++; - } - return count; -} -#endif - -/* - * Resize a hash table. We do this when adding an entry increased the - * size of the table beyond its comfy limit. - * - * This essentially requires re-inserting all elements into the new storage. - * - * If multiple threads can access the hash table, the table's lock should - * have been grabbed before issuing the "lookup+add" call that led to the - * resize, so we don't have a synchronization problem here. - */ -static bool resizeHash(HashTable* pHashTable, int newSize) -{ - HashEntry* pNewEntries; - int i; - - assert(countTombStones(pHashTable) == pHashTable->numDeadEntries); - - pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable)); - if (pNewEntries == NULL) - return false; - - for (i = 0; i < pHashTable->tableSize; i++) { - void* data = pHashTable->pEntries[i].data; - if (data != NULL && data != HASH_TOMBSTONE) { - int hashValue = pHashTable->pEntries[i].hashValue; - int newIdx; - - /* probe for new spot, wrapping around */ - newIdx = hashValue & (newSize-1); - while (pNewEntries[newIdx].data != NULL) - newIdx = (newIdx + 1) & (newSize-1); - - pNewEntries[newIdx].hashValue = hashValue; - pNewEntries[newIdx].data = data; - } - } - - free(pHashTable->pEntries); - pHashTable->pEntries = pNewEntries; - pHashTable->tableSize = newSize; - pHashTable->numDeadEntries = 0; - - assert(countTombStones(pHashTable) == 0); - return true; -} - -/* - * Look up an entry. - * - * We probe on collisions, wrapping around the table. - */ -void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item, - HashCompareFunc cmpFunc, bool doAdd) -{ - HashEntry* pEntry; - HashEntry* pEnd; - void* result = NULL; - - assert(pHashTable->tableSize > 0); - assert(item != HASH_TOMBSTONE); - assert(item != NULL); - - /* jump to the first entry and probe for a match */ - pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; - pEnd = &pHashTable->pEntries[pHashTable->tableSize]; - while (pEntry->data != NULL) { - if (pEntry->data != HASH_TOMBSTONE && - pEntry->hashValue == itemHash && - (*cmpFunc)(pEntry->data, item) == 0) - { - /* match */ - break; - } - - pEntry++; - if (pEntry == pEnd) { /* wrap around to start */ - if (pHashTable->tableSize == 1) - break; /* edge case - single-entry table */ - pEntry = pHashTable->pEntries; - } - } - - if (pEntry->data == NULL) { - if (doAdd) { - pEntry->hashValue = itemHash; - pEntry->data = item; - pHashTable->numEntries++; - - /* - * We've added an entry. See if this brings us too close to full. - */ - if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM - > pHashTable->tableSize * LOAD_NUMER) - { - if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) { - /* don't really have a way to indicate failure */ - LOGE("Dalvik hash resize failure\n"); - abort(); - } - /* note "pEntry" is now invalid */ - } - - /* full table is bad -- search for nonexistent never halts */ - assert(pHashTable->numEntries < pHashTable->tableSize); - result = item; - } else { - assert(result == NULL); - } - } else { - result = pEntry->data; - } - - return result; -} - -/* - * Remove an entry from the table. - * - * Does NOT invoke the "free" function on the item. - */ -bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item) -{ - HashEntry* pEntry; - HashEntry* pEnd; - - assert(pHashTable->tableSize > 0); - - /* jump to the first entry and probe for a match */ - pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; - pEnd = &pHashTable->pEntries[pHashTable->tableSize]; - while (pEntry->data != NULL) { - if (pEntry->data == item) { - pEntry->data = HASH_TOMBSTONE; - pHashTable->numEntries--; - pHashTable->numDeadEntries++; - return true; - } - - pEntry++; - if (pEntry == pEnd) { /* wrap around to start */ - if (pHashTable->tableSize == 1) - break; /* edge case - single-entry table */ - pEntry = pHashTable->pEntries; - } - } - - return false; -} - -/* - * Execute a function on every entry in the hash table. - * - * If "func" returns a nonzero value, terminate early and return the value. - */ -int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg) -{ - int i, val; - - for (i = 0; i < pHashTable->tableSize; i++) { - HashEntry* pEnt = &pHashTable->pEntries[i]; - - if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) { - val = (*func)(pEnt->data, arg); - if (val != 0) - return val; - } - } - - return 0; -} - - -/* - * Look up an entry, counting the number of times we have to probe. - * - * Returns -1 if the entry wasn't found. - */ -int countProbes(HashTable* pHashTable, unsigned int itemHash, const void* item, - HashCompareFunc cmpFunc) -{ - HashEntry* pEntry; - HashEntry* pEnd; - int count = 0; - - assert(pHashTable->tableSize > 0); - assert(item != HASH_TOMBSTONE); - assert(item != NULL); - - /* jump to the first entry and probe for a match */ - pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)]; - pEnd = &pHashTable->pEntries[pHashTable->tableSize]; - while (pEntry->data != NULL) { - if (pEntry->data != HASH_TOMBSTONE && - pEntry->hashValue == itemHash && - (*cmpFunc)(pEntry->data, item) == 0) - { - /* match */ - break; - } - - pEntry++; - if (pEntry == pEnd) { /* wrap around to start */ - if (pHashTable->tableSize == 1) - break; /* edge case - single-entry table */ - pEntry = pHashTable->pEntries; - } - - count++; - } - if (pEntry->data == NULL) - return -1; - - return count; -} - -/* - * Evaluate the amount of probing required for the specified hash table. - * - * We do this by running through all entries in the hash table, computing - * the hash value and then doing a lookup. - * - * The caller should lock the table before calling here. - */ -void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, - HashCompareFunc cmpFunc) -{ - int numEntries, minProbe, maxProbe, totalProbe; - HashIter iter; - - numEntries = maxProbe = totalProbe = 0; - minProbe = 65536*32767; - - for (mzHashIterBegin(pHashTable, &iter); !mzHashIterDone(&iter); - mzHashIterNext(&iter)) - { - const void* data = (const void*)mzHashIterData(&iter); - int count; - - count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc); - - numEntries++; - - if (count < minProbe) - minProbe = count; - if (count > maxProbe) - maxProbe = count; - totalProbe += count; - } - - LOGV("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n", - minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize, - (float) totalProbe / (float) numEntries); -} diff --git a/minzip/Hash.h b/minzip/Hash.h deleted file mode 100644 index e83eac414..000000000 --- a/minzip/Hash.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2007 The Android Open Source Project - * - * General purpose hash table, used for finding classes, methods, etc. - * - * When the number of elements reaches 3/4 of the table's capacity, the - * table will be resized. - */ -#ifndef _MINZIP_HASH -#define _MINZIP_HASH - -#include "inline_magic.h" - -#include <stdlib.h> -#include <stdbool.h> -#include <assert.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/* compute the hash of an item with a specific type */ -typedef unsigned int (*HashCompute)(const void* item); - -/* - * Compare a hash entry with a "loose" item after their hash values match. - * Returns { <0, 0, >0 } depending on ordering of items (same semantics - * as strcmp()). - */ -typedef int (*HashCompareFunc)(const void* tableItem, const void* looseItem); - -/* - * This function will be used to free entries in the table. This can be - * NULL if no free is required, free(), or a custom function. - */ -typedef void (*HashFreeFunc)(void* ptr); - -/* - * Used by mzHashForeach(). - */ -typedef int (*HashForeachFunc)(void* data, void* arg); - -/* - * One entry in the hash table. "data" values are expected to be (or have - * the same characteristics as) valid pointers. In particular, a NULL - * value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates - * a no-longer-used slot that must be stepped over during probing. - * - * Attempting to add a NULL or tombstone value is an error. - * - * When an entry is released, we will call (HashFreeFunc)(entry->data). - */ -typedef struct HashEntry { - unsigned int hashValue; - void* data; -} HashEntry; - -#define HASH_TOMBSTONE ((void*) 0xcbcacccd) // invalid ptr value - -/* - * Expandable hash table. - * - * This structure should be considered opaque. - */ -typedef struct HashTable { - int tableSize; /* must be power of 2 */ - int numEntries; /* current #of "live" entries */ - int numDeadEntries; /* current #of tombstone entries */ - HashEntry* pEntries; /* array on heap */ - HashFreeFunc freeFunc; -} HashTable; - -/* - * Create and initialize a HashTable structure, using "initialSize" as - * a basis for the initial capacity of the table. (The actual initial - * table size may be adjusted upward.) If you know exactly how many - * elements the table will hold, pass the result from mzHashSize() in.) - * - * Returns "false" if unable to allocate the table. - */ -HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc); - -/* - * Compute the capacity needed for a table to hold "size" elements. Use - * this when you know ahead of time how many elements the table will hold. - * Pass this value into mzHashTableCreate() to ensure that you can add - * all elements without needing to reallocate the table. - */ -size_t mzHashSize(size_t size); - -/* - * Clear out a hash table, freeing the contents of any used entries. - */ -void mzHashTableClear(HashTable* pHashTable); - -/* - * Free a hash table. - */ -void mzHashTableFree(HashTable* pHashTable); - -/* - * Get #of entries in hash table. - */ -INLINE int mzHashTableNumEntries(HashTable* pHashTable) { - return pHashTable->numEntries; -} - -/* - * Get total size of hash table (for memory usage calculations). - */ -INLINE int mzHashTableMemUsage(HashTable* pHashTable) { - return sizeof(HashTable) + pHashTable->tableSize * sizeof(HashEntry); -} - -/* - * Look up an entry in the table, possibly adding it if it's not there. - * - * If "item" is not found, and "doAdd" is false, NULL is returned. - * Otherwise, a pointer to the found or added item is returned. (You can - * tell the difference by seeing if return value == item.) - * - * An "add" operation may cause the entire table to be reallocated. - */ -void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item, - HashCompareFunc cmpFunc, bool doAdd); - -/* - * Remove an item from the hash table, given its "data" pointer. Does not - * invoke the "free" function; just detaches it from the table. - */ -bool mzHashTableRemove(HashTable* pHashTable, unsigned int hash, void* item); - -/* - * Execute "func" on every entry in the hash table. - * - * If "func" returns a nonzero value, terminate early and return the value. - */ -int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg); - -/* - * An alternative to mzHashForeach(), using an iterator. - * - * Use like this: - * HashIter iter; - * for (mzHashIterBegin(hashTable, &iter); !mzHashIterDone(&iter); - * mzHashIterNext(&iter)) - * { - * MyData* data = (MyData*)mzHashIterData(&iter); - * } - */ -typedef struct HashIter { - void* data; - HashTable* pHashTable; - int idx; -} HashIter; -INLINE void mzHashIterNext(HashIter* pIter) { - int i = pIter->idx +1; - int lim = pIter->pHashTable->tableSize; - for ( ; i < lim; i++) { - void* data = pIter->pHashTable->pEntries[i].data; - if (data != NULL && data != HASH_TOMBSTONE) - break; - } - pIter->idx = i; -} -INLINE void mzHashIterBegin(HashTable* pHashTable, HashIter* pIter) { - pIter->pHashTable = pHashTable; - pIter->idx = -1; - mzHashIterNext(pIter); -} -INLINE bool mzHashIterDone(HashIter* pIter) { - return (pIter->idx >= pIter->pHashTable->tableSize); -} -INLINE void* mzHashIterData(HashIter* pIter) { - assert(pIter->idx >= 0 && pIter->idx < pIter->pHashTable->tableSize); - return pIter->pHashTable->pEntries[pIter->idx].data; -} - - -/* - * Evaluate hash table performance by examining the number of times we - * have to probe for an entry. - * - * The caller should lock the table beforehand. - */ -typedef unsigned int (*HashCalcFunc)(const void* item); -void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc, - HashCompareFunc cmpFunc); - -#ifdef __cplusplus -} -#endif - -#endif /*_MINZIP_HASH*/ diff --git a/minzip/Log.h b/minzip/Log.h deleted file mode 100644 index 36e62f594..000000000 --- a/minzip/Log.h +++ /dev/null @@ -1,207 +0,0 @@ -// -// Copyright 2005 The Android Open Source Project -// -// C/C++ logging functions. See the logging documentation for API details. -// -// We'd like these to be available from C code (in case we import some from -// somewhere), so this has a C interface. -// -// The output will be correct when the log file is shared between multiple -// threads and/or multiple processes so long as the operating system -// supports O_APPEND. These calls have mutex-protected data structures -// and so are NOT reentrant. Do not use LOG in a signal handler. -// -#ifndef _MINZIP_LOG_H -#define _MINZIP_LOG_H - -#include <stdio.h> - -// --------------------------------------------------------------------- - -/* - * Normally we strip LOGV (VERBOSE messages) from release builds. - * You can modify this (for example with "#define LOG_NDEBUG 0" - * at the top of your source file) to change that behavior. - */ -#ifndef LOG_NDEBUG -#ifdef NDEBUG -#define LOG_NDEBUG 1 -#else -#define LOG_NDEBUG 0 -#endif -#endif - -/* - * This is the local tag used for the following simplified - * logging macros. You can change this preprocessor definition - * before using the other macros to change the tag. - */ -#ifndef LOG_TAG -#define LOG_TAG NULL -#endif - -// --------------------------------------------------------------------- - -/* - * Simplified macro to send a verbose log message using the current LOG_TAG. - */ -#ifndef LOGV -#if LOG_NDEBUG -#define LOGV(...) ((void)0) -#else -#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) -#endif -#endif - -#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) - -#ifndef LOGV_IF -#if LOG_NDEBUG -#define LOGV_IF(cond, ...) ((void)0) -#else -#define LOGV_IF(cond, ...) \ - ( (CONDITION(cond)) \ - ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ - : (void)0 ) -#endif -#endif - -#define LOGVV LOGV -#define LOGVV_IF LOGV_IF - -/* - * Simplified macro to send a debug log message using the current LOG_TAG. - */ -#ifndef LOGD -#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef LOGD_IF -#define LOGD_IF(cond, ...) \ - ( (CONDITION(cond)) \ - ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ - : (void)0 ) -#endif - -/* - * Simplified macro to send an info log message using the current LOG_TAG. - */ -#ifndef LOGI -#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef LOGI_IF -#define LOGI_IF(cond, ...) \ - ( (CONDITION(cond)) \ - ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ - : (void)0 ) -#endif - -/* - * Simplified macro to send a warning log message using the current LOG_TAG. - */ -#ifndef LOGW -#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef LOGW_IF -#define LOGW_IF(cond, ...) \ - ( (CONDITION(cond)) \ - ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ - : (void)0 ) -#endif - -/* - * Simplified macro to send an error log message using the current LOG_TAG. - */ -#ifndef LOGE -#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef LOGE_IF -#define LOGE_IF(cond, ...) \ - ( (CONDITION(cond)) \ - ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ - : (void)0 ) -#endif - - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * verbose priority. - */ -#ifndef IF_LOGV -#if LOG_NDEBUG -#define IF_LOGV() if (false) -#else -#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG) -#endif -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * debug priority. - */ -#ifndef IF_LOGD -#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG) -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * info priority. - */ -#ifndef IF_LOGI -#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG) -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * warn priority. - */ -#ifndef IF_LOGW -#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG) -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * error priority. - */ -#ifndef IF_LOGE -#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG) -#endif - -// --------------------------------------------------------------------- - -/* - * Basic log message macro. - * - * Example: - * LOG(LOG_WARN, NULL, "Failed with error %d", errno); - * - * The second argument may be NULL or "" to indicate the "global" tag. - * - * Non-gcc probably won't have __FUNCTION__. It's not vital. gcc also - * offers __PRETTY_FUNCTION__, which is rather more than we need. - */ -#ifndef LOG -#define LOG(priority, tag, ...) \ - LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) -#endif - -/* - * Log macro that allows you to specify a number for the priority. - */ -#ifndef LOG_PRI -#define LOG_PRI(priority, tag, ...) \ - printf(tag ": " __VA_ARGS__) -#endif - -/* - * Conditional given a desired logging priority and tag. - */ -#ifndef IF_LOG -#define IF_LOG(priority, tag) \ - if (1) -#endif - -#endif // _MINZIP_LOG_H diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c deleted file mode 100644 index e7dd17b51..000000000 --- a/minzip/SysUtil.c +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * System utilities. - */ -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#define LOG_TAG "sysutil" -#include "Log.h" -#include "SysUtil.h" - -static bool sysMapFD(int fd, MemMapping* pMap) { - assert(pMap != NULL); - - struct stat sb; - if (fstat(fd, &sb) == -1) { - LOGE("fstat(%d) failed: %s\n", fd, strerror(errno)); - return false; - } - - void* memPtr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (memPtr == MAP_FAILED) { - LOGE("mmap(%d, R, PRIVATE, %d, 0) failed: %s\n", (int) sb.st_size, fd, strerror(errno)); - return false; - } - - pMap->addr = memPtr; - pMap->length = sb.st_size; - pMap->range_count = 1; - pMap->ranges = malloc(sizeof(MappedRange)); - if (pMap->ranges == NULL) { - LOGE("malloc failed: %s\n", strerror(errno)); - munmap(memPtr, sb.st_size); - return false; - } - pMap->ranges[0].addr = memPtr; - pMap->ranges[0].length = sb.st_size; - - return true; -} - -static int sysMapBlockFile(FILE* mapf, MemMapping* pMap) -{ - char block_dev[PATH_MAX+1]; - size_t size; - unsigned int blksize; - size_t blocks; - unsigned int range_count; - unsigned int i; - - if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) { - LOGE("failed to read block device from header\n"); - return -1; - } - for (i = 0; i < sizeof(block_dev); ++i) { - if (block_dev[i] == '\n') { - block_dev[i] = 0; - break; - } - } - - if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) { - LOGE("failed to parse block map header\n"); - return -1; - } - if (blksize != 0) { - blocks = ((size-1) / blksize) + 1; - } - if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) { - LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n", - size, blksize, range_count); - return -1; - } - - pMap->range_count = range_count; - pMap->ranges = calloc(range_count, sizeof(MappedRange)); - if (pMap->ranges == NULL) { - LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno)); - return -1; - } - - // Reserve enough contiguous address space for the whole file. - unsigned char* reserve; - reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (reserve == MAP_FAILED) { - LOGE("failed to reserve address space: %s\n", strerror(errno)); - free(pMap->ranges); - return -1; - } - - int fd = open(block_dev, O_RDONLY); - if (fd < 0) { - LOGE("failed to open block device %s: %s\n", block_dev, strerror(errno)); - munmap(reserve, blocks * blksize); - free(pMap->ranges); - return -1; - } - - unsigned char* next = reserve; - size_t remaining_size = blocks * blksize; - bool success = true; - for (i = 0; i < range_count; ++i) { - size_t start, end; - if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) { - LOGE("failed to parse range %d in block map\n", i); - success = false; - break; - } - size_t length = (end - start) * blksize; - if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) { - LOGE("unexpected range in block map: %zu %zu\n", start, end); - success = false; - break; - } - - void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize); - if (addr == MAP_FAILED) { - LOGE("failed to map block %d: %s\n", i, strerror(errno)); - success = false; - break; - } - pMap->ranges[i].addr = addr; - pMap->ranges[i].length = length; - - next += length; - remaining_size -= length; - } - if (success && remaining_size != 0) { - LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size); - success = false; - } - if (!success) { - close(fd); - munmap(reserve, blocks * blksize); - free(pMap->ranges); - return -1; - } - - close(fd); - pMap->addr = reserve; - pMap->length = size; - - LOGI("mmapped %d ranges\n", range_count); - - return 0; -} - -int sysMapFile(const char* fn, MemMapping* pMap) -{ - memset(pMap, 0, sizeof(*pMap)); - - if (fn && fn[0] == '@') { - // A map of blocks - FILE* mapf = fopen(fn+1, "r"); - if (mapf == NULL) { - LOGE("Unable to open '%s': %s\n", fn+1, strerror(errno)); - return -1; - } - - if (sysMapBlockFile(mapf, pMap) != 0) { - LOGE("Map of '%s' failed\n", fn); - fclose(mapf); - return -1; - } - - fclose(mapf); - } else { - // This is a regular file. - int fd = open(fn, O_RDONLY); - if (fd == -1) { - LOGE("Unable to open '%s': %s\n", fn, strerror(errno)); - return -1; - } - - if (!sysMapFD(fd, pMap)) { - LOGE("Map of '%s' failed\n", fn); - close(fd); - return -1; - } - - close(fd); - } - return 0; -} - -/* - * Release a memory mapping. - */ -void sysReleaseMap(MemMapping* pMap) -{ - int i; - for (i = 0; i < pMap->range_count; ++i) { - if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) { - LOGE("munmap(%p, %d) failed: %s\n", - pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno)); - } - } - free(pMap->ranges); - pMap->ranges = NULL; - pMap->range_count = 0; -} diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h deleted file mode 100644 index 7adff1e54..000000000 --- a/minzip/SysUtil.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * System utilities. - */ -#ifndef _MINZIP_SYSUTIL -#define _MINZIP_SYSUTIL - -#include <stdio.h> -#include <sys/types.h> - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct MappedRange { - void* addr; - size_t length; -} MappedRange; - -/* - * Use this to keep track of mapped segments. - */ -typedef struct MemMapping { - unsigned char* addr; /* start of data */ - size_t length; /* length of data */ - - int range_count; - MappedRange* ranges; -} MemMapping; - -/* - * Map a file into a private, read-only memory segment. If 'fn' - * begins with an '@' character, it is a map of blocks to be mapped, - * otherwise it is treated as an ordinary file. - * - * On success, "pMap" is filled in, and zero is returned. - */ -int sysMapFile(const char* fn, MemMapping* pMap); - -/* - * Release the pages associated with a shared memory segment. - * - * This does not free "pMap"; it just releases the memory. - */ -void sysReleaseMap(MemMapping* pMap); - -#ifdef __cplusplus -} -#endif - -#endif /*_MINZIP_SYSUTIL*/ diff --git a/minzip/Zip.c b/minzip/Zip.c deleted file mode 100644 index bdb565c64..000000000 --- a/minzip/Zip.c +++ /dev/null @@ -1,1016 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * Simple Zip file support. - */ -#include "safe_iop.h" -#include "zlib.h" - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stdint.h> // for uintptr_t -#include <stdlib.h> -#include <sys/stat.h> // for S_ISLNK() -#include <unistd.h> - -#define LOG_TAG "minzip" -#include "Zip.h" -#include "Bits.h" -#include "Log.h" -#include "DirUtil.h" - -#undef NDEBUG // do this after including Log.h -#include <assert.h> - -#define SORT_ENTRIES 1 - -/* - * Offset and length constants (java.util.zip naming convention). - */ -enum { - CENSIG = 0x02014b50, // PK12 - CENHDR = 46, - - CENVEM = 4, - CENVER = 6, - CENFLG = 8, - CENHOW = 10, - CENTIM = 12, - CENCRC = 16, - CENSIZ = 20, - CENLEN = 24, - CENNAM = 28, - CENEXT = 30, - CENCOM = 32, - CENDSK = 34, - CENATT = 36, - CENATX = 38, - CENOFF = 42, - - ENDSIG = 0x06054b50, // PK56 - ENDHDR = 22, - - ENDSUB = 8, - ENDTOT = 10, - ENDSIZ = 12, - ENDOFF = 16, - ENDCOM = 20, - - EXTSIG = 0x08074b50, // PK78 - EXTHDR = 16, - - EXTCRC = 4, - EXTSIZ = 8, - EXTLEN = 12, - - LOCSIG = 0x04034b50, // PK34 - LOCHDR = 30, - - LOCVER = 4, - LOCFLG = 6, - LOCHOW = 8, - LOCTIM = 10, - LOCCRC = 14, - LOCSIZ = 18, - LOCLEN = 22, - LOCNAM = 26, - LOCEXT = 28, - - STORED = 0, - DEFLATED = 8, - - CENVEM_UNIX = 3 << 8, // the high byte of CENVEM -}; - - -/* - * For debugging, dump the contents of a ZipEntry. - */ -#if 0 -static void dumpEntry(const ZipEntry* pEntry) -{ - LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName); - LOGI(" off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset, - pEntry->compLen, pEntry->uncompLen, pEntry->compression); -} -#endif - -/* - * (This is a mzHashTableLookup callback.) - * - * Compare two ZipEntry structs, by name. - */ -static int hashcmpZipEntry(const void* ventry1, const void* ventry2) -{ - const ZipEntry* entry1 = (const ZipEntry*) ventry1; - const ZipEntry* entry2 = (const ZipEntry*) ventry2; - - if (entry1->fileNameLen != entry2->fileNameLen) - return entry1->fileNameLen - entry2->fileNameLen; - return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen); -} - -/* - * (This is a mzHashTableLookup callback.) - * - * find a ZipEntry struct by name. - */ -static int hashcmpZipName(const void* ventry, const void* vname) -{ - const ZipEntry* entry = (const ZipEntry*) ventry; - const char* name = (const char*) vname; - unsigned int nameLen = strlen(name); - - if (entry->fileNameLen != nameLen) - return entry->fileNameLen - nameLen; - return memcmp(entry->fileName, name, nameLen); -} - -/* - * Compute the hash code for a ZipEntry filename. - * - * Not expected to be compatible with any other hash function, so we init - * to 2 to ensure it doesn't happen to match. - */ -static unsigned int computeHash(const char* name, int nameLen) -{ - unsigned int hash = 2; - - while (nameLen--) - hash = hash * 31 + *name++; - - return hash; -} - -static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry) -{ - unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen); - const ZipEntry* found; - - found = (const ZipEntry*)mzHashTableLookup(pHash, - itemHash, pEntry, hashcmpZipEntry, true); - if (found != pEntry) { - LOGW("WARNING: duplicate entry '%.*s' in Zip\n", - found->fileNameLen, found->fileName); - /* keep going */ - } -} - -static int validFilename(const char *fileName, unsigned int fileNameLen) -{ - // Forbid super long filenames. - if (fileNameLen >= PATH_MAX) { - LOGW("Filename too long (%d chatacters)\n", fileNameLen); - return 0; - } - - // Require all characters to be printable ASCII (no NUL, no UTF-8, etc). - unsigned int i; - for (i = 0; i < fileNameLen; ++i) { - if (fileName[i] < 32 || fileName[i] >= 127) { - LOGW("Filename contains invalid character '\%03o'\n", fileName[i]); - return 0; - } - } - - return 1; -} - -/* - * Parse the contents of a Zip archive. After confirming that the file - * is in fact a Zip, we scan out the contents of the central directory and - * store it in a hash table. - * - * Returns "true" on success. - */ -static bool parseZipArchive(ZipArchive* pArchive) -{ - bool result = false; - const unsigned char* ptr; - unsigned int i, numEntries, cdOffset; - unsigned int val; - - /* - * The first 4 bytes of the file will either be the local header - * signature for the first file (LOCSIG) or, if the archive doesn't - * have any files in it, the end-of-central-directory signature (ENDSIG). - */ - val = get4LE(pArchive->addr); - if (val == ENDSIG) { - LOGW("Found Zip archive, but it looks empty\n"); - goto bail; - } else if (val != LOCSIG) { - LOGW("Not a Zip archive (found 0x%08x)\n", val); - goto bail; - } - - /* - * Find the EOCD. We'll find it immediately unless they have a file - * comment. - */ - ptr = pArchive->addr + pArchive->length - ENDHDR; - - while (ptr >= (const unsigned char*) pArchive->addr) { - if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG) - break; - ptr--; - } - if (ptr < (const unsigned char*) pArchive->addr) { - LOGW("Could not find end-of-central-directory in Zip\n"); - goto bail; - } - - /* - * There are two interesting items in the EOCD block: the number of - * entries in the file, and the file offset of the start of the - * central directory. - */ - numEntries = get2LE(ptr + ENDSUB); - cdOffset = get4LE(ptr + ENDOFF); - - LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset); - if (numEntries == 0 || cdOffset >= pArchive->length) { - LOGW("Invalid entries=%d offset=%d (len=%zd)\n", - numEntries, cdOffset, pArchive->length); - goto bail; - } - - /* - * Create data structures to hold entries. - */ - pArchive->numEntries = numEntries; - pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry)); - pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL); - if (pArchive->pEntries == NULL || pArchive->pHash == NULL) - goto bail; - - ptr = pArchive->addr + cdOffset; - for (i = 0; i < numEntries; i++) { - ZipEntry* pEntry; - unsigned int fileNameLen, extraLen, commentLen, localHdrOffset; - const unsigned char* localHdr; - const char *fileName; - - if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) { - LOGW("Ran off the end (at %d)\n", i); - goto bail; - } - if (get4LE(ptr) != CENSIG) { - LOGW("Missed a central dir sig (at %d)\n", i); - goto bail; - } - - localHdrOffset = get4LE(ptr + CENOFF); - fileNameLen = get2LE(ptr + CENNAM); - extraLen = get2LE(ptr + CENEXT); - commentLen = get2LE(ptr + CENCOM); - fileName = (const char*)ptr + CENHDR; - if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) { - LOGW("Filename ran off the end (at %d)\n", i); - goto bail; - } - if (!validFilename(fileName, fileNameLen)) { - LOGW("Invalid filename (at %d)\n", i); - goto bail; - } - -#if SORT_ENTRIES - /* Figure out where this entry should go (binary search). - */ - if (i > 0) { - int low, high; - - low = 0; - high = i - 1; - while (low <= high) { - int mid; - int diff; - int diffLen; - - mid = low + ((high - low) / 2); // avoid overflow - - if (pArchive->pEntries[mid].fileNameLen < fileNameLen) { - diffLen = pArchive->pEntries[mid].fileNameLen; - } else { - diffLen = fileNameLen; - } - diff = strncmp(pArchive->pEntries[mid].fileName, fileName, - diffLen); - if (diff == 0) { - diff = pArchive->pEntries[mid].fileNameLen - fileNameLen; - } - if (diff < 0) { - low = mid + 1; - } else if (diff > 0) { - high = mid - 1; - } else { - high = mid; - break; - } - } - - unsigned int target = high + 1; - assert(target <= i); - if (target != i) { - /* It belongs somewhere other than at the end of - * the list. Make some room at [target]. - */ - memmove(pArchive->pEntries + target + 1, - pArchive->pEntries + target, - (i - target) * sizeof(ZipEntry)); - } - pEntry = &pArchive->pEntries[target]; - } else { - pEntry = &pArchive->pEntries[0]; - } -#else - pEntry = &pArchive->pEntries[i]; -#endif - pEntry->fileNameLen = fileNameLen; - pEntry->fileName = fileName; - - pEntry->compLen = get4LE(ptr + CENSIZ); - pEntry->uncompLen = get4LE(ptr + CENLEN); - pEntry->compression = get2LE(ptr + CENHOW); - pEntry->modTime = get4LE(ptr + CENTIM); - pEntry->crc32 = get4LE(ptr + CENCRC); - - /* These two are necessary for finding the mode of the file. - */ - pEntry->versionMadeBy = get2LE(ptr + CENVEM); - if ((pEntry->versionMadeBy & 0xff00) != 0 && - (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX) - { - LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n", - pEntry->versionMadeBy >> 8, i); - goto bail; - } - pEntry->externalFileAttributes = get4LE(ptr + CENATX); - - // Perform pArchive->addr + localHdrOffset, ensuring that it won't - // overflow. This is needed because localHdrOffset is untrusted. - if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr, - (uintptr_t)localHdrOffset)) { - LOGW("Integer overflow adding in parseZipArchive\n"); - goto bail; - } - if ((uintptr_t)localHdr + LOCHDR > - (uintptr_t)pArchive->addr + pArchive->length) { - LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i); - goto bail; - } - if (get4LE(localHdr) != LOCSIG) { - LOGW("Missed a local header sig (at %d)\n", i); - goto bail; - } - pEntry->offset = localHdrOffset + LOCHDR - + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT); - if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) { - LOGW("Integer overflow adding in parseZipArchive\n"); - goto bail; - } - if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) { - LOGW("Data ran off the end (at %d)\n", i); - goto bail; - } - -#if !SORT_ENTRIES - /* Add to hash table; no need to lock here. - * Can't do this now if we're sorting, because entries - * will move around. - */ - addEntryToHashTable(pArchive->pHash, pEntry); -#endif - - //dumpEntry(pEntry); - ptr += CENHDR + fileNameLen + extraLen + commentLen; - } - -#if SORT_ENTRIES - /* If we're sorting, we have to wait until all entries - * are in their final places, otherwise the pointers will - * probably point to the wrong things. - */ - for (i = 0; i < numEntries; i++) { - /* Add to hash table; no need to lock here. - */ - addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]); - } -#endif - - result = true; - -bail: - if (!result) { - mzHashTableFree(pArchive->pHash); - pArchive->pHash = NULL; - } - return result; -} - -/* - * Open a Zip archive and scan out the contents. - * - * The easiest way to do this is to mmap() the whole thing and do the - * traditional backward scan for central directory. Since the EOCD is - * a relatively small bit at the end, we should end up only touching a - * small set of pages. - * - * This will be called on non-Zip files, especially during startup, so - * we don't want to be too noisy about failures. (Do we want a "quiet" - * flag?) - * - * On success, we fill out the contents of "pArchive". - */ -int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive) -{ - int err; - - if (length < ENDHDR) { - err = -1; - LOGW("Archive %p is too small to be zip (%zd)\n", pArchive, length); - goto bail; - } - - pArchive->addr = addr; - pArchive->length = length; - - if (!parseZipArchive(pArchive)) { - err = -1; - LOGW("Parsing archive %p failed\n", pArchive); - goto bail; - } - - err = 0; - -bail: - if (err != 0) - mzCloseZipArchive(pArchive); - return err; -} - -/* - * Close a ZipArchive, closing the file and freeing the contents. - * - * NOTE: the ZipArchive may not have been fully created. - */ -void mzCloseZipArchive(ZipArchive* pArchive) -{ - LOGV("Closing archive %p\n", pArchive); - - free(pArchive->pEntries); - - mzHashTableFree(pArchive->pHash); - - pArchive->pHash = NULL; - pArchive->pEntries = NULL; -} - -/* - * Find a matching entry. - * - * Returns NULL if no matching entry found. - */ -const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, - const char* entryName) -{ - unsigned int itemHash = computeHash(entryName, strlen(entryName)); - - return (const ZipEntry*)mzHashTableLookup(pArchive->pHash, - itemHash, (char*) entryName, hashcmpZipName, false); -} - -/* - * Return true if the entry is a symbolic link. - */ -static bool mzIsZipEntrySymlink(const ZipEntry* pEntry) -{ - if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) { - return S_ISLNK(pEntry->externalFileAttributes >> 16); - } - return false; -} - -/* Call processFunction on the uncompressed data of a STORED entry. - */ -static bool processStoredEntry(const ZipArchive *pArchive, - const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, - void *cookie) -{ - return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie); -} - -static bool processDeflatedEntry(const ZipArchive *pArchive, - const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, - void *cookie) -{ - long result = -1; - unsigned char procBuf[32 * 1024]; - z_stream zstream; - int zerr; - long compRemaining; - - compRemaining = pEntry->compLen; - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = pArchive->addr + pEntry->offset; - zstream.avail_in = pEntry->compLen; - zstream.next_out = (Bytef*) procBuf; - zstream.avail_out = sizeof(procBuf); - zstream.data_type = Z_UNKNOWN; - - /* - * Use the undocumented "negative window bits" feature to tell zlib - * that there's no zlib header waiting for it. - */ - zerr = inflateInit2(&zstream, -MAX_WBITS); - if (zerr != Z_OK) { - if (zerr == Z_VERSION_ERROR) { - LOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; - } - - /* - * Loop while we have data. - */ - do { - /* uncompress the data */ - zerr = inflate(&zstream, Z_NO_FLUSH); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - LOGW("zlib inflate call failed (zerr=%d)\n", zerr); - goto z_bail; - } - - /* write when we're full or when we're done */ - if (zstream.avail_out == 0 || - (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf))) - { - long procSize = zstream.next_out - procBuf; - LOGVV("+++ processing %d bytes\n", (int) procSize); - bool ret = processFunction(procBuf, procSize, cookie); - if (!ret) { - LOGW("Process function elected to fail (in inflate)\n"); - goto z_bail; - } - - zstream.next_out = procBuf; - zstream.avail_out = sizeof(procBuf); - } - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - // success! - result = zstream.total_out; - -z_bail: - inflateEnd(&zstream); /* free up any allocated structures */ - -bail: - if (result != pEntry->uncompLen) { - if (result != -1) // error already shown? - LOGW("Size mismatch on inflated file (%ld vs %ld)\n", - result, pEntry->uncompLen); - return false; - } - return true; -} - -/* - * Stream the uncompressed data through the supplied function, - * passing cookie to it each time it gets called. processFunction - * may be called more than once. - * - * If processFunction returns false, the operation is abandoned and - * mzProcessZipEntryContents() immediately returns false. - * - * This is useful for calculating the hash of an entry's uncompressed contents. - */ -bool mzProcessZipEntryContents(const ZipArchive *pArchive, - const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, - void *cookie) -{ - bool ret = false; - - switch (pEntry->compression) { - case STORED: - ret = processStoredEntry(pArchive, pEntry, processFunction, cookie); - break; - case DEFLATED: - ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie); - break; - default: - LOGE("Unsupported compression type %d for entry '%s'\n", - pEntry->compression, pEntry->fileName); - break; - } - - return ret; -} - -typedef struct { - char *buf; - int bufLen; -} CopyProcessArgs; - -static bool copyProcessFunction(const unsigned char *data, int dataLen, - void *cookie) -{ - CopyProcessArgs *args = (CopyProcessArgs *)cookie; - if (dataLen <= args->bufLen) { - memcpy(args->buf, data, dataLen); - args->buf += dataLen; - args->bufLen -= dataLen; - return true; - } - return false; -} - -/* - * Read an entry into a buffer allocated by the caller. - */ -bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, - char *buf, int bufLen) -{ - CopyProcessArgs args; - bool ret; - - args.buf = buf; - args.bufLen = bufLen; - ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction, - (void *)&args); - if (!ret) { - LOGE("Can't extract entry to buffer.\n"); - return false; - } - return true; -} - -static bool writeProcessFunction(const unsigned char *data, int dataLen, - void *cookie) -{ - int fd = (int)(intptr_t)cookie; - if (dataLen == 0) { - return true; - } - ssize_t soFar = 0; - while (true) { - ssize_t n = TEMP_FAILURE_RETRY(write(fd, data+soFar, dataLen-soFar)); - if (n <= 0) { - LOGE("Error writing %zd bytes from zip file from %p: %s\n", - dataLen-soFar, data+soFar, strerror(errno)); - return false; - } else if (n > 0) { - soFar += n; - if (soFar == dataLen) return true; - if (soFar > dataLen) { - LOGE("write overrun? (%zd bytes instead of %d)\n", - soFar, dataLen); - return false; - } - } - } -} - -/* - * Uncompress "pEntry" in "pArchive" to "fd" at the current offset. - */ -bool mzExtractZipEntryToFile(const ZipArchive *pArchive, - const ZipEntry *pEntry, int fd) -{ - bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction, - (void*)(intptr_t)fd); - if (!ret) { - LOGE("Can't extract entry to file.\n"); - return false; - } - return true; -} - -typedef struct { - unsigned char* buffer; - long len; -} BufferExtractCookie; - -static bool bufferProcessFunction(const unsigned char *data, int dataLen, - void *cookie) { - BufferExtractCookie *bec = (BufferExtractCookie*)cookie; - - memmove(bec->buffer, data, dataLen); - bec->buffer += dataLen; - bec->len -= dataLen; - - return true; -} - -/* - * Uncompress "pEntry" in "pArchive" to buffer, which must be large - * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. - */ -bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, - const ZipEntry *pEntry, unsigned char *buffer) -{ - BufferExtractCookie bec; - bec.buffer = buffer; - bec.len = mzGetZipEntryUncompLen(pEntry); - - bool ret = mzProcessZipEntryContents(pArchive, pEntry, - bufferProcessFunction, (void*)&bec); - if (!ret || bec.len != 0) { - LOGE("Can't extract entry to memory buffer.\n"); - return false; - } - return true; -} - - -/* Helper state to make path translation easier and less malloc-happy. - */ -typedef struct { - const char *targetDir; - const char *zipDir; - char *buf; - int targetDirLen; - int zipDirLen; - int bufLen; -} MzPathHelper; - -/* Given the values of targetDir and zipDir in the helper, - * return the target filename of the provided entry. - * The helper must be initialized first. - */ -static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry) -{ - int needLen; - bool firstTime = (helper->buf == NULL); - - /* target file <-- targetDir + / + entry[zipDirLen:] - */ - needLen = helper->targetDirLen + 1 + - pEntry->fileNameLen - helper->zipDirLen + 1; - if (needLen > helper->bufLen) { - char *newBuf; - - needLen *= 2; - newBuf = (char *)realloc(helper->buf, needLen); - if (newBuf == NULL) { - return NULL; - } - helper->buf = newBuf; - helper->bufLen = needLen; - } - - /* Every path will start with the target path and a slash. - */ - if (firstTime) { - char *p = helper->buf; - memcpy(p, helper->targetDir, helper->targetDirLen); - p += helper->targetDirLen; - if (p == helper->buf || p[-1] != '/') { - helper->targetDirLen += 1; - *p++ = '/'; - } - } - - /* Replace the custom part of the path with the appropriate - * part of the entry's path. - */ - char *epath = helper->buf + helper->targetDirLen; - memcpy(epath, pEntry->fileName + helper->zipDirLen, - pEntry->fileNameLen - helper->zipDirLen); - epath += pEntry->fileNameLen - helper->zipDirLen; - *epath = '\0'; - - return helper->buf; -} - -/* - * Inflate all entries under zipDir to the directory specified by - * targetDir, which must exist and be a writable directory. - * - * The immediate children of zipDir will become the immediate - * children of targetDir; e.g., if the archive contains the entries - * - * a/b/c/one - * a/b/c/two - * a/b/c/d/three - * - * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting - * files will be - * - * /tmp/one - * /tmp/two - * /tmp/d/three - * - * Returns true on success, false on failure. - */ -bool mzExtractRecursive(const ZipArchive *pArchive, - const char *zipDir, const char *targetDir, - const struct utimbuf *timestamp, - void (*callback)(const char *fn, void *), void *cookie, - struct selabel_handle *sehnd) -{ - if (zipDir[0] == '/') { - LOGE("mzExtractRecursive(): zipDir must be a relative path.\n"); - return false; - } - if (targetDir[0] != '/') { - LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n"); - return false; - } - - unsigned int zipDirLen; - char *zpath; - - zipDirLen = strlen(zipDir); - zpath = (char *)malloc(zipDirLen + 2); - if (zpath == NULL) { - LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2); - return false; - } - /* If zipDir is empty, we'll extract the entire zip file. - * Otherwise, canonicalize the path. - */ - if (zipDirLen > 0) { - /* Make sure there's (hopefully, exactly one) slash at the - * end of the path. This way we don't need to worry about - * accidentally extracting "one/twothree" when a path like - * "one/two" is specified. - */ - memcpy(zpath, zipDir, zipDirLen); - if (zpath[zipDirLen-1] != '/') { - zpath[zipDirLen++] = '/'; - } - } - zpath[zipDirLen] = '\0'; - - /* Set up the helper structure that we'll use to assemble paths. - */ - MzPathHelper helper; - helper.targetDir = targetDir; - helper.targetDirLen = strlen(helper.targetDir); - helper.zipDir = zpath; - helper.zipDirLen = strlen(helper.zipDir); - helper.buf = NULL; - helper.bufLen = 0; - - /* Walk through the entries and extract anything whose path begins - * with zpath. - //TODO: since the entries are sorted, binary search for the first match - // and stop after the first non-match. - */ - unsigned int i; - bool seenMatch = false; - int ok = true; - int extractCount = 0; - for (i = 0; i < pArchive->numEntries; i++) { - ZipEntry *pEntry = pArchive->pEntries + i; - if (pEntry->fileNameLen < zipDirLen) { - //TODO: look out for a single empty directory entry that matches zpath, but - // missing the trailing slash. Most zip files seem to include - // the trailing slash, but I think it's legal to leave it off. - // e.g., zpath "a/b/", entry "a/b", with no children of the entry. - /* No chance of matching. - */ -#if SORT_ENTRIES - if (seenMatch) { - /* Since the entries are sorted, we can give up - * on the first mismatch after the first match. - */ - break; - } -#endif - continue; - } - /* If zpath is empty, this strncmp() will match everything, - * which is what we want. - */ - if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) { -#if SORT_ENTRIES - if (seenMatch) { - /* Since the entries are sorted, we can give up - * on the first mismatch after the first match. - */ - break; - } -#endif - continue; - } - /* This entry begins with zipDir, so we'll extract it. - */ - seenMatch = true; - - /* Find the target location of the entry. - */ - const char *targetFile = targetEntryPath(&helper, pEntry); - if (targetFile == NULL) { - LOGE("Can't assemble target path for \"%.*s\"\n", - pEntry->fileNameLen, pEntry->fileName); - ok = false; - break; - } - -#define UNZIP_DIRMODE 0755 -#define UNZIP_FILEMODE 0644 - /* - * Create the file or directory. We ignore directory entries - * because we recursively create paths to each file entry we encounter - * in the zip archive anyway. - * - * NOTE: A "directory entry" in a zip archive is just a zero length - * entry that ends in a "/". They're not mandatory and many tools get - * rid of them. We need to process them only if we want to preserve - * empty directories from the archive. - */ - if (pEntry->fileName[pEntry->fileNameLen-1] != '/') { - /* This is not a directory. First, make sure that - * the containing directory exists. - */ - int ret = dirCreateHierarchy( - targetFile, UNZIP_DIRMODE, timestamp, true, sehnd); - if (ret != 0) { - LOGE("Can't create containing directory for \"%s\": %s\n", - targetFile, strerror(errno)); - ok = false; - break; - } - - /* - * The entry is a regular file or a symlink. Open the target for writing. - * - * TODO: This behavior for symlinks seems rather bizarre. For a - * symlink foo/bar/baz -> foo/tar/taz, we will create a file called - * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We - * warn about this for now and preserve older behavior. - */ - if (mzIsZipEntrySymlink(pEntry)) { - LOGE("Symlink entry \"%.*s\" will be output as a regular file.", - pEntry->fileNameLen, pEntry->fileName); - } - - char *secontext = NULL; - - if (sehnd) { - selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE); - setfscreatecon(secontext); - } - - int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC, - UNZIP_FILEMODE); - - if (secontext) { - freecon(secontext); - setfscreatecon(NULL); - } - - if (fd < 0) { - LOGE("Can't create target file \"%s\": %s\n", - targetFile, strerror(errno)); - ok = false; - break; - } - - bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd); - if (ok) { - ok = (fsync(fd) == 0); - } - if (close(fd) != 0) { - ok = false; - } - if (!ok) { - LOGE("Error extracting \"%s\"\n", targetFile); - ok = false; - break; - } - - if (timestamp != NULL && utime(targetFile, timestamp)) { - LOGE("Error touching \"%s\"\n", targetFile); - ok = false; - break; - } - - LOGV("Extracted file \"%s\"\n", targetFile); - ++extractCount; - } - - if (callback != NULL) callback(targetFile, cookie); - } - - LOGV("Extracted %d file(s)\n", extractCount); - - free(helper.buf); - free(zpath); - - return ok; -} diff --git a/minzip/Zip.h b/minzip/Zip.h deleted file mode 100644 index 86d8db597..000000000 --- a/minzip/Zip.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2006 The Android Open Source Project - * - * Simple Zip archive support. - */ -#ifndef _MINZIP_ZIP -#define _MINZIP_ZIP - -#include "inline_magic.h" - -#include <stdlib.h> -#include <utime.h> - -#include "Hash.h" -#include "SysUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#include <selinux/selinux.h> -#include <selinux/label.h> - -/* - * One entry in the Zip archive. Treat this as opaque -- use accessors below. - * - * TODO: we're now keeping the pages mapped so we don't have to copy the - * filename. We can change the accessors to retrieve the various pieces - * directly from the source file instead of copying them out, for a very - * slight speed hit and a modest reduction in memory usage. - */ -typedef struct ZipEntry { - unsigned int fileNameLen; - const char* fileName; // not null-terminated - long offset; - long compLen; - long uncompLen; - int compression; - long modTime; - long crc32; - int versionMadeBy; - long externalFileAttributes; -} ZipEntry; - -/* - * One Zip archive. Treat as opaque. - */ -typedef struct ZipArchive { - unsigned int numEntries; - ZipEntry* pEntries; - HashTable* pHash; // maps file name to ZipEntry - unsigned char* addr; - size_t length; -} ZipArchive; - -/* - * Represents a non-NUL-terminated string, - * which is how entry names are stored. - */ -typedef struct { - const char *str; - size_t len; -} UnterminatedString; - -/* - * Open a Zip archive. - * - * On success, returns 0 and populates "pArchive". Returns nonzero errno - * value on failure. - */ -int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive); - -/* - * Close archive, releasing resources associated with it. - * - * Depending on the implementation this could unmap pages used by classes - * stored in a Jar. This should only be done after unloading classes. - */ -void mzCloseZipArchive(ZipArchive* pArchive); - - -/* - * Find an entry in the Zip archive, by name. - */ -const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, - const char* entryName); - -INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) { - return pEntry->offset; -} -INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) { - return pEntry->uncompLen; -} - -/* - * Type definition for the callback function used by - * mzProcessZipEntryContents(). - */ -typedef bool (*ProcessZipEntryContentsFunction)(const unsigned char *data, - int dataLen, void *cookie); - -/* - * Stream the uncompressed data through the supplied function, - * passing cookie to it each time it gets called. processFunction - * may be called more than once. - * - * If processFunction returns false, the operation is abandoned and - * mzProcessZipEntryContents() immediately returns false. - * - * This is useful for calculating the hash of an entry's uncompressed contents. - */ -bool mzProcessZipEntryContents(const ZipArchive *pArchive, - const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, - void *cookie); - -/* - * Read an entry into a buffer allocated by the caller. - */ -bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, - char* buf, int bufLen); - -/* - * Inflate and write an entry to a file. - */ -bool mzExtractZipEntryToFile(const ZipArchive *pArchive, - const ZipEntry *pEntry, int fd); - -/* - * Inflate and write an entry to a memory buffer, which must be long - * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. - */ -bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, - const ZipEntry *pEntry, unsigned char* buffer); - -/* - * Inflate all files under zipDir to the directory specified by - * targetDir, which must exist and be a writable directory. - * - * Directory entries and symlinks are not extracted. - * - * - * The immediate children of zipDir will become the immediate - * children of targetDir; e.g., if the archive contains the entries - * - * a/b/c/one - * a/b/c/two - * a/b/c/d/three - * - * and mzExtractRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting - * files will be - * - * /tmp/one - * /tmp/two - * /tmp/d/three - * - * If timestamp is non-NULL, file timestamps will be set accordingly. - * - * If callback is non-NULL, it will be invoked with each unpacked file. - * - * Returns true on success, false on failure. - */ -bool mzExtractRecursive(const ZipArchive *pArchive, - const char *zipDir, const char *targetDir, - const struct utimbuf *timestamp, - void (*callback)(const char *fn, void*), void *cookie, - struct selabel_handle *sehnd); - -#ifdef __cplusplus -} -#endif - -#endif /*_MINZIP_ZIP*/ diff --git a/mounts.cpp b/mounts.cpp new file mode 100644 index 000000000..f23376b06 --- /dev/null +++ b/mounts.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 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 "mounts.h" + +#include <errno.h> +#include <fcntl.h> +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> + +#include <string> +#include <vector> + +struct MountedVolume { + std::string device; + std::string mount_point; + std::string filesystem; + std::string flags; +}; + +std::vector<MountedVolume*> g_mounts_state; + +bool scan_mounted_volumes() { + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + delete g_mounts_state[i]; + } + g_mounts_state.clear(); + + // Open and read mount table entries. + FILE* fp = setmntent("/proc/mounts", "re"); + if (fp == NULL) { + return false; + } + mntent* e; + while ((e = getmntent(fp)) != NULL) { + MountedVolume* v = new MountedVolume; + v->device = e->mnt_fsname; + v->mount_point = e->mnt_dir; + v->filesystem = e->mnt_type; + v->flags = e->mnt_opts; + g_mounts_state.push_back(v); + } + endmntent(fp); + return true; +} + +MountedVolume* find_mounted_volume_by_device(const char* device) { + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + if (g_mounts_state[i]->device == device) return g_mounts_state[i]; + } + return nullptr; +} + +MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { + for (size_t i = 0; i < g_mounts_state.size(); ++i) { + if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i]; + } + return nullptr; +} + +int unmount_mounted_volume(MountedVolume* volume) { + // Intentionally pass the empty string to umount if the caller tries + // to unmount a volume they already unmounted using this + // function. + std::string mount_point = volume->mount_point; + volume->mount_point.clear(); + return umount(mount_point.c_str()); +} + +int remount_read_only(MountedVolume* volume) { + return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(), + MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); +} diff --git a/minzip/Inlines.c b/mounts.h index 91f87751d..1b7670329 100644 --- a/minzip/Inlines.c +++ b/mounts.h @@ -14,12 +14,19 @@ * limitations under the License. */ -/* Make sure that non-inlined versions of INLINED-marked functions - * exist so that debug builds (which don't generally do inlining) - * don't break. - */ -#define MINZIP_GENERATE_INLINES 1 -#include "Bits.h" -#include "Hash.h" -#include "SysUtil.h" -#include "Zip.h" +#ifndef MOUNTS_H_ +#define MOUNTS_H_ + +struct MountedVolume; + +bool scan_mounted_volumes(); + +MountedVolume* find_mounted_volume_by_device(const char* device); + +MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point); + +int unmount_mounted_volume(MountedVolume* volume); + +int remount_read_only(MountedVolume* volume); + +#endif diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk deleted file mode 100644 index b7d35c27a..000000000 --- a/mtdutils/Android.mk +++ /dev/null @@ -1,20 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - mtdutils.c \ - mounts.c - -LOCAL_MODULE := libmtdutils -LOCAL_CLANG := true - -include $(BUILD_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_CLANG := true -LOCAL_SRC_FILES := flash_image.c -LOCAL_MODULE := flash_image -LOCAL_MODULE_TAGS := eng -LOCAL_STATIC_LIBRARIES := libmtdutils -LOCAL_SHARED_LIBRARIES := libcutils liblog libc -include $(BUILD_EXECUTABLE) diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c deleted file mode 100644 index 36ffa1314..000000000 --- a/mtdutils/flash_image.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2008 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 <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "cutils/log.h" -#include "mtdutils.h" - -#ifdef LOG_TAG -#undef LOG_TAG -#endif -#define LOG_TAG "flash_image" - -#define HEADER_SIZE 2048 // size of header to compare for equality - -void die(const char *msg, ...) { - int err = errno; - va_list args; - va_start(args, msg); - char buf[1024]; - vsnprintf(buf, sizeof(buf), msg, args); - va_end(args); - - if (err != 0) { - strlcat(buf, ": ", sizeof(buf)); - strlcat(buf, strerror(err), sizeof(buf)); - } - - fprintf(stderr, "%s\n", buf); - ALOGE("%s\n", buf); - exit(1); -} - -/* Read an image file and write it to a flash partition. */ - -int main(int argc, char **argv) { - const MtdPartition *ptn; - MtdWriteContext *write; - void *data; - unsigned sz; - - if (argc != 3) { - fprintf(stderr, "usage: %s partition file.img\n", argv[0]); - return 2; - } - - if (mtd_scan_partitions() <= 0) die("error scanning partitions"); - const MtdPartition *partition = mtd_find_partition_by_name(argv[1]); - if (partition == NULL) die("can't find %s partition", argv[1]); - - // If the first part of the file matches the partition, skip writing - - int fd = open(argv[2], O_RDONLY); - if (fd < 0) die("error opening %s", argv[2]); - - char header[HEADER_SIZE]; - int headerlen = TEMP_FAILURE_RETRY(read(fd, header, sizeof(header))); - if (headerlen <= 0) die("error reading %s header", argv[2]); - - MtdReadContext *in = mtd_read_partition(partition); - if (in == NULL) { - ALOGW("error opening %s: %s\n", argv[1], strerror(errno)); - // just assume it needs re-writing - } else { - char check[HEADER_SIZE]; - int checklen = mtd_read_data(in, check, sizeof(check)); - if (checklen <= 0) { - ALOGW("error reading %s: %s\n", argv[1], strerror(errno)); - // just assume it needs re-writing - } else if (checklen == headerlen && !memcmp(header, check, headerlen)) { - ALOGI("header is the same, not flashing %s\n", argv[1]); - return 0; - } - mtd_read_close(in); - } - - // Skip the header (we'll come back to it), write everything else - ALOGI("flashing %s from %s\n", argv[1], argv[2]); - - MtdWriteContext *out = mtd_write_partition(partition); - if (out == NULL) die("error writing %s", argv[1]); - - char buf[HEADER_SIZE]; - memset(buf, 0, headerlen); - int wrote = mtd_write_data(out, buf, headerlen); - if (wrote != headerlen) die("error writing %s", argv[1]); - - int len; - while ((len = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf)))) > 0) { - wrote = mtd_write_data(out, buf, len); - if (wrote != len) die("error writing %s", argv[1]); - } - if (len < 0) die("error reading %s", argv[2]); - - if (mtd_write_close(out)) die("error closing %s", argv[1]); - - // Now come back and write the header last - - out = mtd_write_partition(partition); - if (out == NULL) die("error re-opening %s", argv[1]); - - wrote = mtd_write_data(out, header, headerlen); - if (wrote != headerlen) die("error re-writing %s", argv[1]); - - // Need to write a complete block, so write the rest of the first block - size_t block_size; - if (mtd_partition_info(partition, NULL, &block_size, NULL)) - die("error getting %s block size", argv[1]); - - if (TEMP_FAILURE_RETRY(lseek(fd, headerlen, SEEK_SET)) != headerlen) - die("error rewinding %s", argv[2]); - - int left = block_size - headerlen; - while (left < 0) left += block_size; - while (left > 0) { - len = TEMP_FAILURE_RETRY(read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left)); - if (len <= 0) die("error reading %s", argv[2]); - if (mtd_write_data(out, buf, len) != len) - die("error writing %s", argv[1]); - left -= len; - } - - if (mtd_write_close(out)) die("error closing %s", argv[1]); - return 0; -} diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c deleted file mode 100644 index 6a9b03d30..000000000 --- a/mtdutils/mounts.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2007 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 <mntent.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <fcntl.h> -#include <errno.h> -#include <sys/mount.h> - -#include "mounts.h" - -struct MountedVolume { - const char *device; - const char *mount_point; - const char *filesystem; - const char *flags; -}; - -typedef struct { - MountedVolume *volumes; - int volumes_allocd; - int volume_count; -} MountsState; - -static MountsState g_mounts_state = { - NULL, // volumes - 0, // volumes_allocd - 0 // volume_count -}; - -static inline void -free_volume_internals(const MountedVolume *volume, int zero) -{ - free((char *)volume->device); - free((char *)volume->mount_point); - free((char *)volume->filesystem); - free((char *)volume->flags); - if (zero) { - memset((void *)volume, 0, sizeof(*volume)); - } -} - -#define PROC_MOUNTS_FILENAME "/proc/mounts" - -int -scan_mounted_volumes() -{ - FILE* fp; - struct mntent* mentry; - - if (g_mounts_state.volumes == NULL) { - const int numv = 32; - MountedVolume *volumes = malloc(numv * sizeof(*volumes)); - if (volumes == NULL) { - errno = ENOMEM; - return -1; - } - g_mounts_state.volumes = volumes; - g_mounts_state.volumes_allocd = numv; - memset(volumes, 0, numv * sizeof(*volumes)); - } else { - /* Free the old volume strings. - */ - int i; - for (i = 0; i < g_mounts_state.volume_count; i++) { - free_volume_internals(&g_mounts_state.volumes[i], 1); - } - } - g_mounts_state.volume_count = 0; - - /* Open and read mount table entries. */ - fp = setmntent(PROC_MOUNTS_FILENAME, "r"); - if (fp == NULL) { - return -1; - } - while ((mentry = getmntent(fp)) != NULL) { - MountedVolume* v = &g_mounts_state.volumes[g_mounts_state.volume_count++]; - v->device = strdup(mentry->mnt_fsname); - v->mount_point = strdup(mentry->mnt_dir); - v->filesystem = strdup(mentry->mnt_type); - v->flags = strdup(mentry->mnt_opts); - } - endmntent(fp); - return 0; -} - -const MountedVolume * -find_mounted_volume_by_device(const char *device) -{ - if (g_mounts_state.volumes != NULL) { - int i; - for (i = 0; i < g_mounts_state.volume_count; i++) { - MountedVolume *v = &g_mounts_state.volumes[i]; - /* May be null if it was unmounted and we haven't rescanned. - */ - if (v->device != NULL) { - if (strcmp(v->device, device) == 0) { - return v; - } - } - } - } - return NULL; -} - -const MountedVolume * -find_mounted_volume_by_mount_point(const char *mount_point) -{ - if (g_mounts_state.volumes != NULL) { - int i; - for (i = 0; i < g_mounts_state.volume_count; i++) { - MountedVolume *v = &g_mounts_state.volumes[i]; - /* May be null if it was unmounted and we haven't rescanned. - */ - if (v->mount_point != NULL) { - if (strcmp(v->mount_point, mount_point) == 0) { - return v; - } - } - } - } - return NULL; -} - -int -unmount_mounted_volume(const MountedVolume *volume) -{ - /* Intentionally pass NULL to umount if the caller tries - * to unmount a volume they already unmounted using this - * function. - */ - int ret = umount(volume->mount_point); - if (ret == 0) { - free_volume_internals(volume, 1); - return 0; - } - return ret; -} - -int -remount_read_only(const MountedVolume* volume) -{ - return mount(volume->device, volume->mount_point, volume->filesystem, - MS_NOATIME | MS_NODEV | MS_NODIRATIME | - MS_RDONLY | MS_REMOUNT, 0); -} diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h deleted file mode 100644 index d721355b8..000000000 --- a/mtdutils/mounts.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 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 MTDUTILS_MOUNTS_H_ -#define MTDUTILS_MOUNTS_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct MountedVolume MountedVolume; - -int scan_mounted_volumes(void); - -const MountedVolume *find_mounted_volume_by_device(const char *device); - -const MountedVolume * -find_mounted_volume_by_mount_point(const char *mount_point); - -int unmount_mounted_volume(const MountedVolume *volume); - -int remount_read_only(const MountedVolume* volume); - -#ifdef __cplusplus -} -#endif - -#endif // MTDUTILS_MOUNTS_H_ diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c deleted file mode 100644 index cd4f52cd5..000000000 --- a/mtdutils/mtdutils.c +++ /dev/null @@ -1,561 +0,0 @@ -/* - * Copyright (C) 2007 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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> -#include <sys/mount.h> // for _IOW, _IOR, mount() -#include <sys/stat.h> -#include <mtd/mtd-user.h> -#undef NDEBUG -#include <assert.h> - -#include "mtdutils.h" - -struct MtdPartition { - int device_index; - unsigned int size; - unsigned int erase_size; - char *name; -}; - -struct MtdReadContext { - const MtdPartition *partition; - char *buffer; - size_t consumed; - int fd; -}; - -struct MtdWriteContext { - const MtdPartition *partition; - char *buffer; - size_t stored; - int fd; - - off_t* bad_block_offsets; - int bad_block_alloc; - int bad_block_count; -}; - -typedef struct { - MtdPartition *partitions; - int partitions_allocd; - int partition_count; -} MtdState; - -static MtdState g_mtd_state = { - NULL, // partitions - 0, // partitions_allocd - -1 // partition_count -}; - -#define MTD_PROC_FILENAME "/proc/mtd" - -int -mtd_scan_partitions() -{ - char buf[2048]; - const char *bufp; - int fd; - int i; - ssize_t nbytes; - - if (g_mtd_state.partitions == NULL) { - const int nump = 32; - MtdPartition *partitions = malloc(nump * sizeof(*partitions)); - if (partitions == NULL) { - errno = ENOMEM; - return -1; - } - g_mtd_state.partitions = partitions; - g_mtd_state.partitions_allocd = nump; - memset(partitions, 0, nump * sizeof(*partitions)); - } - g_mtd_state.partition_count = 0; - - /* Initialize all of the entries to make things easier later. - * (Lets us handle sparsely-numbered partitions, which - * may not even be possible.) - */ - for (i = 0; i < g_mtd_state.partitions_allocd; i++) { - MtdPartition *p = &g_mtd_state.partitions[i]; - if (p->name != NULL) { - free(p->name); - p->name = NULL; - } - p->device_index = -1; - } - - /* Open and read the file contents. - */ - fd = open(MTD_PROC_FILENAME, O_RDONLY); - if (fd < 0) { - goto bail; - } - nbytes = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf) - 1)); - close(fd); - if (nbytes < 0) { - goto bail; - } - buf[nbytes] = '\0'; - - /* Parse the contents of the file, which looks like: - * - * # cat /proc/mtd - * dev: size erasesize name - * mtd0: 00080000 00020000 "bootloader" - * mtd1: 00400000 00020000 "mfg_and_gsm" - * mtd2: 00400000 00020000 "0000000c" - * mtd3: 00200000 00020000 "0000000d" - * mtd4: 04000000 00020000 "system" - * mtd5: 03280000 00020000 "userdata" - */ - bufp = buf; - while (nbytes > 0) { - int mtdnum, mtdsize, mtderasesize; - int matches; - char mtdname[64]; - mtdname[0] = '\0'; - mtdnum = -1; - - matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]", - &mtdnum, &mtdsize, &mtderasesize, mtdname); - /* This will fail on the first line, which just contains - * column headers. - */ - if (matches == 4) { - MtdPartition *p = &g_mtd_state.partitions[mtdnum]; - p->device_index = mtdnum; - p->size = mtdsize; - p->erase_size = mtderasesize; - p->name = strdup(mtdname); - if (p->name == NULL) { - errno = ENOMEM; - goto bail; - } - g_mtd_state.partition_count++; - } - - /* Eat the line. - */ - while (nbytes > 0 && *bufp != '\n') { - bufp++; - nbytes--; - } - if (nbytes > 0) { - bufp++; - nbytes--; - } - } - - return g_mtd_state.partition_count; - -bail: - // keep "partitions" around so we can free the names on a rescan. - g_mtd_state.partition_count = -1; - return -1; -} - -const MtdPartition * -mtd_find_partition_by_name(const char *name) -{ - if (g_mtd_state.partitions != NULL) { - int i; - for (i = 0; i < g_mtd_state.partitions_allocd; i++) { - MtdPartition *p = &g_mtd_state.partitions[i]; - if (p->device_index >= 0 && p->name != NULL) { - if (strcmp(p->name, name) == 0) { - return p; - } - } - } - } - return NULL; -} - -int -mtd_mount_partition(const MtdPartition *partition, const char *mount_point, - const char *filesystem, int read_only) -{ - const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME; - char devname[64]; - int rv = -1; - - sprintf(devname, "/dev/block/mtdblock%d", partition->device_index); - if (!read_only) { - rv = mount(devname, mount_point, filesystem, flags, NULL); - } - if (read_only || rv < 0) { - rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0); - if (rv < 0) { - printf("Failed to mount %s on %s: %s\n", - devname, mount_point, strerror(errno)); - } else { - printf("Mount %s on %s read-only\n", devname, mount_point); - } - } -#if 1 //TODO: figure out why this is happening; remove include of stat.h - if (rv >= 0) { - /* For some reason, the x bits sometimes aren't set on the root - * of mounted volumes. - */ - struct stat st; - rv = stat(mount_point, &st); - if (rv < 0) { - return rv; - } - mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH; - if (new_mode != st.st_mode) { -printf("Fixing execute permissions for %s\n", mount_point); - rv = chmod(mount_point, new_mode); - if (rv < 0) { - printf("Couldn't fix permissions for %s: %s\n", - mount_point, strerror(errno)); - } - } - } -#endif - return rv; -} - -int -mtd_partition_info(const MtdPartition *partition, - size_t *total_size, size_t *erase_size, size_t *write_size) -{ - char mtddevname[32]; - sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); - int fd = open(mtddevname, O_RDONLY); - if (fd < 0) return -1; - - struct mtd_info_user mtd_info; - int ret = ioctl(fd, MEMGETINFO, &mtd_info); - close(fd); - if (ret < 0) return -1; - - if (total_size != NULL) *total_size = mtd_info.size; - if (erase_size != NULL) *erase_size = mtd_info.erasesize; - if (write_size != NULL) *write_size = mtd_info.writesize; - return 0; -} - -MtdReadContext *mtd_read_partition(const MtdPartition *partition) -{ - MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext)); - if (ctx == NULL) return NULL; - - ctx->buffer = malloc(partition->erase_size); - if (ctx->buffer == NULL) { - free(ctx); - return NULL; - } - - char mtddevname[32]; - sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); - ctx->fd = open(mtddevname, O_RDONLY); - if (ctx->fd < 0) { - free(ctx->buffer); - free(ctx); - return NULL; - } - - ctx->partition = partition; - ctx->consumed = partition->erase_size; - return ctx; -} - -static int read_block(const MtdPartition *partition, int fd, char *data) -{ - struct mtd_ecc_stats before, after; - if (ioctl(fd, ECCGETSTATS, &before)) { - printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno)); - return -1; - } - - loff_t pos = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); - if (pos == -1) { - printf("mtd: read_block: couldn't SEEK_CUR: %s\n", strerror(errno)); - return -1; - } - - ssize_t size = partition->erase_size; - int mgbb; - - while (pos + size <= (int) partition->size) { - if (TEMP_FAILURE_RETRY(lseek64(fd, pos, SEEK_SET)) != pos || - TEMP_FAILURE_RETRY(read(fd, data, size)) != size) { - printf("mtd: read error at 0x%08llx (%s)\n", - (long long)pos, strerror(errno)); - } else if (ioctl(fd, ECCGETSTATS, &after)) { - printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno)); - return -1; - } else if (after.failed != before.failed) { - printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n", - after.corrected - before.corrected, - after.failed - before.failed, (long long)pos); - // copy the comparison baseline for the next read. - memcpy(&before, &after, sizeof(struct mtd_ecc_stats)); - } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) { - fprintf(stderr, - "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n", - mgbb, (long long)pos, strerror(errno)); - } else { - return 0; // Success! - } - - pos += partition->erase_size; - } - - errno = ENOSPC; - return -1; -} - -ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len) -{ - size_t read = 0; - while (read < len) { - if (ctx->consumed < ctx->partition->erase_size) { - size_t avail = ctx->partition->erase_size - ctx->consumed; - size_t copy = len - read < avail ? len - read : avail; - memcpy(data + read, ctx->buffer + ctx->consumed, copy); - ctx->consumed += copy; - read += copy; - } - - // Read complete blocks directly into the user's buffer - while (ctx->consumed == ctx->partition->erase_size && - len - read >= ctx->partition->erase_size) { - if (read_block(ctx->partition, ctx->fd, data + read)) return -1; - read += ctx->partition->erase_size; - } - - if (read >= len) { - return read; - } - - // Read the next block into the buffer - if (ctx->consumed == ctx->partition->erase_size && read < len) { - if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1; - ctx->consumed = 0; - } - } - - return read; -} - -void mtd_read_close(MtdReadContext *ctx) -{ - close(ctx->fd); - free(ctx->buffer); - free(ctx); -} - -MtdWriteContext *mtd_write_partition(const MtdPartition *partition) -{ - MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext)); - if (ctx == NULL) return NULL; - - ctx->bad_block_offsets = NULL; - ctx->bad_block_alloc = 0; - ctx->bad_block_count = 0; - - ctx->buffer = malloc(partition->erase_size); - if (ctx->buffer == NULL) { - free(ctx); - return NULL; - } - - char mtddevname[32]; - sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); - ctx->fd = open(mtddevname, O_RDWR); - if (ctx->fd < 0) { - free(ctx->buffer); - free(ctx); - return NULL; - } - - ctx->partition = partition; - ctx->stored = 0; - return ctx; -} - -static void add_bad_block_offset(MtdWriteContext *ctx, off_t pos) { - if (ctx->bad_block_count + 1 > ctx->bad_block_alloc) { - ctx->bad_block_alloc = (ctx->bad_block_alloc*2) + 1; - ctx->bad_block_offsets = realloc(ctx->bad_block_offsets, - ctx->bad_block_alloc * sizeof(off_t)); - } - ctx->bad_block_offsets[ctx->bad_block_count++] = pos; -} - -static int write_block(MtdWriteContext *ctx, const char *data) -{ - const MtdPartition *partition = ctx->partition; - int fd = ctx->fd; - - off_t pos = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_CUR)); - if (pos == (off_t) -1) { - printf("mtd: write_block: couldn't SEEK_CUR: %s\n", strerror(errno)); - return -1; - } - - ssize_t size = partition->erase_size; - while (pos + size <= (int) partition->size) { - loff_t bpos = pos; - int ret = ioctl(fd, MEMGETBADBLOCK, &bpos); - if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { - add_bad_block_offset(ctx, pos); - fprintf(stderr, - "mtd: not writing bad block at 0x%08lx (ret %d): %s\n", - pos, ret, strerror(errno)); - pos += partition->erase_size; - continue; // Don't try to erase known factory-bad blocks. - } - - struct erase_info_user erase_info; - erase_info.start = pos; - erase_info.length = size; - int retry; - for (retry = 0; retry < 2; ++retry) { - if (ioctl(fd, MEMERASE, &erase_info) < 0) { - printf("mtd: erase failure at 0x%08lx (%s)\n", - pos, strerror(errno)); - continue; - } - if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos || - TEMP_FAILURE_RETRY(write(fd, data, size)) != size) { - printf("mtd: write error at 0x%08lx (%s)\n", - pos, strerror(errno)); - } - - char verify[size]; - if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos || - TEMP_FAILURE_RETRY(read(fd, verify, size)) != size) { - printf("mtd: re-read error at 0x%08lx (%s)\n", - pos, strerror(errno)); - continue; - } - if (memcmp(data, verify, size) != 0) { - printf("mtd: verification error at 0x%08lx (%s)\n", - pos, strerror(errno)); - continue; - } - - if (retry > 0) { - printf("mtd: wrote block after %d retries\n", retry); - } - printf("mtd: successfully wrote block at %lx\n", pos); - return 0; // Success! - } - - // Try to erase it once more as we give up on this block - add_bad_block_offset(ctx, pos); - printf("mtd: skipping write block at 0x%08lx\n", pos); - ioctl(fd, MEMERASE, &erase_info); - pos += partition->erase_size; - } - - // Ran out of space on the device - errno = ENOSPC; - return -1; -} - -ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len) -{ - size_t wrote = 0; - while (wrote < len) { - // Coalesce partial writes into complete blocks - if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) { - size_t avail = ctx->partition->erase_size - ctx->stored; - size_t copy = len - wrote < avail ? len - wrote : avail; - memcpy(ctx->buffer + ctx->stored, data + wrote, copy); - ctx->stored += copy; - wrote += copy; - } - - // If a complete block was accumulated, write it - if (ctx->stored == ctx->partition->erase_size) { - if (write_block(ctx, ctx->buffer)) return -1; - ctx->stored = 0; - } - - // Write complete blocks directly from the user's buffer - while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) { - if (write_block(ctx, data + wrote)) return -1; - wrote += ctx->partition->erase_size; - } - } - - return wrote; -} - -off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks) -{ - // Zero-pad and write any pending data to get us to a block boundary - if (ctx->stored > 0) { - size_t zero = ctx->partition->erase_size - ctx->stored; - memset(ctx->buffer + ctx->stored, 0, zero); - if (write_block(ctx, ctx->buffer)) return -1; - ctx->stored = 0; - } - - off_t pos = TEMP_FAILURE_RETRY(lseek(ctx->fd, 0, SEEK_CUR)); - if ((off_t) pos == (off_t) -1) { - printf("mtd_erase_blocks: couldn't SEEK_CUR: %s\n", strerror(errno)); - return -1; - } - - const int total = (ctx->partition->size - pos) / ctx->partition->erase_size; - if (blocks < 0) blocks = total; - if (blocks > total) { - errno = ENOSPC; - return -1; - } - - // Erase the specified number of blocks - while (blocks-- > 0) { - loff_t bpos = pos; - if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) { - printf("mtd: not erasing bad block at 0x%08lx\n", pos); - pos += ctx->partition->erase_size; - continue; // Don't try to erase known factory-bad blocks. - } - - struct erase_info_user erase_info; - erase_info.start = pos; - erase_info.length = ctx->partition->erase_size; - if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) { - printf("mtd: erase failure at 0x%08lx\n", pos); - } - pos += ctx->partition->erase_size; - } - - return pos; -} - -int mtd_write_close(MtdWriteContext *ctx) -{ - int r = 0; - // Make sure any pending data gets written - if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1; - if (close(ctx->fd)) r = -1; - free(ctx->bad_block_offsets); - free(ctx->buffer); - free(ctx); - return r; -} diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h deleted file mode 100644 index 8059d6a4d..000000000 --- a/mtdutils/mtdutils.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2007 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 MTDUTILS_H_ -#define MTDUTILS_H_ - -#include <sys/types.h> // for size_t, etc. - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct MtdPartition MtdPartition; - -int mtd_scan_partitions(void); - -const MtdPartition *mtd_find_partition_by_name(const char *name); - -/* mount_point is like "/system" - * filesystem is like "yaffs2" - */ -int mtd_mount_partition(const MtdPartition *partition, const char *mount_point, - const char *filesystem, int read_only); - -/* get the partition and the minimum erase/write block size. NULL is ok. - */ -int mtd_partition_info(const MtdPartition *partition, - size_t *total_size, size_t *erase_size, size_t *write_size); - -/* read or write raw data from a partition, starting at the beginning. - * skips bad blocks as best we can. - */ -typedef struct MtdReadContext MtdReadContext; -typedef struct MtdWriteContext MtdWriteContext; - -MtdReadContext *mtd_read_partition(const MtdPartition *); -ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len); -void mtd_read_close(MtdReadContext *); - -MtdWriteContext *mtd_write_partition(const MtdPartition *); -ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len); -off_t mtd_erase_blocks(MtdWriteContext *, int blocks); /* 0 ok, -1 for all */ -int mtd_write_close(MtdWriteContext *); - -#ifdef __cplusplus -} -#endif - -#endif // MTDUTILS_H_ diff --git a/otafault/Android.mk b/otafault/Android.mk index ba7add855..71c2c62f6 100644 --- a/otafault/Android.mk +++ b/otafault/Android.mk @@ -17,11 +17,13 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) otafault_static_libs := \ - libbase \ - libminzip \ + libziparchive \ libz \ - libselinux + libselinux \ + libbase \ + liblog +LOCAL_CFLAGS := -Werror LOCAL_SRC_FILES := config.cpp ota_io.cpp LOCAL_MODULE_TAGS := eng LOCAL_MODULE := libotafault @@ -32,12 +34,15 @@ LOCAL_WHOLE_STATIC_LIBRARIES := $(otafault_static_libs) include $(BUILD_STATIC_LIBRARY) +# otafault_test (static executable) +# =============================== include $(CLEAR_VARS) LOCAL_SRC_FILES := config.cpp ota_io.cpp test.cpp LOCAL_MODULE_TAGS := tests LOCAL_MODULE := otafault_test LOCAL_STATIC_LIBRARIES := $(otafault_static_libs) +LOCAL_CFLAGS := -Werror LOCAL_C_INCLUDES := bootable/recovery LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/otafault/config.cpp b/otafault/config.cpp index b4567392d..8590833ee 100644 --- a/otafault/config.cpp +++ b/otafault/config.cpp @@ -21,38 +21,42 @@ #include <unistd.h> #include <android-base/stringprintf.h> +#include <ziparchive/zip_archive.h> -#include "minzip/Zip.h" #include "config.h" #include "ota_io.h" #define OTAIO_MAX_FNAME_SIZE 128 -static ZipArchive* archive; +static ZipArchiveHandle archive; +static bool is_retry = false; static std::map<std::string, bool> should_inject_cache; static std::string get_type_path(const char* io_type) { return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type); } -void ota_io_init(ZipArchive* za) { +void ota_io_init(ZipArchiveHandle za, bool retry) { archive = za; + is_retry = retry; ota_set_fault_files(); } bool should_fault_inject(const char* io_type) { // archive will be NULL if we used an entry point other // than updater/updater.cpp:main - if (archive == NULL) { + if (archive == nullptr || is_retry) { return false; } const std::string type_path = get_type_path(io_type); if (should_inject_cache.find(type_path) != should_inject_cache.end()) { return should_inject_cache[type_path]; } - const ZipEntry* entry = mzFindZipEntry(archive, type_path.c_str()); - should_inject_cache[type_path] = entry != nullptr; - return entry != NULL; + ZipString zip_type_path(type_path.c_str()); + ZipEntry entry; + int status = FindEntry(archive, zip_type_path, &entry); + should_inject_cache[type_path] = (status == 0); + return (status == 0); } bool should_hit_cache() { @@ -63,7 +67,9 @@ std::string fault_fname(const char* io_type) { std::string type_path = get_type_path(io_type); std::string fname; fname.resize(OTAIO_MAX_FNAME_SIZE); - const ZipEntry* entry = mzFindZipEntry(archive, type_path.c_str()); - mzReadZipEntry(archive, entry, &fname[0], OTAIO_MAX_FNAME_SIZE); + ZipString zip_type_path(type_path.c_str()); + ZipEntry entry; + int status = FindEntry(archive, zip_type_path, &entry); + ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(&fname[0]), OTAIO_MAX_FNAME_SIZE); return fname; } diff --git a/otafault/config.h b/otafault/config.h index 4430be3fb..4adbdd121 100644 --- a/otafault/config.h +++ b/otafault/config.h @@ -41,7 +41,7 @@ #include <stdbool.h> -#include "minzip/Zip.h" +#include <ziparchive/zip_archive.h> #define OTAIO_BASE_DIR ".libotafault" #define OTAIO_READ "READ" @@ -52,7 +52,7 @@ /* * Initialize libotafault by providing a reference to the OTA package. */ -void ota_io_init(ZipArchive* za); +void ota_io_init(ZipArchiveHandle zip, bool retry); /* * Return true if a config file is present for the given IO type. diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp index 04458537b..3a89bb5dd 100644 --- a/otafault/ota_io.cpp +++ b/otafault/ota_io.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include <map> +#include "ota_io.h" #include <errno.h> #include <fcntl.h> @@ -22,15 +22,17 @@ #include <sys/stat.h> #include <unistd.h> +#include <map> +#include <memory> + #include "config.h" -#include "ota_io.h" static std::map<intptr_t, const char*> filename_cache; static std::string read_fault_file_name = ""; static std::string write_fault_file_name = ""; static std::string fsync_fault_file_name = ""; -static bool get_hit_file(const char* cached_path, std::string ffn) { +static bool get_hit_file(const char* cached_path, const std::string& ffn) { return should_hit_cache() ? !strncmp(cached_path, OTAIO_CACHE_FNAME, strlen(cached_path)) : !strncmp(cached_path, ffn.c_str(), strlen(cached_path)); @@ -68,17 +70,33 @@ FILE* ota_fopen(const char* path, const char* mode) { return fh; } -int ota_close(int fd) { +static int __ota_close(int fd) { // descriptors can be reused, so make sure not to leave them in the cache filename_cache.erase(fd); return close(fd); } -int ota_fclose(FILE* fh) { - filename_cache.erase((intptr_t)fh); +void OtaCloser::Close(int fd) { + __ota_close(fd); +} + +int ota_close(unique_fd& fd) { + return __ota_close(fd.release()); +} + +static int __ota_fclose(FILE* fh) { + filename_cache.erase(reinterpret_cast<intptr_t>(fh)); return fclose(fh); } +void OtaFcloser::operator()(FILE* f) const { + __ota_fclose(f); +}; + +int ota_fclose(unique_file& fh) { + return __ota_fclose(fh.release()); +} + size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { if (should_fault_inject(OTAIO_READ)) { auto cached = filename_cache.find((intptr_t)stream); @@ -92,6 +110,7 @@ size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { } } size_t status = fread(ptr, size, nitems, stream); + // If I/O error occurs, set the retry-update flag. if (status != nitems && errno == EIO) { have_eio_error = true; } diff --git a/otafault/ota_io.h b/otafault/ota_io.h index 84187a76e..9428f1b1f 100644 --- a/otafault/ota_io.h +++ b/otafault/ota_io.h @@ -26,6 +26,10 @@ #include <stdio.h> #include <sys/stat.h> +#include <memory> + +#include <android-base/unique_fd.h> + #define OTAIO_CACHE_FNAME "/cache/saved.file" void ota_set_fault_files(); @@ -36,10 +40,6 @@ int ota_open(const char* path, int oflags, mode_t mode); FILE* ota_fopen(const char* filename, const char* mode); -int ota_close(int fd); - -int ota_fclose(FILE* fh); - size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream); ssize_t ota_read(int fd, void* buf, size_t nbyte); @@ -50,4 +50,20 @@ ssize_t ota_write(int fd, const void* buf, size_t nbyte); int ota_fsync(int fd); +struct OtaCloser { + static void Close(int); +}; + +using unique_fd = android::base::unique_fd_impl<OtaCloser>; + +int ota_close(unique_fd& fd); + +struct OtaFcloser { + void operator()(FILE*) const; +}; + +using unique_file = std::unique_ptr<FILE, OtaFcloser>; + +int ota_fclose(unique_file& fh); + #endif diff --git a/otautil/Android.mk b/otautil/Android.mk new file mode 100644 index 000000000..f7ca9a9ee --- /dev/null +++ b/otautil/Android.mk @@ -0,0 +1,33 @@ +# Copyright (C) 2016 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + SysUtil.cpp \ + DirUtil.cpp \ + ZipUtil.cpp \ + ThermalUtil.cpp + +LOCAL_STATIC_LIBRARIES := \ + libselinux \ + libbase + +LOCAL_MODULE := libotautil +LOCAL_CFLAGS := \ + -Werror \ + -Wall + +include $(BUILD_STATIC_LIBRARY) diff --git a/minzip/DirUtil.c b/otautil/DirUtil.cpp index 97cb2e0ee..e08e360c0 100644 --- a/minzip/DirUtil.c +++ b/otautil/DirUtil.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "DirUtil.h" + #include <stdlib.h> #include <string.h> #include <stdio.h> @@ -24,7 +26,10 @@ #include <dirent.h> #include <limits.h> -#include "DirUtil.h" +#include <string> + +#include <selinux/label.h> +#include <selinux/selinux.h> typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus; @@ -66,43 +71,25 @@ dirCreateHierarchy(const char *path, int mode, errno = ENOENT; return -1; } - - /* Allocate a path that we can modify; stick a slash on - * the end to make things easier. - */ - size_t pathLen = strlen(path); - char *cpath = (char *)malloc(pathLen + 2); - if (cpath == NULL) { - errno = ENOMEM; - return -1; - } - memcpy(cpath, path, pathLen); + // Allocate a path that we can modify; stick a slash on + // the end to make things easier. + std::string cpath = path; if (stripFileName) { - /* Strip everything after the last slash. - */ - char *c = cpath + pathLen - 1; - while (c != cpath && *c != '/') { - c--; - } - if (c == cpath) { - //xxx test this path - /* No directory component. Act like the path was empty. - */ + // Strip everything after the last slash. + size_t pos = cpath.rfind('/'); + if (pos == std::string::npos) { errno = ENOENT; - free(cpath); return -1; } - c[1] = '\0'; // Terminate after the slash we found. + cpath.resize(pos + 1); } else { - /* Make sure that the path ends in a slash. - */ - cpath[pathLen] = '/'; - cpath[pathLen + 1] = '\0'; + // Make sure that the path ends in a slash. + cpath.push_back('/'); } /* See if it already exists. */ - ds = getPathDirStatus(cpath); + ds = getPathDirStatus(cpath.c_str()); if (ds == DDIR) { return 0; } else if (ds == DILLEGAL) { @@ -112,7 +99,8 @@ dirCreateHierarchy(const char *path, int mode, /* Walk up the path from the root and make each level. * If a directory already exists, no big deal. */ - char *p = cpath; + const char *path_start = &cpath[0]; + char *p = &cpath[0]; while (*p != '\0') { /* Skip any slashes, watching out for the end of the string. */ @@ -135,12 +123,11 @@ dirCreateHierarchy(const char *path, int mode, /* Check this part of the path and make a new directory * if necessary. */ - ds = getPathDirStatus(cpath); + ds = getPathDirStatus(path_start); if (ds == DILLEGAL) { /* Could happen if some other process/thread is * messing with the filesystem. */ - free(cpath); return -1; } else if (ds == DMISSING) { int err; @@ -148,11 +135,11 @@ dirCreateHierarchy(const char *path, int mode, char *secontext = NULL; if (sehnd) { - selabel_lookup(sehnd, &secontext, cpath, mode); + selabel_lookup(sehnd, &secontext, path_start, mode); setfscreatecon(secontext); } - err = mkdir(cpath, mode); + err = mkdir(path_start, mode); if (secontext) { freecon(secontext); @@ -160,22 +147,17 @@ dirCreateHierarchy(const char *path, int mode, } if (err != 0) { - free(cpath); return -1; } - if (timestamp != NULL && utime(cpath, timestamp)) { - free(cpath); + if (timestamp != NULL && utime(path_start, timestamp)) { return -1; } } // else, this directory already exists. - - /* Repair the path and continue. - */ + + // Repair the path and continue. *p = '/'; } - free(cpath); - return 0; } diff --git a/minzip/DirUtil.h b/otautil/DirUtil.h index 85a00128b..85b83c387 100644 --- a/minzip/DirUtil.h +++ b/otautil/DirUtil.h @@ -24,8 +24,7 @@ extern "C" { #endif -#include <selinux/selinux.h> -#include <selinux/label.h> +struct selabel_handle; /* Like "mkdir -p", try to guarantee that all directories * specified in path are present, creating as many directories diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp new file mode 100644 index 000000000..a2133b953 --- /dev/null +++ b/otautil/SysUtil.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2006 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 "SysUtil.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> + +static bool sysMapFD(int fd, MemMapping* pMap) { + CHECK(pMap != nullptr); + + struct stat sb; + if (fstat(fd, &sb) == -1) { + PLOG(ERROR) << "fstat(" << fd << ") failed"; + return false; + } + + void* memPtr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (memPtr == MAP_FAILED) { + PLOG(ERROR) << "mmap(" << sb.st_size << ", R, PRIVATE, " << fd << ", 0) failed"; + return false; + } + + pMap->addr = static_cast<unsigned char*>(memPtr); + pMap->length = sb.st_size; + pMap->ranges.push_back({ memPtr, static_cast<size_t>(sb.st_size) }); + + return true; +} + +// A "block map" which looks like this (from uncrypt/uncrypt.cpp): +// +// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device +// 49652 4096 # file size in bytes, block size +// 3 # count of block ranges +// 1000 1008 # block range 0 +// 2100 2102 # ... block range 1 +// 30 33 # ... block range 2 +// +// Each block range represents a half-open interval; the line "30 33" +// reprents the blocks [30, 31, 32]. +static int sysMapBlockFile(const char* filename, MemMapping* pMap) { + CHECK(pMap != nullptr); + + std::string content; + if (!android::base::ReadFileToString(filename, &content)) { + PLOG(ERROR) << "Failed to read " << filename; + return -1; + } + + std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n"); + if (lines.size() < 4) { + LOG(ERROR) << "Block map file is too short: " << lines.size(); + return -1; + } + + size_t size; + unsigned int blksize; + if (sscanf(lines[1].c_str(), "%zu %u", &size, &blksize) != 2) { + LOG(ERROR) << "Failed to parse file size and block size: " << lines[1]; + return -1; + } + + size_t range_count; + if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) { + LOG(ERROR) << "Failed to parse block map header: " << lines[2]; + return -1; + } + + size_t blocks; + if (blksize != 0) { + blocks = ((size - 1) / blksize) + 1; + } + if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0 || + lines.size() != 3 + range_count) { + LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize + << ", range_count " << range_count << ", lines " << lines.size(); + return -1; + } + + // Reserve enough contiguous address space for the whole file. + void* reserve = mmap64(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (reserve == MAP_FAILED) { + PLOG(ERROR) << "failed to reserve address space"; + return -1; + } + + const std::string& block_dev = lines[0]; + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_dev.c_str(), O_RDONLY))); + if (fd == -1) { + PLOG(ERROR) << "failed to open block device " << block_dev; + munmap(reserve, blocks * blksize); + return -1; + } + + pMap->ranges.resize(range_count); + + unsigned char* next = static_cast<unsigned char*>(reserve); + size_t remaining_size = blocks * blksize; + bool success = true; + for (size_t i = 0; i < range_count; ++i) { + const std::string& line = lines[i + 3]; + + size_t start, end; + if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) { + LOG(ERROR) << "failed to parse range " << i << " in block map: " << line; + success = false; + break; + } + size_t length = (end - start) * blksize; + if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) { + LOG(ERROR) << "unexpected range in block map: " << start << " " << end; + success = false; + break; + } + + void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, + static_cast<off64_t>(start) * blksize); + if (addr == MAP_FAILED) { + PLOG(ERROR) << "failed to map block " << i; + success = false; + break; + } + pMap->ranges[i].addr = addr; + pMap->ranges[i].length = length; + + next += length; + remaining_size -= length; + } + if (success && remaining_size != 0) { + LOG(ERROR) << "ranges in block map are invalid: remaining_size = " << remaining_size; + success = false; + } + if (!success) { + munmap(reserve, blocks * blksize); + return -1; + } + + pMap->addr = static_cast<unsigned char*>(reserve); + pMap->length = size; + + LOG(INFO) << "mmapped " << range_count << " ranges"; + + return 0; +} + +int sysMapFile(const char* fn, MemMapping* pMap) { + if (fn == nullptr || pMap == nullptr) { + LOG(ERROR) << "Invalid argument(s)"; + return -1; + } + + *pMap = {}; + + if (fn[0] == '@') { + if (sysMapBlockFile(fn + 1, pMap) != 0) { + LOG(ERROR) << "Map of '" << fn << "' failed"; + return -1; + } + } else { + // This is a regular file. + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn, O_RDONLY))); + if (fd == -1) { + PLOG(ERROR) << "Unable to open '" << fn << "'"; + return -1; + } + + if (!sysMapFD(fd, pMap)) { + LOG(ERROR) << "Map of '" << fn << "' failed"; + return -1; + } + } + return 0; +} + +/* + * Release a memory mapping. + */ +void sysReleaseMap(MemMapping* pMap) { + std::for_each(pMap->ranges.cbegin(), pMap->ranges.cend(), [](const MappedRange& range) { + if (munmap(range.addr, range.length) == -1) { + PLOG(ERROR) << "munmap(" << range.addr << ", " << range.length << ") failed"; + } + }); + pMap->ranges.clear(); +} diff --git a/otautil/SysUtil.h b/otautil/SysUtil.h new file mode 100644 index 000000000..6a79bf31f --- /dev/null +++ b/otautil/SysUtil.h @@ -0,0 +1,55 @@ +/* + * Copyright 2006 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 _OTAUTIL_SYSUTIL +#define _OTAUTIL_SYSUTIL + +#include <sys/types.h> + +#include <vector> + +struct MappedRange { + void* addr; + size_t length; +}; + +/* + * Use this to keep track of mapped segments. + */ +struct MemMapping { + unsigned char* addr; /* start of data */ + size_t length; /* length of data */ + + std::vector<MappedRange> ranges; +}; + +/* + * Map a file into a private, read-only memory segment. If 'fn' + * begins with an '@' character, it is a map of blocks to be mapped, + * otherwise it is treated as an ordinary file. + * + * On success, "pMap" is filled in, and zero is returned. + */ +int sysMapFile(const char* fn, MemMapping* pMap); + +/* + * Release the pages associated with a shared memory segment. + * + * This does not free "pMap"; it just releases the memory. + */ +void sysReleaseMap(MemMapping* pMap); + +#endif // _OTAUTIL_SYSUTIL diff --git a/otautil/ThermalUtil.cpp b/otautil/ThermalUtil.cpp new file mode 100644 index 000000000..13d36432a --- /dev/null +++ b/otautil/ThermalUtil.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 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 "ThermalUtil.h" + +#include <dirent.h> +#include <stdio.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/strings.h> + +static constexpr auto THERMAL_PREFIX = "/sys/class/thermal/"; + +static int thermal_filter(const dirent* de) { + if (android::base::StartsWith(de->d_name, "thermal_zone")) { + return 1; + } + return 0; +} + +static std::vector<std::string> InitThermalPaths() { + dirent** namelist; + int n = scandir(THERMAL_PREFIX, &namelist, thermal_filter, alphasort); + if (n == -1) { + PLOG(ERROR) << "Failed to scandir " << THERMAL_PREFIX; + return {}; + } + if (n == 0) { + LOG(ERROR) << "Failed to find CPU thermal info in " << THERMAL_PREFIX; + return {}; + } + + std::vector<std::string> thermal_paths; + while (n--) { + thermal_paths.push_back(THERMAL_PREFIX + std::string(namelist[n]->d_name) + "/temp"); + free(namelist[n]); + } + free(namelist); + return thermal_paths; +} + +int GetMaxValueFromThermalZone() { + static std::vector<std::string> thermal_paths = InitThermalPaths(); + int max_temperature = -1; + for (const auto& path : thermal_paths) { + std::string content; + if (!android::base::ReadFileToString(path, &content)) { + PLOG(WARNING) << "Failed to read " << path; + continue; + } + + int temperature; + if (!android::base::ParseInt(android::base::Trim(content), &temperature)) { + LOG(WARNING) << "Failed to parse integer in " << content; + continue; + } + max_temperature = std::max(temperature, max_temperature); + } + LOG(INFO) << "current maximum temperature: " << max_temperature; + return max_temperature; +}
\ No newline at end of file diff --git a/otautil/ThermalUtil.h b/otautil/ThermalUtil.h new file mode 100644 index 000000000..43ab55940 --- /dev/null +++ b/otautil/ThermalUtil.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 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 OTAUTIL_THERMALUTIL_H +#define OTAUTIL_THERMALUTIL_H + +// We can find the temperature reported by all sensors in /sys/class/thermal/thermal_zone*/temp. +// Their values are in millidegree Celsius; and we will log the maximum one. +int GetMaxValueFromThermalZone(); + +#endif // OTAUTIL_THERMALUTIL_H diff --git a/otautil/ZipUtil.cpp b/otautil/ZipUtil.cpp new file mode 100644 index 000000000..714c956ed --- /dev/null +++ b/otautil/ZipUtil.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 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 "ZipUtil.h" + +#include <errno.h> +#include <fcntl.h> +#include <utime.h> + +#include <string> + +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <selinux/label.h> +#include <selinux/selinux.h> +#include <ziparchive/zip_archive.h> + +#include "DirUtil.h" + +static constexpr mode_t UNZIP_DIRMODE = 0755; +static constexpr mode_t UNZIP_FILEMODE = 0644; + +bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path, + const std::string& dest_path, const struct utimbuf* timestamp, + struct selabel_handle* sehnd) { + if (!zip_path.empty() && zip_path[0] == '/') { + LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path; + return false; + } + if (dest_path.empty() || dest_path[0] != '/') { + LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path; + return false; + } + + void* cookie; + std::string target_dir(dest_path); + if (dest_path.back() != '/') { + target_dir += '/'; + } + std::string prefix_path(zip_path); + if (!zip_path.empty() && zip_path.back() != '/') { + prefix_path += '/'; + } + const ZipString zip_prefix(prefix_path.c_str()); + + int ret = StartIteration(zip, &cookie, &zip_prefix, nullptr); + if (ret != 0) { + LOG(ERROR) << "failed to start iterating zip entries."; + return false; + } + + std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration); + ZipEntry entry; + ZipString name; + int extractCount = 0; + while (Next(cookie, &entry, &name) == 0) { + std::string entry_name(name.name, name.name + name.name_length); + CHECK_LE(prefix_path.size(), entry_name.size()); + std::string path = target_dir + entry_name.substr(prefix_path.size()); + // Skip dir. + if (path.back() == '/') { + continue; + } + //TODO(b/31917448) handle the symlink. + + if (dirCreateHierarchy(path.c_str(), UNZIP_DIRMODE, timestamp, true, sehnd) != 0) { + LOG(ERROR) << "failed to create dir for " << path; + return false; + } + + char *secontext = NULL; + if (sehnd) { + selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE); + setfscreatecon(secontext); + } + android::base::unique_fd fd(open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, UNZIP_FILEMODE)); + if (fd == -1) { + PLOG(ERROR) << "Can't create target file \"" << path << "\""; + return false; + } + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } + + int err = ExtractEntryToFile(zip, &entry, fd); + if (err != 0) { + LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err); + return false; + } + + if (fsync(fd) != 0) { + PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\""; + return false; + } + + if (timestamp != nullptr && utime(path.c_str(), timestamp)) { + PLOG(ERROR) << "Error touching \"" << path << "\""; + return false; + } + + LOG(INFO) << "Extracted file \"" << path << "\""; + ++extractCount; + } + + LOG(INFO) << "Extracted " << extractCount << " file(s)"; + return true; +} diff --git a/otautil/ZipUtil.h b/otautil/ZipUtil.h new file mode 100644 index 000000000..cda405c2a --- /dev/null +++ b/otautil/ZipUtil.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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 _OTAUTIL_ZIPUTIL_H +#define _OTAUTIL_ZIPUTIL_H + +#include <utime.h> + +#include <string> + +#include <selinux/label.h> +#include <ziparchive/zip_archive.h> + +/* + * Inflate all files under zip_path to the directory specified by + * dest_path, which must exist and be a writable directory. The zip_path + * is allowed to be an empty string, in which case the whole package + * will be extracted. + * + * Directory entries are not extracted. + * + * The immediate children of zip_path will become the immediate + * children of dest_path; e.g., if the archive contains the entries + * + * a/b/c/one + * a/b/c/two + * a/b/c/d/three + * + * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting + * files will be + * + * /tmp/one + * /tmp/two + * /tmp/d/three + * + * If timestamp is non-NULL, file timestamps will be set accordingly. + * + * Returns true on success, false on failure. + */ +bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path, + const std::string& dest_path, const struct utimbuf* timestamp, + struct selabel_handle* sehnd); + +#endif // _OTAUTIL_ZIPUTIL_H diff --git a/print_sha1.h b/print_sha1.h index c7c1f3651..1f8589519 100644 --- a/print_sha1.h +++ b/print_sha1.h @@ -20,7 +20,7 @@ #include <stdint.h> #include <string> -#include "openssl/sha.h" +#include <openssl/sha.h> static std::string print_sha1(const uint8_t* sha1, size_t len) { const char* hex = "0123456789abcdef"; @@ -41,7 +41,7 @@ static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) { } static std::string print_hex(const uint8_t* bytes, size_t len) { - return print_sha1(bytes, len); + return print_sha1(bytes, len); } #endif // RECOVERY_PRINT_SHA1_H diff --git a/private/install.h b/private/install.h new file mode 100644 index 000000000..12d303b01 --- /dev/null +++ b/private/install.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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. + */ + +// Private headers exposed for testing purpose only. + +#pragma once + +#include <string> +#include <vector> + +#include <ziparchive/zip_archive.h> + +int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count, + int status_fd, std::vector<std::string>* cmd); diff --git a/recovery-persist.cpp b/recovery-persist.cpp index 25df03f47..d706ccac8 100644 --- a/recovery-persist.cpp +++ b/recovery-persist.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "recovery-persist" - // // Strictly to deal with reboot into system after OTA after /data // mounts to pull the last pmsg file data and place it @@ -32,7 +30,6 @@ // --force-persist ignore /cache mount, always rotate in the contents. // -#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -40,31 +37,31 @@ #include <string> -#include <android/log.h> /* Android Log Priority Tags */ #include <android-base/file.h> -#include <log/log.h> -#include <log/logger.h> /* Android Log packet format */ +#include <android-base/logging.h> #include <private/android_logger.h> /* private pmsg functions */ +#include "rotate_logs.h" + static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log"; static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0"; static const char *LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg"; static const char *LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0"; static const char *ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops"; -static const int KEEP_LOG_COUNT = 10; - // close a file, log an error if the error indicator is set static void check_and_fclose(FILE *fp, const char *name) { fflush(fp); - if (ferror(fp)) SLOGE("%s %s", name, strerror(errno)); + if (ferror(fp)) { + PLOG(ERROR) << "Error in " << name; + } fclose(fp); } static void copy_file(const char* source, const char* destination) { FILE* dest_fp = fopen(destination, "w"); if (dest_fp == nullptr) { - SLOGE("%s %s", destination, strerror(errno)); + PLOG(ERROR) << "Can't open " << destination; } else { FILE* source_fp = fopen(source, "r"); if (source_fp != nullptr) { @@ -81,39 +78,6 @@ static void copy_file(const char* source, const char* destination) { static bool rotated = false; -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -static void rotate_logs(int max) { - // Logs should only be rotated once. - - if (rotated) { - return; - } - rotated = true; - - for (int i = max-1; i >= 0; --i) { - std::string old_log(LAST_LOG_FILE); - if (i > 0) { - old_log += "." + std::to_string(i); - } - std::string new_log(LAST_LOG_FILE); - new_log += "." + std::to_string(i+1); - - // Ignore errors if old_log doesn't exist. - rename(old_log.c_str(), new_log.c_str()); - - std::string old_kmsg(LAST_KMSG_FILE); - if (i > 0) { - old_kmsg += "." + std::to_string(i); - } - std::string new_kmsg(LAST_KMSG_FILE); - new_kmsg += "." + std::to_string(i+1); - - rename(old_kmsg.c_str(), new_kmsg.c_str()); - } -} - ssize_t logsave( log_id_t /* logId */, char /* prio */, @@ -139,7 +103,8 @@ ssize_t logsave( // already-rotated files? Algorithm thus far is KISS: one file, // one rotation allowed. - rotate_logs(KEEP_LOG_COUNT); + rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); + rotated = true; return android::base::WriteStringToFile(buffer, destination.c_str()); } @@ -157,7 +122,7 @@ int main(int argc, char **argv) { static const char mounts_file[] = "/proc/mounts"; FILE *fp = fopen(mounts_file, "r"); if (!fp) { - SLOGV("%s %s", mounts_file, strerror(errno)); + PLOG(ERROR) << "failed to open " << mounts_file; } else { char *line = NULL; size_t len = 0; diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp index 70adc70ee..14565d3f4 100644 --- a/recovery-refresh.cpp +++ b/recovery-refresh.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "recovery-refresh" - // // Strictly to deal with reboot into system after OTA, then // reboot while in system before boot complete landing us back @@ -40,65 +38,11 @@ // #include <string.h> - #include <string> -#include <android/log.h> /* Android Log Priority Tags */ -#include <log/logger.h> /* Android Log packet format */ #include <private/android_logger.h> /* private pmsg functions */ -static const char LAST_KMSG_FILE[] = "recovery/last_kmsg"; -static const char LAST_LOG_FILE[] = "recovery/last_log"; - -static ssize_t logbasename( - log_id_t /* logId */, - char /* prio */, - const char *filename, - const char * /* buf */, size_t len, - void *arg) { - if (strstr(LAST_KMSG_FILE, filename) || - strstr(LAST_LOG_FILE, filename)) { - bool *doRotate = reinterpret_cast<bool *>(arg); - *doRotate = true; - } - return len; -} - -static ssize_t logrotate( - log_id_t logId, - char prio, - const char *filename, - const char *buf, size_t len, - void *arg) { - bool *doRotate = reinterpret_cast<bool *>(arg); - if (!*doRotate) { - return __android_log_pmsg_file_write(logId, prio, filename, buf, len); - } - - std::string name(filename); - size_t dot = name.find_last_of("."); - std::string sub = name.substr(0, dot); - - if (!strstr(LAST_KMSG_FILE, sub.c_str()) && - !strstr(LAST_LOG_FILE, sub.c_str())) { - return __android_log_pmsg_file_write(logId, prio, filename, buf, len); - } - - // filename rotation - if (dot == std::string::npos) { - name += ".1"; - } else { - std::string number = name.substr(dot + 1); - if (!isdigit(number.data()[0])) { - name += ".1"; - } else { - unsigned long long i = std::stoull(number); - name = sub + "." + std::to_string(i + 1); - } - } - - return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); -} +#include "rotate_logs.h" int main(int argc, char **argv) { static const char filter[] = "recovery/"; @@ -106,7 +50,6 @@ int main(int argc, char **argv) { static const char rotate_flag[] = "--rotate"; ssize_t ret; bool doRotate = false; - // Take last pmsg contents and rewrite it to the current pmsg session. if ((argc <= 1) || !argv[1] || (((doRotate = strcmp(argv[1], rotate_flag))) && diff --git a/recovery.cpp b/recovery.cpp index 0f0b978e7..c1a31b6a8 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -34,23 +34,30 @@ #include <time.h> #include <unistd.h> +#include <algorithm> #include <chrono> +#include <memory> #include <string> #include <vector> #include <adb.h> -#include <android/log.h> /* Android Log Priority Tags */ #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 <android-base/unique_fd.h> #include <bootloader_message/bootloader_message.h> #include <cutils/android_reboot.h> -#include <cutils/properties.h> -#include <log/logger.h> /* Android Log packet format */ -#include <private/android_logger.h> /* private pmsg functions */ - +#include <cutils/properties.h> /* for property_list */ #include <healthd/BatteryMonitor.h> +#include <private/android_logger.h> /* private pmsg functions */ +#include <private/android_filesystem_config.h> /* for AID_SYSTEM */ +#include <selinux/android.h> +#include <selinux/label.h> +#include <selinux/selinux.h> +#include <ziparchive/zip_archive.h> #include "adb_install.h" #include "common.h" @@ -59,18 +66,16 @@ #include "fuse_sdcard_provider.h" #include "fuse_sideload.h" #include "install.h" +#include "minadbd/minadbd.h" #include "minui/minui.h" -#include "minzip/DirUtil.h" -#include "minzip/Zip.h" +#include "otautil/DirUtil.h" #include "roots.h" -#include "ui.h" -#include "unique_fd.h" +#include "rotate_logs.h" #include "screen_ui.h" - -struct selabel_handle *sehandle; +#include "stub_ui.h" +#include "ui.h" static const struct option OPTIONS[] = { - { "send_intent", required_argument, NULL, 'i' }, { "update_package", required_argument, NULL, 'u' }, { "retry_count", required_argument, NULL, 'n' }, { "wipe_data", no_argument, NULL, 'w' }, @@ -80,12 +85,12 @@ static const struct option OPTIONS[] = { { "sideload_auto_reboot", no_argument, NULL, 'a' }, { "just_exit", no_argument, NULL, 'x' }, { "locale", required_argument, NULL, 'l' }, - { "stages", required_argument, NULL, 'g' }, { "shutdown_after", no_argument, NULL, 'p' }, { "reason", required_argument, NULL, 'r' }, { "security", no_argument, NULL, 'e'}, { "wipe_ab", no_argument, NULL, 0 }, { "wipe_package_size", required_argument, NULL, 0 }, + { "prompt_and_wipe_data", no_argument, NULL, 0 }, { NULL, 0, NULL, 0 }, }; @@ -97,7 +102,6 @@ static const std::vector<std::string> bootreason_blacklist { static const char *CACHE_LOG_DIR = "/cache/recovery"; static const char *COMMAND_FILE = "/cache/recovery/command"; -static const char *INTENT_FILE = "/cache/recovery/intent"; static const char *LOG_FILE = "/cache/recovery/log"; static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; static const char *LOCALE_FILE = "/cache/recovery/last_locale"; @@ -110,7 +114,6 @@ static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; static const char *LAST_LOG_FILE = "/cache/recovery/last_log"; -static const int KEEP_LOG_COUNT = 10; // We will try to apply the update package 5 times at most in case of an I/O error. static const int EIO_RETRY_COUNT = 4; static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; @@ -119,25 +122,28 @@ static const int BATTERY_READ_TIMEOUT_IN_SEC = 10; // So we should check battery with a slightly lower limitation. static const int BATTERY_OK_PERCENTAGE = 20; static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15; -constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe"; +static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe"; +static constexpr const char* DEFAULT_LOCALE = "en-US"; -RecoveryUI* ui = NULL; -static const char* locale = "en_US"; -char* stage = NULL; -char* reason = NULL; -bool modified_flash = false; +static std::string locale; static bool has_cache = false; +RecoveryUI* ui = nullptr; +bool modified_flash = false; +std::string stage; +const char* reason = nullptr; +struct selabel_handle* sehandle; + /* * The recovery tool communicates with the main system through /cache files. * /cache/recovery/command - INPUT - command line for tool, one arg per line * /cache/recovery/log - OUTPUT - combined log file from recovery run(s) - * /cache/recovery/intent - OUTPUT - intent that was passed in * * The arguments which may be supplied in the recovery.command file: - * --send_intent=anystring - write the text out to recovery.intent * --update_package=path - verify install an OTA package file * --wipe_data - erase user data (and cache), then reboot + * --prompt_and_wipe_data - prompt the user that data is corrupt, + * with their consent erase user data (and cache), then reboot * --wipe_cache - wipe cache (but not user data), then reboot * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs * --just_exit - do nothing; exit and reboot @@ -170,30 +176,13 @@ static bool has_cache = false; * -- after this, rebooting will (try to) restart the main system -- * 7. ** if install failed ** * 7a. prompt_and_wait() shows an error icon and waits for the user - * 7b; the user reboots (pulling the battery, etc) into the main system - * 8. main() calls maybe_install_firmware_update() - * ** if the update contained radio/hboot firmware **: - * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache" - * -- after this, rebooting will reformat cache & restart main system -- - * 8b. m_i_f_u() writes firmware image into raw cache partition - * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache" - * -- after this, rebooting will attempt to reinstall firmware -- - * 8d. bootloader tries to flash firmware - * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache") - * -- after this, rebooting will reformat cache & restart main system -- - * 8f. erase_volume() reformats /cache - * 8g. finish_recovery() erases BCB - * -- after this, rebooting will (try to) restart the main system -- - * 9. main() calls reboot() to boot main system + * 7b. the user reboots (pulling the battery, etc) into the main system */ -static const int MAX_ARG_LENGTH = 4096; -static const int MAX_ARGS = 100; - // open a given path, mounting partitions as necessary FILE* fopen_path(const char *path, const char *mode) { if (ensure_path_mounted(path) != 0) { - LOGE("Can't mount %s\n", path); + LOG(ERROR) << "Can't mount " << path; return NULL; } @@ -208,19 +197,31 @@ FILE* fopen_path(const char *path, const char *mode) { // close a file, log an error if the error indicator is set static void check_and_fclose(FILE *fp, const char *name) { fflush(fp); - if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno)); + if (fsync(fileno(fp)) == -1) { + PLOG(ERROR) << "Failed to fsync " << name; + } + if (ferror(fp)) { + PLOG(ERROR) << "Error in " << name; + } fclose(fp); } bool is_ro_debuggable() { - char value[PROPERTY_VALUE_MAX+1]; - return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1'); + return android::base::GetBoolProperty("ro.debuggable", false); +} + +bool reboot(const std::string& command) { + std::string cmd = command; + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + cmd += ",quiescent"; + } + return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd); } static void redirect_stdio(const char* filename) { int pipefd[2]; if (pipe(pipefd) == -1) { - LOGE("pipe failed: %s\n", strerror(errno)); + PLOG(ERROR) << "pipe failed"; // Fall back to traditional logging mode without timestamps. // If these fail, there's not really anywhere to complain... @@ -232,7 +233,7 @@ static void redirect_stdio(const char* filename) { pid_t pid = fork(); if (pid == -1) { - LOGE("fork failed: %s\n", strerror(errno)); + PLOG(ERROR) << "fork failed"; // Fall back to traditional logging mode without timestamps. // If these fail, there's not really anywhere to complain... @@ -251,17 +252,17 @@ static void redirect_stdio(const char* filename) { // Child logger to actually write to the log file. FILE* log_fp = fopen(filename, "a"); if (log_fp == nullptr) { - LOGE("fopen \"%s\" failed: %s\n", filename, strerror(errno)); + PLOG(ERROR) << "fopen \"" << filename << "\" failed"; close(pipefd[0]); - _exit(1); + _exit(EXIT_FAILURE); } FILE* pipe_fp = fdopen(pipefd[0], "r"); if (pipe_fp == nullptr) { - LOGE("fdopen failed: %s\n", strerror(errno)); + PLOG(ERROR) << "fdopen failed"; check_and_fclose(log_fp, filename); close(pipefd[0]); - _exit(1); + _exit(EXIT_FAILURE); } char* line = nullptr; @@ -278,12 +279,12 @@ static void redirect_stdio(const char* filename) { fflush(log_fp); } - LOGE("getline failed: %s\n", strerror(errno)); + PLOG(ERROR) << "getline failed"; free(line); check_and_fclose(log_fp, filename); close(pipefd[0]); - _exit(1); + _exit(EXIT_FAILURE); } else { // Redirect stdout/stderr to the logger process. // Close the unused read end. @@ -293,10 +294,10 @@ static void redirect_stdio(const char* filename) { setbuf(stderr, nullptr); if (dup2(pipefd[1], STDOUT_FILENO) == -1) { - LOGE("dup2 stdout failed: %s\n", strerror(errno)); + PLOG(ERROR) << "dup2 stdout failed"; } if (dup2(pipefd[1], STDERR_FILENO) == -1) { - LOGE("dup2 stderr failed: %s\n", strerror(errno)); + PLOG(ERROR) << "dup2 stderr failed"; } close(pipefd[1]); @@ -307,104 +308,95 @@ static void redirect_stdio(const char* filename) { // - the actual command line // - the bootloader control block (one per line, after "recovery") // - the contents of COMMAND_FILE (one per line) -static void -get_args(int *argc, char ***argv) { - bootloader_message boot = {}; - std::string err; - if (!read_bootloader_message(&boot, &err)) { - LOGE("%s\n", err.c_str()); - // If fails, leave a zeroed bootloader_message. - memset(&boot, 0, sizeof(boot)); - } - stage = strndup(boot.stage, sizeof(boot.stage)); - - if (boot.command[0] != 0 && boot.command[0] != 255) { - LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); - } - - if (boot.status[0] != 0 && boot.status[0] != 255) { - LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status); - } - - // --- if arguments weren't supplied, look in the bootloader control block - if (*argc <= 1) { - boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination - const char *arg = strtok(boot.recovery, "\n"); - if (arg != NULL && !strcmp(arg, "recovery")) { - *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); - (*argv)[0] = strdup(arg); - for (*argc = 1; *argc < MAX_ARGS; ++*argc) { - if ((arg = strtok(NULL, "\n")) == NULL) break; - (*argv)[*argc] = strdup(arg); - } - LOGI("Got arguments from boot message\n"); - } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { - LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); - } - } - - // --- if that doesn't work, try the command file (if we have /cache). - if (*argc <= 1 && has_cache) { - FILE *fp = fopen_path(COMMAND_FILE, "r"); - if (fp != NULL) { - char *token; - char *argv0 = (*argv)[0]; - *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); - (*argv)[0] = argv0; // use the same program name - - char buf[MAX_ARG_LENGTH]; - for (*argc = 1; *argc < MAX_ARGS; ++*argc) { - if (!fgets(buf, sizeof(buf), fp)) break; - token = strtok(buf, "\r\n"); - if (token != NULL) { - (*argv)[*argc] = strdup(token); // Strip newline. - } else { - --*argc; - } - } - - check_and_fclose(fp, COMMAND_FILE); - LOGI("Got arguments from %s\n", COMMAND_FILE); - } - } - - // --> write the arguments we have back into the bootloader control block - // always boot into recovery after this (until finish_recovery() is called) - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - int i; - for (i = 1; i < *argc; ++i) { - strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery)); - strlcat(boot.recovery, "\n", sizeof(boot.recovery)); - } - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); - } +static std::vector<std::string> get_args(const int argc, char** const argv) { + CHECK_GT(argc, 0); + + bootloader_message boot = {}; + std::string err; + if (!read_bootloader_message(&boot, &err)) { + LOG(ERROR) << err; + // If fails, leave a zeroed bootloader_message. + boot = {}; + } + stage = std::string(boot.stage); + + if (boot.command[0] != 0) { + std::string boot_command = std::string(boot.command, sizeof(boot.command)); + LOG(INFO) << "Boot command: " << boot_command; + } + + if (boot.status[0] != 0) { + std::string boot_status = std::string(boot.status, sizeof(boot.status)); + LOG(INFO) << "Boot status: " << boot_status; + } + + std::vector<std::string> args(argv, argv + argc); + + // --- if arguments weren't supplied, look in the bootloader control block + if (args.size() == 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + std::string boot_recovery(boot.recovery); + std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n"); + if (!tokens.empty() && tokens[0] == "recovery") { + for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from boot message"; + } else if (boot.recovery[0] != 0) { + LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; + } + } + + // --- if that doesn't work, try the command file (if we have /cache). + if (args.size() == 1 && has_cache) { + std::string content; + if (ensure_path_mounted(COMMAND_FILE) == 0 && + android::base::ReadFileToString(COMMAND_FILE, &content)) { + std::vector<std::string> tokens = android::base::Split(content, "\n"); + // All the arguments in COMMAND_FILE are needed (unlike the BCB message, + // COMMAND_FILE doesn't use filename as the first argument). + for (auto it = tokens.begin(); it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; + } + } + + // Write the arguments (excluding the filename in args[0]) back into the + // bootloader control block. So the device will always boot into recovery to + // finish the pending work, until finish_recovery() is called. + std::vector<std::string> options(args.cbegin() + 1, args.cend()); + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } + + return args; } -static void -set_sdcard_update_bootloader_message() { - bootloader_message boot = {}; - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - std::string err; - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); - } +// Set the BCB to reboot back into recovery (it won't resume the install from +// sdcard though). +static void set_sdcard_update_bootloader_message() { + std::vector<std::string> options; + std::string err; + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } } // Read from kernel log into buffer and write out to file. static void save_kernel_log(const char* destination) { int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0); if (klog_buf_len <= 0) { - LOGE("Error getting klog size: %s\n", strerror(errno)); + PLOG(ERROR) << "Error getting klog size"; return; } std::string buffer(klog_buf_len, 0); int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len); if (n == -1) { - LOGE("Error in reading klog: %s\n", strerror(errno)); + PLOG(ERROR) << "Error in reading klog"; return; } buffer.resize(n); @@ -424,17 +416,17 @@ static void copy_log_file_to_pmsg(const char* source, const char* destination) { } // How much of the temp log we have copied to the copy in cache. -static long tmplog_offset = 0; +static off_t tmplog_offset = 0; static void copy_log_file(const char* source, const char* destination, bool append) { FILE* dest_fp = fopen_path(destination, append ? "a" : "w"); if (dest_fp == nullptr) { - LOGE("Can't open %s\n", destination); + PLOG(ERROR) << "Can't open " << destination; } else { FILE* source_fp = fopen(source, "r"); if (source_fp != nullptr) { if (append) { - fseek(source_fp, tmplog_offset, SEEK_SET); // Since last write + fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write } char buf[4096]; size_t bytes; @@ -442,7 +434,7 @@ static void copy_log_file(const char* source, const char* destination, bool appe fwrite(buf, 1, bytes, dest_fp); } if (append) { - tmplog_offset = ftell(source_fp); + tmplog_offset = ftello(source_fp); } check_and_fclose(source_fp, source); } @@ -450,37 +442,6 @@ static void copy_log_file(const char* source, const char* destination, bool appe } } -// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. -// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. -// Overwrite any existing last_log.$max and last_kmsg.$max. -static void rotate_logs(int max) { - // Logs should only be rotated once. - static bool rotated = false; - if (rotated) { - return; - } - rotated = true; - ensure_path_mounted(LAST_LOG_FILE); - ensure_path_mounted(LAST_KMSG_FILE); - - for (int i = max-1; i >= 0; --i) { - std::string old_log = android::base::StringPrintf("%s", LAST_LOG_FILE); - if (i > 0) { - old_log += "." + std::to_string(i); - } - std::string new_log = android::base::StringPrintf("%s.%d", LAST_LOG_FILE, i+1); - // Ignore errors if old_log doesn't exist. - rename(old_log.c_str(), new_log.c_str()); - - std::string old_kmsg = android::base::StringPrintf("%s", LAST_KMSG_FILE); - if (i > 0) { - old_kmsg += "." + std::to_string(i); - } - std::string new_kmsg = android::base::StringPrintf("%s.%d", LAST_KMSG_FILE, i+1); - rename(old_kmsg.c_str(), new_kmsg.c_str()); - } -} - static void copy_logs() { // We only rotate and record the log of the current session if there are // actual attempts to modify the flash, such as wipes, installs from BCB @@ -499,7 +460,9 @@ static void copy_logs() { return; } - rotate_logs(KEEP_LOG_COUNT); + ensure_path_mounted(LAST_LOG_FILE); + ensure_path_mounted(LAST_KMSG_FILE); + rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE); // Copy logs to cache so the system can find out what happened. copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true); @@ -507,60 +470,43 @@ static void copy_logs() { copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false); save_kernel_log(LAST_KMSG_FILE); chmod(LOG_FILE, 0600); - chown(LOG_FILE, 1000, 1000); // system user + chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM); chmod(LAST_KMSG_FILE, 0600); - chown(LAST_KMSG_FILE, 1000, 1000); // system user + chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM); chmod(LAST_LOG_FILE, 0640); chmod(LAST_INSTALL_FILE, 0644); sync(); } // clear the recovery command and prepare to boot a (hopefully working) system, -// copy our log file to cache as well (for the system to read), and -// record any intent we were asked to communicate back to the system. -// this function is idempotent: call it as many times as you like. -static void -finish_recovery(const char *send_intent) { - // By this point, we're ready to return to the main system... - if (send_intent != NULL && has_cache) { - FILE *fp = fopen_path(INTENT_FILE, "w"); - if (fp == NULL) { - LOGE("Can't open %s\n", INTENT_FILE); - } else { - fputs(send_intent, fp); - check_and_fclose(fp, INTENT_FILE); - } - } - +// copy our log file to cache as well (for the system to read). This function is +// idempotent: call it as many times as you like. +static void finish_recovery() { // Save the locale to cache, so if recovery is next started up // without a --locale argument (eg, directly from the bootloader) // it will use the last-known locale. - if (locale != NULL) { - size_t len = strlen(locale); - __pmsg_write(LOCALE_FILE, locale, len); - if (has_cache) { - LOGI("Saving locale \"%s\"\n", locale); - FILE* fp = fopen_path(LOCALE_FILE, "w"); - fwrite(locale, 1, len, fp); - fflush(fp); - fsync(fileno(fp)); - check_and_fclose(fp, LOCALE_FILE); + if (!locale.empty() && has_cache) { + LOG(INFO) << "Saving locale \"" << locale << "\""; + + FILE* fp = fopen_path(LOCALE_FILE, "w"); + if (!android::base::WriteStringToFd(locale, fileno(fp))) { + PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE; } + check_and_fclose(fp, LOCALE_FILE); } copy_logs(); // Reset to normal system boot so recovery won't cycle indefinitely. - bootloader_message boot = {}; std::string err; - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); + if (!clear_bootloader_message(&err)) { + LOG(ERROR) << "Failed to clear BCB message: " << err; } // Remove the command file, so recovery won't repeat indefinitely. if (has_cache) { if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { - LOGW("Can't unlink %s\n", COMMAND_FILE); + LOG(WARNING) << "Can't unlink " << COMMAND_FILE; } ensure_path_unmounted(CACHE_ROOT); } @@ -568,287 +514,239 @@ finish_recovery(const char *send_intent) { sync(); // For good measure. } -typedef struct _saved_log_file { - char* name; - struct stat st; - unsigned char* data; - struct _saved_log_file* next; -} saved_log_file; +struct saved_log_file { + std::string name; + struct stat sb; + std::string data; +}; static bool erase_volume(const char* volume) { - bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); - bool is_data = (strcmp(volume, DATA_ROOT) == 0); + bool is_cache = (strcmp(volume, CACHE_ROOT) == 0); + bool is_data = (strcmp(volume, DATA_ROOT) == 0); - ui->SetBackground(RecoveryUI::ERASING); - ui->SetProgressType(RecoveryUI::INDETERMINATE); + ui->SetBackground(RecoveryUI::ERASING); + ui->SetProgressType(RecoveryUI::INDETERMINATE); - saved_log_file* head = NULL; - - if (is_cache) { - // If we're reformatting /cache, we load any past logs - // (i.e. "/cache/recovery/last_*") and the current log - // ("/cache/recovery/log") into memory, so we can restore them after - // the reformat. - - ensure_path_mounted(volume); - - DIR* d; - struct dirent* de; - d = opendir(CACHE_LOG_DIR); - if (d) { - char path[PATH_MAX]; - strcpy(path, CACHE_LOG_DIR); - strcat(path, "/"); - int path_len = strlen(path); - while ((de = readdir(d)) != NULL) { - if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { - saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file)); - strcpy(path+path_len, de->d_name); - p->name = strdup(path); - if (stat(path, &(p->st)) == 0) { - // truncate files to 512kb - if (p->st.st_size > (1 << 19)) { - p->st.st_size = 1 << 19; - } - p->data = (unsigned char*) malloc(p->st.st_size); - FILE* f = fopen(path, "rb"); - fread(p->data, 1, p->st.st_size, f); - fclose(f); - p->next = head; - head = p; - } else { - free(p); - } - } - } - closedir(d); - } else { - if (errno != ENOENT) { - printf("opendir failed: %s\n", strerror(errno)); - } - } - } + std::vector<saved_log_file> log_files; + + if (is_cache) { + // If we're reformatting /cache, we load any past logs + // (i.e. "/cache/recovery/last_*") and the current log + // ("/cache/recovery/log") into memory, so we can restore them after + // the reformat. - ui->Print("Formatting %s...\n", volume); + ensure_path_mounted(volume); - ensure_path_unmounted(volume); + struct dirent* de; + std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir); + if (d) { + while ((de = readdir(d.get())) != nullptr) { + if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) { + std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name); + + struct stat sb; + if (stat(path.c_str(), &sb) == 0) { + // truncate files to 512kb + if (sb.st_size > (1 << 19)) { + sb.st_size = 1 << 19; + } - int result; + std::string data(sb.st_size, '\0'); + FILE* f = fopen(path.c_str(), "rb"); + fread(&data[0], 1, data.size(), f); + fclose(f); - if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { - // Create convert_fbe breadcrumb file to signal to init - // to convert to file based encryption, not full disk encryption - if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { - ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); - return true; + log_files.emplace_back(saved_log_file{ path, sb, data }); + } } - FILE* f = fopen(CONVERT_FBE_FILE, "wb"); - if (!f) { - ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); - return true; + } + } else { + if (errno != ENOENT) { + PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR; + } + } + } + + ui->Print("Formatting %s...\n", volume); + + ensure_path_unmounted(volume); + + int result; + + if (is_data && reason && strcmp(reason, "convert_fbe") == 0) { + // Create convert_fbe breadcrumb file to signal to init + // to convert to file based encryption, not full disk encryption + if (mkdir(CONVERT_FBE_DIR, 0700) != 0) { + ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno)); + return true; + } + FILE* f = fopen(CONVERT_FBE_FILE, "wb"); + if (!f) { + ui->Print("Failed to convert to file encryption %s\n", strerror(errno)); + return true; + } + fclose(f); + result = format_volume(volume, CONVERT_FBE_DIR); + remove(CONVERT_FBE_FILE); + rmdir(CONVERT_FBE_DIR); + } else { + result = format_volume(volume); + } + + if (is_cache) { + // Re-create the log dir and write back the log entries. + if (ensure_path_mounted(CACHE_LOG_DIR) == 0 && + dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) { + for (const auto& log : log_files) { + if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid, + log.sb.st_gid)) { + PLOG(ERROR) << "Failed to write to " << log.name; } - fclose(f); - result = format_volume(volume, CONVERT_FBE_DIR); - remove(CONVERT_FBE_FILE); - rmdir(CONVERT_FBE_DIR); + } } else { - result = format_volume(volume); + PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR; } - if (is_cache) { - while (head) { - FILE* f = fopen_path(head->name, "wb"); - if (f) { - fwrite(head->data, 1, head->st.st_size, f); - fclose(f); - chmod(head->name, head->st.st_mode); - chown(head->name, head->st.st_uid, head->st.st_gid); - } - free(head->name); - free(head->data); - saved_log_file* temp = head->next; - free(head); - head = temp; - } - - // Any part of the log we'd copied to cache is now gone. - // Reset the pointer so we copy from the beginning of the temp - // log. - tmplog_offset = 0; - copy_logs(); - } + // Any part of the log we'd copied to cache is now gone. + // Reset the pointer so we copy from the beginning of the temp + // log. + tmplog_offset = 0; + copy_logs(); + } - return (result == 0); + return (result == 0); } -static int -get_menu_selection(const char* const * headers, const char* const * items, - int menu_only, int initial_selection, Device* device) { - // throw away keys pressed previously, so user doesn't - // accidentally trigger menu items. - ui->FlushKeys(); - - ui->StartMenu(headers, items, initial_selection); - int selected = initial_selection; - int chosen_item = -1; - - while (chosen_item < 0) { - int key = ui->WaitKey(); - int visible = ui->IsTextVisible(); - - if (key == -1) { // ui_wait_key() timed out - if (ui->WasTextEverVisible()) { - continue; - } else { - LOGI("timed out waiting for key input; rebooting.\n"); - ui->EndMenu(); - return 0; // XXX fixme - } - } - - int action = device->HandleMenuKey(key, visible); - - if (action < 0) { - switch (action) { - case Device::kHighlightUp: - selected = ui->SelectMenu(--selected); - break; - case Device::kHighlightDown: - selected = ui->SelectMenu(++selected); - break; - case Device::kInvokeItem: - chosen_item = selected; - break; - case Device::kNoAction: - break; - } - } else if (!menu_only) { - chosen_item = action; - } - } - - ui->EndMenu(); - return chosen_item; +// Display a menu with the specified 'headers' and 'items'. Device specific HandleMenuKey() may +// return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only +// a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the +// (non-negative) chosen item number, or -1 if timed out waiting for input. +static int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, + int initial_selection, Device* device) { + // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. + ui->FlushKeys(); + + ui->StartMenu(headers, items, initial_selection); + + int selected = initial_selection; + int chosen_item = -1; + while (chosen_item < 0) { + int key = ui->WaitKey(); + if (key == -1) { // WaitKey() timed out. + if (ui->WasTextEverVisible()) { + continue; + } else { + LOG(INFO) << "Timed out waiting for key input; rebooting."; + ui->EndMenu(); + return -1; + } + } + + bool visible = ui->IsTextVisible(); + int action = device->HandleMenuKey(key, visible); + + if (action < 0) { + switch (action) { + case Device::kHighlightUp: + selected = ui->SelectMenu(--selected); + break; + case Device::kHighlightDown: + selected = ui->SelectMenu(++selected); + break; + case Device::kInvokeItem: + chosen_item = selected; + break; + case Device::kNoAction: + break; + } + } else if (!menu_only) { + chosen_item = action; + } + } + + ui->EndMenu(); + return chosen_item; } -static int compare_string(const void* a, const void* b) { - return strcmp(*(const char**)a, *(const char**)b); -} +// Returns the selected filename, or an empty string. +static std::string browse_directory(const std::string& path, Device* device) { + ensure_path_mounted(path.c_str()); -// Returns a malloc'd path, or NULL. -static char* browse_directory(const char* path, Device* device) { - ensure_path_mounted(path); + std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir); + if (!d) { + PLOG(ERROR) << "error opening " << path; + return ""; + } - DIR* d = opendir(path); - if (d == NULL) { - LOGE("error opening %s: %s\n", path, strerror(errno)); - return NULL; - } + std::vector<std::string> dirs; + std::vector<std::string> zips = { "../" }; // "../" is always the first entry. - int d_size = 0; - int d_alloc = 10; - char** dirs = (char**)malloc(d_alloc * sizeof(char*)); - int z_size = 1; - int z_alloc = 10; - char** zips = (char**)malloc(z_alloc * sizeof(char*)); - zips[0] = strdup("../"); + dirent* de; + while ((de = readdir(d.get())) != nullptr) { + std::string name(de->d_name); - struct dirent* de; - while ((de = readdir(d)) != NULL) { - int name_len = strlen(de->d_name); - - if (de->d_type == DT_DIR) { - // skip "." and ".." entries - if (name_len == 1 && de->d_name[0] == '.') continue; - if (name_len == 2 && de->d_name[0] == '.' && - de->d_name[1] == '.') continue; - - if (d_size >= d_alloc) { - d_alloc *= 2; - dirs = (char**)realloc(dirs, d_alloc * sizeof(char*)); - } - dirs[d_size] = (char*)malloc(name_len + 2); - strcpy(dirs[d_size], de->d_name); - dirs[d_size][name_len] = '/'; - dirs[d_size][name_len+1] = '\0'; - ++d_size; - } else if (de->d_type == DT_REG && - name_len >= 4 && - strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) { - if (z_size >= z_alloc) { - z_alloc *= 2; - zips = (char**)realloc(zips, z_alloc * sizeof(char*)); - } - zips[z_size++] = strdup(de->d_name); - } + if (de->d_type == DT_DIR) { + // Skip "." and ".." entries. + if (name == "." || name == "..") continue; + dirs.push_back(name + "/"); + } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) { + zips.push_back(name); } - closedir(d); + } - qsort(dirs, d_size, sizeof(char*), compare_string); - qsort(zips, z_size, sizeof(char*), compare_string); + std::sort(dirs.begin(), dirs.end()); + std::sort(zips.begin(), zips.end()); - // append dirs to the zips list - if (d_size + z_size + 1 > z_alloc) { - z_alloc = d_size + z_size + 1; - zips = (char**)realloc(zips, z_alloc * sizeof(char*)); - } - memcpy(zips + z_size, dirs, d_size * sizeof(char*)); - free(dirs); - z_size += d_size; - zips[z_size] = NULL; + // Append dirs to the zips list. + zips.insert(zips.end(), dirs.begin(), dirs.end()); - const char* headers[] = { "Choose a package to install:", path, NULL }; + const char* entries[zips.size() + 1]; + entries[zips.size()] = nullptr; + for (size_t i = 0; i < zips.size(); i++) { + entries[i] = zips[i].c_str(); + } - char* result; - int chosen_item = 0; - while (true) { - chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); + const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr }; - char* item = zips[chosen_item]; - int item_len = strlen(item); - if (chosen_item == 0) { // item 0 is always "../" - // go up but continue browsing (if the caller is update_directory) - result = NULL; - break; - } + int chosen_item = 0; + while (true) { + chosen_item = get_menu_selection(headers, entries, true, chosen_item, device); - char new_path[PATH_MAX]; - strlcpy(new_path, path, PATH_MAX); - strlcat(new_path, "/", PATH_MAX); - strlcat(new_path, item, PATH_MAX); - - if (item[item_len-1] == '/') { - // recurse down into a subdirectory - new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' - result = browse_directory(new_path, device); - if (result) break; - } else { - // selected a zip file: return the malloc'd path to the caller. - result = strdup(new_path); - break; - } + const std::string& item = zips[chosen_item]; + if (chosen_item == 0) { + // Go up but continue browsing (if the caller is browse_directory). + return ""; } - for (int i = 0; i < z_size; ++i) free(zips[i]); - free(zips); + std::string new_path = path + "/" + item; + if (new_path.back() == '/') { + // Recurse down into a subdirectory. + new_path.pop_back(); + std::string result = browse_directory(new_path, device); + if (!result.empty()) return result; + } else { + // Selected a zip file: return the path to the caller. + return new_path; + } + } - return result; + // Unreachable. } static bool yes_no(Device* device, const char* question1, const char* question2) { const char* headers[] = { question1, question2, NULL }; const char* items[] = { " No", " Yes", NULL }; - int chosen_item = get_menu_selection(headers, items, 1, 0, device); + int chosen_item = get_menu_selection(headers, items, true, 0, device); return (chosen_item == 1); } -// Return true on success. -static bool wipe_data(int should_confirm, Device* device) { - if (should_confirm && !yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!")) { - return false; - } +static bool ask_to_wipe_data(Device* device) { + return yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!"); +} +// Return true on success. +static bool wipe_data(Device* device) { modified_flash = true; ui->Print("\n-- Wiping data...\n"); @@ -861,6 +759,30 @@ static bool wipe_data(int should_confirm, Device* device) { return success; } +static bool prompt_and_wipe_data(Device* device) { + const char* const headers[] = { + "Can't load Android system. Your data may be corrupt.", + "If you continue to get this message, you may need to", + "perform a factory data reset and erase all user data", + "stored on this device.", + NULL + }; + const char* const items[] = { + "Try again", + "Factory data reset", + NULL + }; + for (;;) { + int chosen_item = get_menu_selection(headers, items, true, 0, device); + if (chosen_item != 1) { + return true; // Just reboot, no wipe; not a failure, user asked for it + } + if (ask_to_wipe_data(device)) { + return wipe_data(device); + } + } +} + // Return true on success. static bool wipe_cache(bool should_confirm, Device* device) { if (!has_cache) { @@ -880,47 +802,45 @@ static bool wipe_cache(bool should_confirm, Device* device) { return success; } -// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. -// Otherwise, it goes with BLKDISCARD (if device supports BLKDISCARDZEROES) or -// BLKZEROOUT. +// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with +// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT. static bool secure_wipe_partition(const std::string& partition) { - unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); - if (fd.get() == -1) { - LOGE("failed to open \"%s\": %s\n", partition.c_str(), strerror(errno)); - return false; - } + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open \"" << partition << "\""; + return false; + } - uint64_t range[2] = {0, 0}; - if (ioctl(fd.get(), BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { - LOGE("failed to get partition size: %s\n", strerror(errno)); + uint64_t range[2] = { 0, 0 }; + if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) { + PLOG(ERROR) << "Failed to get partition size"; + return false; + } + LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1]; + + LOG(INFO) << " Trying BLKSECDISCARD..."; + if (ioctl(fd, BLKSECDISCARD, &range) == -1) { + PLOG(WARNING) << " Failed"; + + // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. + unsigned int zeroes; + if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { + LOG(INFO) << " Trying BLKDISCARD..."; + if (ioctl(fd, BLKDISCARD, &range) == -1) { + PLOG(ERROR) << " Failed"; return false; + } + } else { + LOG(INFO) << " Trying BLKZEROOUT..."; + if (ioctl(fd, BLKZEROOUT, &range) == -1) { + PLOG(ERROR) << " Failed"; + return false; + } } - printf("Secure-wiping \"%s\" from %" PRIu64 " to %" PRIu64 ".\n", - partition.c_str(), range[0], range[1]); - - printf("Trying BLKSECDISCARD...\t"); - if (ioctl(fd.get(), BLKSECDISCARD, &range) == -1) { - printf("failed: %s\n", strerror(errno)); - - // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT. - unsigned int zeroes; - if (ioctl(fd.get(), BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) { - printf("Trying BLKDISCARD...\t"); - if (ioctl(fd.get(), BLKDISCARD, &range) == -1) { - printf("failed: %s\n", strerror(errno)); - return false; - } - } else { - printf("Trying BLKZEROOUT...\t"); - if (ioctl(fd.get(), BLKZEROOUT, &range) == -1) { - printf("failed: %s\n", strerror(errno)); - return false; - } - } - } + } - printf("done\n"); - return true; + LOG(INFO) << " Done"; + return true; } // Check if the wipe package matches expectation: @@ -928,35 +848,35 @@ static bool secure_wipe_partition(const std::string& partition) { // 2. check metadata (ota-type, pre-device and serial number if having one). static bool check_wipe_package(size_t wipe_package_size) { if (wipe_package_size == 0) { - LOGE("wipe_package_size is zero.\n"); + LOG(ERROR) << "wipe_package_size is zero"; return false; } std::string wipe_package; std::string err_str; if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) { - LOGE("Failed to read wipe package: %s\n", err_str.c_str()); + PLOG(ERROR) << "Failed to read wipe package"; return false; } if (!verify_package(reinterpret_cast<const unsigned char*>(wipe_package.data()), wipe_package.size())) { - LOGE("Failed to verify package.\n"); + LOG(ERROR) << "Failed to verify package"; return false; } // Extract metadata - ZipArchive zip; - int err = mzOpenZipArchive(reinterpret_cast<unsigned char*>(&wipe_package[0]), - wipe_package.size(), &zip); + ZipArchiveHandle zip; + int err = OpenArchiveFromMemory(static_cast<void*>(&wipe_package[0]), wipe_package.size(), + "wipe_package", &zip); if (err != 0) { - LOGE("Can't open wipe package: %s\n", err != -1 ? strerror(err) : "bad"); + LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err); return false; } std::string metadata; - if (!read_metadata_from_package(&zip, &metadata)) { - mzCloseZipArchive(&zip); + if (!read_metadata_from_package(zip, &metadata)) { + CloseArchive(zip); return false; } - mzCloseZipArchive(&zip); + CloseArchive(zip); // Check metadata std::vector<std::string> lines = android::base::Split(metadata, "\n"); @@ -969,13 +889,11 @@ static bool check_wipe_package(size_t wipe_package_size) { ota_type_matched = true; } else if (android::base::StartsWith(line, "pre-device=")) { std::string device_type = line.substr(strlen("pre-device=")); - char real_device_type[PROPERTY_VALUE_MAX]; - property_get("ro.build.product", real_device_type, ""); + std::string real_device_type = android::base::GetProperty("ro.build.product", ""); device_type_matched = (device_type == real_device_type); } else if (android::base::StartsWith(line, "serialno=")) { std::string serial_no = line.substr(strlen("serialno=")); - char real_serial_no[PROPERTY_VALUE_MAX]; - property_get("ro.serialno", real_serial_no, ""); + std::string real_serial_no = android::base::GetProperty("ro.serialno", ""); has_serial_number = true; serial_number_matched = (serial_no == real_serial_no); } @@ -990,12 +908,12 @@ static bool wipe_ab_device(size_t wipe_package_size) { ui->SetProgressType(RecoveryUI::INDETERMINATE); if (!check_wipe_package(wipe_package_size)) { - LOGE("Failed to verify wipe package\n"); + LOG(ERROR) << "Failed to verify wipe package"; return false; } std::string partition_list; if (!android::base::ReadFileToString(RECOVERY_WIPE, &partition_list)) { - LOGE("failed to read \"%s\".\n", RECOVERY_WIPE); + LOG(ERROR) << "failed to read \"" << RECOVERY_WIPE << "\""; return false; } @@ -1014,98 +932,94 @@ static bool wipe_ab_device(size_t wipe_package_size) { } static void choose_recovery_file(Device* device) { - // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry - char* entries[1 + KEEP_LOG_COUNT * 2 + 1]; - memset(entries, 0, sizeof(entries)); - - unsigned int n = 0; - - if (has_cache) { - // Add LAST_LOG_FILE + LAST_LOG_FILE.x - // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x - for (int i = 0; i < KEEP_LOG_COUNT; i++) { - char* log_file; - int ret; - ret = (i == 0) ? asprintf(&log_file, "%s", LAST_LOG_FILE) : - asprintf(&log_file, "%s.%d", LAST_LOG_FILE, i); - if (ret == -1) { - // memory allocation failure - return early. Should never happen. - return; - } - if ((ensure_path_mounted(log_file) != 0) || (access(log_file, R_OK) == -1)) { - free(log_file); - } else { - entries[n++] = log_file; - } - - char* kmsg_file; - ret = (i == 0) ? asprintf(&kmsg_file, "%s", LAST_KMSG_FILE) : - asprintf(&kmsg_file, "%s.%d", LAST_KMSG_FILE, i); - if (ret == -1) { - // memory allocation failure - return early. Should never happen. - return; - } - if ((ensure_path_mounted(kmsg_file) != 0) || (access(kmsg_file, R_OK) == -1)) { - free(kmsg_file); - } else { - entries[n++] = kmsg_file; - } - } - } else { - // If cache partition is not found, view /tmp/recovery.log instead. - ui->Print("No /cache partition found.\n"); - if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { - return; - } else{ - entries[n++] = strdup(TEMPORARY_LOG_FILE); + std::vector<std::string> entries; + if (has_cache) { + for (int i = 0; i < KEEP_LOG_COUNT; i++) { + auto add_to_entries = [&](const char* filename) { + std::string log_file(filename); + if (i > 0) { + log_file += "." + std::to_string(i); } - } - entries[n++] = strdup("Back"); - - const char* headers[] = { "Select file to view", nullptr }; + if (ensure_path_mounted(log_file.c_str()) == 0 && access(log_file.c_str(), R_OK) == 0) { + entries.push_back(std::move(log_file)); + } + }; - while (true) { - int chosen_item = get_menu_selection(headers, entries, 1, 0, device); - if (strcmp(entries[chosen_item], "Back") == 0) break; + // Add LAST_LOG_FILE + LAST_LOG_FILE.x + add_to_entries(LAST_LOG_FILE); - ui->ShowFile(entries[chosen_item]); + // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x + add_to_entries(LAST_KMSG_FILE); } - - for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) { - free(entries[i]); + } else { + // If cache partition is not found, view /tmp/recovery.log instead. + if (access(TEMPORARY_LOG_FILE, R_OK) == -1) { + return; + } else { + entries.push_back(TEMPORARY_LOG_FILE); } -} + } -static void run_graphics_test(Device* device) { - // Switch to graphics screen. - ui->ShowText(false); + entries.push_back("Back"); - ui->SetProgressType(RecoveryUI::INDETERMINATE); - ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - sleep(1); + std::vector<const char*> menu_entries(entries.size()); + std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(), + [](const std::string& entry) { return entry.c_str(); }); + menu_entries.push_back(nullptr); - ui->SetBackground(RecoveryUI::ERROR); - sleep(1); + const char* headers[] = { "Select file to view", nullptr }; - ui->SetBackground(RecoveryUI::NO_COMMAND); - sleep(1); + int chosen_item = 0; + while (true) { + chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device); + if (entries[chosen_item] == "Back") break; - ui->SetBackground(RecoveryUI::ERASING); - sleep(1); - - ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); - - ui->SetProgressType(RecoveryUI::DETERMINATE); - ui->ShowProgress(1.0, 10.0); - float fraction = 0.0; - for (size_t i = 0; i < 100; ++i) { - fraction += .01; - ui->SetProgress(fraction); - usleep(100000); - } + ui->ShowFile(entries[chosen_item].c_str()); + } +} - ui->ShowText(true); +static void run_graphics_test() { + // Switch to graphics screen. + ui->ShowText(false); + + ui->SetProgressType(RecoveryUI::INDETERMINATE); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + + ui->SetBackground(RecoveryUI::ERROR); + sleep(1); + + ui->SetBackground(RecoveryUI::NO_COMMAND); + sleep(1); + + ui->SetBackground(RecoveryUI::ERASING); + sleep(1); + + // Calling SetBackground() after SetStage() to trigger a redraw. + ui->SetStage(1, 3); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + ui->SetStage(2, 3); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + ui->SetStage(3, 3); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + sleep(1); + + ui->SetStage(-1, -1); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + + ui->SetProgressType(RecoveryUI::DETERMINATE); + ui->ShowProgress(1.0, 10.0); + float fraction = 0.0; + for (size_t i = 0; i < 100; ++i) { + fraction += .01; + ui->SetProgress(fraction); + usleep(100000); + } + + ui->ShowText(true); } // How long (in seconds) we wait for the fuse-provided package file to @@ -1120,14 +1034,14 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { return INSTALL_ERROR; } - char* path = browse_directory(SDCARD_ROOT, device); - if (path == NULL) { + std::string path = browse_directory(SDCARD_ROOT, device); + if (path.empty()) { ui->Print("\n-- No package file selected.\n"); ensure_path_unmounted(SDCARD_ROOT); return INSTALL_ERROR; } - ui->Print("\n-- Install %s ...\n", path); + ui->Print("\n-- Install %s ...\n", path.c_str()); set_sdcard_update_bootloader_message(); // We used to use fuse in a thread as opposed to a process. Since accessing @@ -1135,7 +1049,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { // to deadlock when a page fault occurs. (Bug: 26313124) pid_t child; if ((child = fork()) == 0) { - bool status = start_sdcard_fuse(path); + bool status = start_sdcard_fuse(path.c_str()); _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -1158,7 +1072,7 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { sleep(1); continue; } else { - LOGE("Timed out waiting for the fuse-provided package.\n"); + LOG(ERROR) << "Timed out waiting for the fuse-provided package."; result = INSTALL_ERROR; kill(child, SIGKILL); break; @@ -1180,118 +1094,116 @@ static int apply_from_sdcard(Device* device, bool* wipe_cache) { } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOGE("Error exit from the fuse process: %d\n", WEXITSTATUS(status)); + LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status); } ensure_path_unmounted(SDCARD_ROOT); return result; } -// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION -// means to take the default, which is to reboot or shutdown depending -// on if the --shutdown_after flag was passed to recovery. -static Device::BuiltinAction -prompt_and_wait(Device* device, int status) { - for (;;) { - finish_recovery(NULL); - switch (status) { - case INSTALL_SUCCESS: - case INSTALL_NONE: - ui->SetBackground(RecoveryUI::NO_COMMAND); - break; - - case INSTALL_ERROR: - case INSTALL_CORRUPT: - ui->SetBackground(RecoveryUI::ERROR); - break; - } - ui->SetProgressType(RecoveryUI::EMPTY); - - int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device); - - // device-specific code may take some action here. It may - // return one of the core actions handled in the switch - // statement below. - Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); +// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, +// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. +static Device::BuiltinAction prompt_and_wait(Device* device, int status) { + for (;;) { + finish_recovery(); + switch (status) { + case INSTALL_SUCCESS: + case INSTALL_NONE: + ui->SetBackground(RecoveryUI::NO_COMMAND); + break; - bool should_wipe_cache = false; - switch (chosen_action) { - case Device::NO_ACTION: - break; + case INSTALL_ERROR: + case INSTALL_CORRUPT: + ui->SetBackground(RecoveryUI::ERROR); + break; + } + ui->SetProgressType(RecoveryUI::EMPTY); - case Device::REBOOT: - case Device::SHUTDOWN: - case Device::REBOOT_BOOTLOADER: - return chosen_action; + int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); - case Device::WIPE_DATA: - wipe_data(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + // Device-specific code may take some action here. It may return one of the core actions + // handled in the switch statement below. + Device::BuiltinAction chosen_action = + (chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item); - case Device::WIPE_CACHE: - wipe_cache(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + bool should_wipe_cache = false; + switch (chosen_action) { + case Device::NO_ACTION: + break; - case Device::APPLY_ADB_SIDELOAD: - case Device::APPLY_SDCARD: - { - bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); - if (adb) { - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); - } else { - status = apply_from_sdcard(device, &should_wipe_cache); - } + case Device::REBOOT: + case Device::SHUTDOWN: + case Device::REBOOT_BOOTLOADER: + return chosen_action; - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; - } - } + case Device::WIPE_DATA: + if (ui->IsTextVisible()) { + if (ask_to_wipe_data(device)) { + wipe_data(device); + } + } else { + wipe_data(device); + return Device::NO_ACTION; + } + break; - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } else if (!ui->IsTextVisible()) { - return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); - } - } - break; + case Device::WIPE_CACHE: + wipe_cache(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - case Device::VIEW_RECOVERY_LOGS: - choose_recovery_file(device); - break; + case Device::APPLY_ADB_SIDELOAD: + case Device::APPLY_SDCARD: + { + bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); + if (adb) { + status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); + } else { + status = apply_from_sdcard(device, &should_wipe_cache); + } + + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } + } + + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + copy_logs(); + } else if (!ui->IsTextVisible()) { + return Device::NO_ACTION; // reboot if logs aren't visible + } else { + ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); + } + } + break; - case Device::RUN_GRAPHICS_TEST: - run_graphics_test(device); - break; + case Device::VIEW_RECOVERY_LOGS: + choose_recovery_file(device); + break; - case Device::MOUNT_SYSTEM: - char system_root_image[PROPERTY_VALUE_MAX]; - property_get("ro.build.system_root_image", system_root_image, ""); - - // For a system image built with the root directory (i.e. - // system_root_image == "true"), we mount it to /system_root, and symlink /system - // to /system_root/system to make adb shell work (the symlink is created through - // the build system). - // Bug: 22855115 - if (strcmp(system_root_image, "true") == 0) { - if (ensure_path_mounted_at("/", "/system_root") != -1) { - ui->Print("Mounted /system.\n"); - } - } else { - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); - } - } + case Device::RUN_GRAPHICS_TEST: + run_graphics_test(); + break; - break; + case Device::MOUNT_SYSTEM: + // For a system image built with the root directory (i.e. system_root_image == "true"), we + // mount it to /system_root, and symlink /system to /system_root/system to make adb shell + // work (the symlink is created through the build system). (Bug: 22855115) + if (android::base::GetBoolProperty("ro.build.system_root_image", false)) { + if (ensure_path_mounted_at("/", "/system_root") != -1) { + ui->Print("Mounted /system.\n"); + } + } else { + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } } + break; } + } } static void @@ -1299,40 +1211,44 @@ print_property(const char *key, const char *name, void *cookie) { printf("%s=%s\n", key, name); } -static void -load_locale_from_cache() { - FILE* fp = fopen_path(LOCALE_FILE, "r"); - char buffer[80]; - if (fp != NULL) { - fgets(buffer, sizeof(buffer), fp); - int j = 0; - unsigned int i; - for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) { - if (!isspace(buffer[i])) { - buffer[j++] = buffer[i]; - } - } - buffer[j] = 0; - locale = strdup(buffer); - check_and_fclose(fp, LOCALE_FILE); +static std::string load_locale_from_cache() { + if (ensure_path_mounted(LOCALE_FILE) != 0) { + LOG(ERROR) << "Can't mount " << LOCALE_FILE; + return ""; } -} -static RecoveryUI* gCurrentUI = NULL; + std::string content; + if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { + PLOG(ERROR) << "Can't read " << LOCALE_FILE; + return ""; + } -void -ui_print(const char* format, ...) { - char buffer[256]; + return android::base::Trim(content); +} +void ui_print(const char* format, ...) { + std::string buffer; va_list ap; va_start(ap, format); - vsnprintf(buffer, sizeof(buffer), format, ap); + android::base::StringAppendV(&buffer, format, ap); va_end(ap); - if (gCurrentUI != NULL) { - gCurrentUI->Print("%s", buffer); + if (ui != nullptr) { + ui->Print("%s", buffer.c_str()); + } else { + fputs(buffer.c_str(), stdout); + } +} + +static constexpr char log_characters[] = "VDIWEF"; + +void UiLogger(android::base::LogId id, android::base::LogSeverity severity, + const char* tag, const char* file, unsigned int line, + const char* message) { + if (severity >= android::base::ERROR && ui != nullptr) { + ui->Print("E:%s\n", message); } else { - fputs(buffer, stdout); + fprintf(stdout, "%c:%s\n", log_characters[severity], message); } } @@ -1390,42 +1306,32 @@ static bool is_battery_ok() { } } -static void set_retry_bootloader_message(int retry_count, int argc, char** argv) { - bootloader_message boot = {}; - strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); - strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery)); - - for (int i = 1; i < argc; ++i) { - if (strstr(argv[i], "retry_count") == nullptr) { - strlcat(boot.recovery, argv[i], sizeof(boot.recovery)); - strlcat(boot.recovery, "\n", sizeof(boot.recovery)); - } +static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) { + std::vector<std::string> options; + for (const auto& arg : args) { + if (!android::base::StartsWith(arg, "--retry_count")) { + options.push_back(arg); } + } - // Initialize counter to 1 if it's not in BCB, otherwise increment it by 1. - if (retry_count == 0) { - strlcat(boot.recovery, "--retry_count=1\n", sizeof(boot.recovery)); - } else { - char buffer[20]; - snprintf(buffer, sizeof(buffer), "--retry_count=%d\n", retry_count+1); - strlcat(boot.recovery, buffer, sizeof(boot.recovery)); - } - std::string err; - if (!write_bootloader_message(boot, &err)) { - LOGE("%s\n", err.c_str()); - } + // Increment the retry counter by 1. + options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count + 1)); + std::string err; + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << err; + } } static bool bootreason_in_blacklist() { - char bootreason[PROPERTY_VALUE_MAX]; - if (property_get("ro.boot.bootreason", bootreason, nullptr) > 0) { - for (const auto& str : bootreason_blacklist) { - if (strcasecmp(str.c_str(), bootreason) == 0) { - return true; - } - } - } - return false; + std::string bootreason = android::base::GetProperty("ro.boot.bootreason", ""); + if (!bootreason.empty()) { + for (const auto& str : bootreason_blacklist) { + if (strcasecmp(str.c_str(), bootreason.c_str()) == 0) { + return true; + } + } + } + return false; } static void log_failure_code(ErrorCode code, const char *update_package) { @@ -1436,68 +1342,23 @@ static void log_failure_code(ErrorCode code, const char *update_package) { }; std::string log_content = android::base::Join(log_buffer, "\n"); if (!android::base::WriteStringToFile(log_content, TEMPORARY_INSTALL_FILE)) { - LOGE("failed to write %s: %s\n", TEMPORARY_INSTALL_FILE, strerror(errno)); + PLOG(ERROR) << "failed to write " << TEMPORARY_INSTALL_FILE; } // Also write the info into last_log. - LOGI("%s\n", log_content.c_str()); -} - -static ssize_t logbasename( - log_id_t /* logId */, - char /* prio */, - const char *filename, - const char * /* buf */, size_t len, - void *arg) { - if (strstr(LAST_KMSG_FILE, filename) || - strstr(LAST_LOG_FILE, filename)) { - bool *doRotate = reinterpret_cast<bool *>(arg); - *doRotate = true; - } - return len; -} - -static ssize_t logrotate( - log_id_t logId, - char prio, - const char *filename, - const char *buf, size_t len, - void *arg) { - bool *doRotate = reinterpret_cast<bool *>(arg); - if (!*doRotate) { - return __android_log_pmsg_file_write(logId, prio, filename, buf, len); - } - - std::string name(filename); - size_t dot = name.find_last_of("."); - std::string sub = name.substr(0, dot); - - if (!strstr(LAST_KMSG_FILE, sub.c_str()) && - !strstr(LAST_LOG_FILE, sub.c_str())) { - return __android_log_pmsg_file_write(logId, prio, filename, buf, len); - } - - // filename rotation - if (dot == std::string::npos) { - name += ".1"; - } else { - std::string number = name.substr(dot + 1); - if (!isdigit(number.data()[0])) { - name += ".1"; - } else { - unsigned long long i = std::stoull(number); - name = sub + "." + std::to_string(i + 1); - } - } - - return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); + LOG(INFO) << log_content; } int main(int argc, char **argv) { + // We don't have logcat yet under recovery; so we'll print error on screen and + // log to stdout (which is redirected to recovery.log) as we used to do. + android::base::InitLogging(argv, &UiLogger); + // Take last pmsg contents and rewrite it to the current pmsg session. static const char filter[] = "recovery/"; // Do we need to rotate? bool doRotate = false; + __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate); @@ -1514,7 +1375,7 @@ int main(int argc, char **argv) { // only way recovery should be run with this argument is when it // starts a copy of itself from the apply_from_adb() function. if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { - adb_server_main(0, DEFAULT_ADB_PORT, -1); + minadbd_main(); return 0; } @@ -1529,11 +1390,14 @@ int main(int argc, char **argv) { load_volume_table(); has_cache = volume_for_path(CACHE_ROOT) != nullptr; - get_args(&argc, &argv); + std::vector<std::string> args = get_args(argc, argv); + std::vector<char*> args_to_parse(args.size()); + std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), + [](const std::string& arg) { return const_cast<char*>(arg.c_str()); }); - const char *send_intent = NULL; const char *update_package = NULL; bool should_wipe_data = false; + bool should_prompt_and_wipe_data = false; bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; @@ -1547,9 +1411,9 @@ int main(int argc, char **argv) { int arg; int option_index; - while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { + while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS, + &option_index)) != -1) { switch (arg) { - case 'i': send_intent = optarg; break; case 'n': android::base::ParseInt(optarg, &retry_count, 0); break; case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; @@ -1559,64 +1423,67 @@ int main(int argc, char **argv) { case 'a': sideload = true; sideload_auto_reboot = true; break; case 'x': just_exit = true; break; case 'l': locale = optarg; break; - case 'g': { - if (stage == NULL || *stage == '\0') { - char buffer[20] = "1/"; - strncat(buffer, optarg, sizeof(buffer)-3); - stage = strdup(buffer); - } - break; - } case 'p': shutdown_after = true; break; case 'r': reason = optarg; break; case 'e': security_update = true; break; case 0: { - if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) { + std::string option = OPTIONS[option_index].name; + if (option == "wipe_ab") { should_wipe_ab = true; - break; - } else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) { + } else if (option == "wipe_package_size") { android::base::ParseUint(optarg, &wipe_package_size); - break; + } else if (option == "prompt_and_wipe_data") { + should_prompt_and_wipe_data = true; } break; } case '?': - LOGE("Invalid command argument\n"); + LOG(ERROR) << "Invalid command argument"; continue; } } - if (locale == nullptr && has_cache) { - load_locale_from_cache(); + if (locale.empty()) { + if (has_cache) { + locale = load_locale_from_cache(); + } + + if (locale.empty()) { + locale = DEFAULT_LOCALE; + } } - printf("locale is [%s]\n", locale); - printf("stage is [%s]\n", stage); + + printf("locale is [%s]\n", locale.c_str()); + printf("stage is [%s]\n", stage.c_str()); printf("reason is [%s]\n", reason); Device* device = make_device(); - ui = device->GetUI(); - gCurrentUI = ui; + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + printf("Quiescent recovery mode.\n"); + ui = new StubRecoveryUI(); + } else { + ui = device->GetUI(); + + if (!ui->Init(locale)) { + printf("Failed to initialize UI, use stub UI instead.\n"); + ui = new StubRecoveryUI(); + } + } - ui->SetLocale(locale); - ui->Init(); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); int st_cur, st_max; - if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) { + if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); - struct selinux_opt seopts[] = { - { SELABEL_OPT_PATH, "/file_contexts" } - }; - - sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); - + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); if (!sehandle) { ui->Print("Warning: No file_contexts\n"); } @@ -1624,30 +1491,10 @@ int main(int argc, char **argv) { device->StartRecovery(); printf("Command:"); - for (arg = 0; arg < argc; arg++) { - printf(" \"%s\"", argv[arg]); - } - printf("\n"); - - if (update_package) { - // For backwards compatibility on the cache partition only, if - // we're given an old 'root' path "CACHE:foo", change it to - // "/cache/foo". - if (strncmp(update_package, "CACHE:", 6) == 0) { - int len = strlen(update_package) + 10; - char* modified_path = (char*)malloc(len); - if (modified_path) { - strlcpy(modified_path, "/cache/", len); - strlcat(modified_path, update_package+6, len); - printf("(replacing path \"%s\" with \"%s\")\n", - update_package, modified_path); - update_package = modified_path; - } - else - printf("modified_path allocation failed\n"); - } + for (const auto& arg : args) { + printf(" \"%s\"", arg.c_str()); } - printf("\n"); + printf("\n\n"); property_list(print_property, NULL); printf("\n"); @@ -1685,13 +1532,12 @@ int main(int argc, char **argv) { // times before we abandon this OTA update. if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { copy_logs(); - set_retry_bootloader_message(retry_count, argc, argv); + set_retry_bootloader_message(retry_count, args); // Print retry count on screen. ui->Print("Retry attempt %d\n", retry_count); // Reboot and retry the update - int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); - if (ret < 0) { + if (!reboot("reboot,recovery")) { ui->Print("Reboot failed\n"); } else { while (true) { @@ -1708,9 +1554,16 @@ int main(int argc, char **argv) { } } } else if (should_wipe_data) { - if (!wipe_data(false, device)) { + if (!wipe_data(device)) { + status = INSTALL_ERROR; + } + } else if (should_prompt_and_wipe_data) { + ui->ShowText(true); + ui->SetBackground(RecoveryUI::ERROR); + if (!prompt_and_wipe_data(device)) { status = INSTALL_ERROR; } + ui->ShowText(false); } else if (should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; @@ -1766,26 +1619,26 @@ int main(int argc, char **argv) { } // Save logs and clean up before rebooting or shutting down. - finish_recovery(send_intent); + finish_recovery(); switch (after) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); - property_set(ANDROID_RB_PROPERTY, "shutdown,"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); break; case Device::REBOOT_BOOTLOADER: ui->Print("Rebooting to bootloader...\n"); - property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; default: ui->Print("Rebooting...\n"); - property_set(ANDROID_RB_PROPERTY, "reboot,"); + reboot("reboot,"); break; } while (true) { - pause(); + pause(); } // Should be unreachable. return EXIT_SUCCESS; diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png Binary files differindex e500d0268..0982544d2 100644 --- a/res-hdpi/images/erasing_text.png +++ b/res-hdpi/images/erasing_text.png diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png Binary files differindex 9a3597b82..3a06f6eb1 100644 --- a/res-hdpi/images/error_text.png +++ b/res-hdpi/images/error_text.png diff --git a/res-hdpi/images/installing_security_text.png b/res-hdpi/images/installing_security_text.png Binary files differindex 76e747410..b1acd2336 100644 --- a/res-hdpi/images/installing_security_text.png +++ b/res-hdpi/images/installing_security_text.png diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png Binary files differindex 5d2a5fa0c..f0f5d8b6c 100644 --- a/res-hdpi/images/installing_text.png +++ b/res-hdpi/images/installing_text.png diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png Binary files differindex a567ad141..def503678 100644 --- a/res-hdpi/images/no_command_text.png +++ b/res-hdpi/images/no_command_text.png diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png Binary files differindex ad68a1941..82b4461ba 100644 --- a/res-mdpi/images/erasing_text.png +++ b/res-mdpi/images/erasing_text.png diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png Binary files differindex 8ea5acb20..adb45131f 100644 --- a/res-mdpi/images/error_text.png +++ b/res-mdpi/images/error_text.png diff --git a/res-mdpi/images/installing_security_text.png b/res-mdpi/images/installing_security_text.png Binary files differindex 615b9b73f..54e556448 100644 --- a/res-mdpi/images/installing_security_text.png +++ b/res-mdpi/images/installing_security_text.png diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png Binary files differindex 6cf8716b5..d42331820 100644 --- a/res-mdpi/images/installing_text.png +++ b/res-mdpi/images/installing_text.png diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png Binary files differindex 5cc6b3715..cd77ff457 100644 --- a/res-mdpi/images/no_command_text.png +++ b/res-mdpi/images/no_command_text.png diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png Binary files differindex 01176e8cc..333edbe27 100644 --- a/res-xhdpi/images/erasing_text.png +++ b/res-xhdpi/images/erasing_text.png diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png Binary files differindex 4f890fa59..e26258438 100644 --- a/res-xhdpi/images/error_text.png +++ b/res-xhdpi/images/error_text.png diff --git a/res-xhdpi/images/installing_security_text.png b/res-xhdpi/images/installing_security_text.png Binary files differindex 6192832e8..e0f0f3ea7 100644 --- a/res-xhdpi/images/installing_security_text.png +++ b/res-xhdpi/images/installing_security_text.png diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png Binary files differindex 441352949..a7e67f512 100644 --- a/res-xhdpi/images/installing_text.png +++ b/res-xhdpi/images/installing_text.png diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png Binary files differindex 9f7403753..13aef7b71 100644 --- a/res-xhdpi/images/no_command_text.png +++ b/res-xhdpi/images/no_command_text.png diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png Binary files differindex b8653eb95..80e7c475e 100644 --- a/res-xxhdpi/images/erasing_text.png +++ b/res-xxhdpi/images/erasing_text.png diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png Binary files differindex d77ee5085..32a1965b8 100644 --- a/res-xxhdpi/images/error_text.png +++ b/res-xxhdpi/images/error_text.png diff --git a/res-xxhdpi/images/installing_security_text.png b/res-xxhdpi/images/installing_security_text.png Binary files differindex ca0c191d4..c53c9ac21 100644 --- a/res-xxhdpi/images/installing_security_text.png +++ b/res-xxhdpi/images/installing_security_text.png diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png Binary files differindex a76a174f0..38b18d20d 100644 --- a/res-xxhdpi/images/installing_text.png +++ b/res-xxhdpi/images/installing_text.png diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png Binary files differindex 5e363e3a5..a0666d8dc 100644 --- a/res-xxhdpi/images/no_command_text.png +++ b/res-xxhdpi/images/no_command_text.png diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png Binary files differindex f4a466118..4f7b37b51 100644 --- a/res-xxxhdpi/images/erasing_text.png +++ b/res-xxxhdpi/images/erasing_text.png diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png Binary files differindex 317a7716f..052bf2142 100644 --- a/res-xxxhdpi/images/error_text.png +++ b/res-xxxhdpi/images/error_text.png diff --git a/res-xxxhdpi/images/installing_security_text.png b/res-xxxhdpi/images/installing_security_text.png Binary files differindex c99c9072a..a9e739b17 100644 --- a/res-xxxhdpi/images/installing_security_text.png +++ b/res-xxxhdpi/images/installing_security_text.png diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png Binary files differindex 91d833017..2d1948677 100644 --- a/res-xxxhdpi/images/installing_text.png +++ b/res-xxxhdpi/images/installing_text.png diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png Binary files differindex b6eb964ba..ee0c23865 100644 --- a/res-xxxhdpi/images/no_command_text.png +++ b/res-xxxhdpi/images/no_command_text.png @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "roots.h" + #include <errno.h> #include <stdlib.h> #include <sys/mount.h> @@ -24,13 +26,13 @@ #include <ctype.h> #include <fcntl.h> +#include <android-base/logging.h> +#include <ext4_utils/make_ext4fs.h> +#include <ext4_utils/wipe.h> #include <fs_mgr.h> -#include "mtdutils/mtdutils.h" -#include "mtdutils/mounts.h" -#include "roots.h" + #include "common.h" -#include "make_ext4fs.h" -#include "wipe.h" +#include "mounts.h" #include "cryptfs.h" static struct fstab *fstab = NULL; @@ -42,15 +44,15 @@ void load_volume_table() int i; int ret; - fstab = fs_mgr_read_fstab("/etc/recovery.fstab"); + fstab = fs_mgr_read_fstab_default(); if (!fstab) { - LOGE("failed to read /etc/recovery.fstab\n"); + LOG(ERROR) << "failed to read default fstab"; return; } ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk"); if (ret < 0 ) { - LOGE("failed to add /tmp entry to fstab\n"); + LOG(ERROR) << "failed to add /tmp entry to fstab"; fs_mgr_free_fstab(fstab); fstab = NULL; return; @@ -74,7 +76,7 @@ Volume* volume_for_path(const char* path) { int ensure_path_mounted_at(const char* path, const char* mount_point) { Volume* v = volume_for_path(path); if (v == NULL) { - LOGE("unknown volume for path [%s]\n", path); + LOG(ERROR) << "unknown volume for path [" << path << "]"; return -1; } if (strcmp(v->fs_type, "ramdisk") == 0) { @@ -82,10 +84,8 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { return 0; } - int result; - result = scan_mounted_volumes(); - if (result < 0) { - LOGE("failed to scan mounted volumes\n"); + if (!scan_mounted_volumes()) { + LOG(ERROR) << "failed to scan mounted volumes"; return -1; } @@ -93,8 +93,7 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { mount_point = v->mount_point; } - const MountedVolume* mv = - find_mounted_volume_by_mount_point(mount_point); + MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); if (mv) { // volume is already mounted return 0; @@ -102,29 +101,30 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { mkdir(mount_point, 0755); // in case it doesn't already exist - if (strcmp(v->fs_type, "yaffs2") == 0) { - // mount an MTD partition as a YAFFS2 filesystem. - mtd_scan_partitions(); - const MtdPartition* partition; - partition = mtd_find_partition_by_name(v->blk_device); - if (partition == NULL) { - LOGE("failed to find \"%s\" partition to mount at \"%s\"\n", - v->blk_device, mount_point); - return -1; - } - return mtd_mount_partition(partition, mount_point, v->fs_type, 0); - } else if (strcmp(v->fs_type, "ext4") == 0 || + if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { - result = mount(v->blk_device, mount_point, v->fs_type, - v->flags, v->fs_options); - if (result == 0) return 0; + int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options); + if (result == -1 && fs_mgr_is_formattable(v)) { + LOG(ERROR) << "failed to mount " << mount_point << " (" << strerror(errno) + << ") , formatting....."; + bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer"); + if (fs_mgr_do_format(v, crypt_footer) == 0) { + result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options); + } else { + PLOG(ERROR) << "failed to format " << mount_point; + return -1; + } + } - LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno)); - return -1; + if (result == -1) { + PLOG(ERROR) << "failed to mount " << mount_point; + return -1; + } + return 0; } - LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, mount_point); + LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point; return -1; } @@ -136,7 +136,7 @@ int ensure_path_mounted(const char* path) { int ensure_path_unmounted(const char* path) { Volume* v = volume_for_path(path); if (v == NULL) { - LOGE("unknown volume for path [%s]\n", path); + LOG(ERROR) << "unknown volume for path [" << path << "]"; return -1; } if (strcmp(v->fs_type, "ramdisk") == 0) { @@ -144,15 +144,12 @@ int ensure_path_unmounted(const char* path) { return -1; } - int result; - result = scan_mounted_volumes(); - if (result < 0) { - LOGE("failed to scan mounted volumes\n"); + if (!scan_mounted_volumes()) { + LOG(ERROR) << "failed to scan mounted volumes"; return -1; } - const MountedVolume* mv = - find_mounted_volume_by_mount_point(v->mount_point); + MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point); if (mv == NULL) { // volume is already unmounted return 0; @@ -166,11 +163,11 @@ static int exec_cmd(const char* path, char* const argv[]) { pid_t child; if ((child = vfork()) == 0) { execv(path, argv); - _exit(-1); + _exit(EXIT_FAILURE); } waitpid(child, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOGE("%s failed with status %d\n", path, WEXITSTATUS(status)); + LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status); } return WEXITSTATUS(status); } @@ -178,55 +175,32 @@ static int exec_cmd(const char* path, char* const argv[]) { int format_volume(const char* volume, const char* directory) { Volume* v = volume_for_path(volume); if (v == NULL) { - LOGE("unknown volume \"%s\"\n", volume); + LOG(ERROR) << "unknown volume \"" << volume << "\""; return -1; } if (strcmp(v->fs_type, "ramdisk") == 0) { // you can't format the ramdisk. - LOGE("can't format_volume \"%s\"", volume); + LOG(ERROR) << "can't format_volume \"" << volume << "\""; return -1; } if (strcmp(v->mount_point, volume) != 0) { - LOGE("can't give path \"%s\" to format_volume\n", volume); + LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume"; return -1; } if (ensure_path_unmounted(volume) != 0) { - LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point); + LOG(ERROR) << "format_volume failed to unmount \"" << v->mount_point << "\""; return -1; } - if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) { - mtd_scan_partitions(); - const MtdPartition* partition = mtd_find_partition_by_name(v->blk_device); - if (partition == NULL) { - LOGE("format_volume: no MTD partition \"%s\"\n", v->blk_device); - return -1; - } - - MtdWriteContext *write = mtd_write_partition(partition); - if (write == NULL) { - LOGW("format_volume: can't open MTD \"%s\"\n", v->blk_device); - return -1; - } else if (mtd_erase_blocks(write, -1) == (off_t) -1) { - LOGW("format_volume: can't erase MTD \"%s\"\n", v->blk_device); - mtd_write_close(write); - return -1; - } else if (mtd_write_close(write)) { - LOGW("format_volume: can't close MTD \"%s\"\n", v->blk_device); - return -1; - } - return 0; - } - if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) { // if there's a key_loc that looks like a path, it should be a // block device for storing encryption metadata. wipe it too. if (v->key_loc != NULL && v->key_loc[0] == '/') { - LOGI("wiping %s\n", v->key_loc); + LOG(INFO) << "wiping " << v->key_loc; int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644); if (fd < 0) { - LOGE("format_volume: failed to open %s\n", v->key_loc); + LOG(ERROR) << "format_volume: failed to open " << v->key_loc; return -1; } wipe_block_device(fd, get_file_size(fd)); @@ -241,19 +215,27 @@ int format_volume(const char* volume, const char* directory) { } int result; if (strcmp(v->fs_type, "ext4") == 0) { - result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory); + if (v->erase_blk_size != 0 && v->logical_blk_size != 0) { + result = make_ext4fs_directory_align(v->blk_device, length, volume, sehandle, + directory, v->erase_blk_size, v->logical_blk_size); + } else { + result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory); + } } else { /* Has to be f2fs because we checked earlier. */ if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) { - LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type); + LOG(ERROR) << "format_volume: crypt footer + negative length (" << length + << ") not supported on " << v->fs_type; return -1; } if (length < 0) { - LOGE("format_volume: negative length (%zd) not supported on %s\n", length, v->fs_type); + LOG(ERROR) << "format_volume: negative length (" << length + << ") not supported on " << v->fs_type; return -1; } char *num_sectors; if (asprintf(&num_sectors, "%zd", length / 512) <= 0) { - LOGE("format_volume: failed to create %s command for %s\n", v->fs_type, v->blk_device); + LOG(ERROR) << "format_volume: failed to create " << v->fs_type + << " command for " << v->blk_device; return -1; } const char *f2fs_path = "/sbin/mkfs.f2fs"; @@ -263,13 +245,13 @@ int format_volume(const char* volume, const char* directory) { free(num_sectors); } if (result != 0) { - LOGE("format_volume: make %s failed on %s with %d(%s)\n", v->fs_type, v->blk_device, result, strerror(errno)); + PLOG(ERROR) << "format_volume: make " << v->fs_type << " failed on " << v->blk_device; return -1; } return 0; } - LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type); + LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported"; return -1; } @@ -279,7 +261,7 @@ int format_volume(const char* volume) { int setup_install_mounts() { if (fstab == NULL) { - LOGE("can't set up install mounts: no fstab loaded\n"); + LOG(ERROR) << "can't set up install mounts: no fstab loaded"; return -1; } for (int i = 0; i < fstab->num_entries; ++i) { @@ -288,13 +270,13 @@ int setup_install_mounts() { if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) { if (ensure_path_mounted(v->mount_point) != 0) { - LOGE("failed to mount %s\n", v->mount_point); + LOG(ERROR) << "failed to mount " << v->mount_point; return -1; } } else { if (ensure_path_unmounted(v->mount_point) != 0) { - LOGE("failed to unmount %s\n", v->mount_point); + LOG(ERROR) << "failed to unmount " << v->mount_point; return -1; } } @@ -17,7 +17,7 @@ #ifndef RECOVERY_ROOTS_H_ #define RECOVERY_ROOTS_H_ -#include "common.h" +typedef struct fstab_rec Volume; // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); diff --git a/rotate_logs.cpp b/rotate_logs.cpp new file mode 100644 index 000000000..fc220215e --- /dev/null +++ b/rotate_logs.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 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 "rotate_logs.h" + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/stringprintf.h> +#include <private/android_logger.h> /* private pmsg functions */ + +static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg"; +static const std::string LAST_LOG_FILTER = "recovery/last_log"; + +ssize_t logbasename( + log_id_t /* logId */, + char /* prio */, + const char *filename, + const char * /* buf */, size_t len, + void *arg) { + bool* doRotate = static_cast<bool*>(arg); + if (LAST_KMSG_FILTER.find(filename) != std::string::npos || + LAST_LOG_FILTER.find(filename) != std::string::npos) { + *doRotate = true; + } + return len; +} + +ssize_t logrotate( + log_id_t logId, + char prio, + const char *filename, + const char *buf, size_t len, + void *arg) { + bool* doRotate = static_cast<bool*>(arg); + if (!*doRotate) { + return __android_log_pmsg_file_write(logId, prio, filename, buf, len); + } + + std::string name(filename); + size_t dot = name.find_last_of('.'); + std::string sub = name.substr(0, dot); + + if (LAST_KMSG_FILTER.find(sub) == std::string::npos && + LAST_LOG_FILTER.find(sub) == std::string::npos) { + return __android_log_pmsg_file_write(logId, prio, filename, buf, len); + } + + // filename rotation + if (dot == std::string::npos) { + name += ".1"; + } else { + std::string number = name.substr(dot + 1); + if (!isdigit(number[0])) { + name += ".1"; + } else { + size_t i; + if (!android::base::ParseUint(number, &i)) { + LOG(ERROR) << "failed to parse uint in " << number; + return -1; + } + name = sub + "." + std::to_string(i + 1); + } + } + + return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len); +} + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file) { + // Logs should only be rotated once. + static bool rotated = false; + if (rotated) { + return; + } + rotated = true; + + for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) { + std::string old_log = android::base::StringPrintf("%s", last_log_file); + if (i > 0) { + old_log += "." + std::to_string(i); + } + std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i+1); + // Ignore errors if old_log doesn't exist. + rename(old_log.c_str(), new_log.c_str()); + + std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file); + if (i > 0) { + old_kmsg += "." + std::to_string(i); + } + std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i+1); + rename(old_kmsg.c_str(), new_kmsg.c_str()); + } +} diff --git a/rotate_logs.h b/rotate_logs.h new file mode 100644 index 000000000..809c213b6 --- /dev/null +++ b/rotate_logs.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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 _ROTATE_LOGS_H +#define _ROTATE_LOGS_H + +#include <string> + +#include <private/android_logger.h> /* private pmsg functions */ + +constexpr int KEEP_LOG_COUNT = 10; + +ssize_t logbasename(log_id_t /* logId */, + char /* prio */, + const char *filename, + const char * /* buf */, size_t len, + void *arg); + +ssize_t logrotate( + log_id_t logId, + char prio, + const char *filename, + const char *buf, size_t len, + void *arg); + +// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max. +// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max. +// Overwrite any existing last_log.$max and last_kmsg.$max. +void rotate_logs(const char* last_log_file, const char* last_kmsg_file); + +#endif //_ROTATE_LOG_H diff --git a/screen_ui.cpp b/screen_ui.cpp index 95b97d15c..bb2772dd8 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -29,11 +29,13 @@ #include <time.h> #include <unistd.h> +#include <string> #include <vector> +#include <android-base/logging.h> +#include <android-base/properties.h> #include <android-base/strings.h> #include <android-base/stringprintf.h> -#include <cutils/properties.h> #include "common.h" #include "device.h" @@ -50,37 +52,34 @@ static double now() { return tv.tv_sec + tv.tv_usec / 1000000.0; } -ScreenRecoveryUI::ScreenRecoveryUI() : - currentIcon(NONE), - locale(nullptr), - progressBarType(EMPTY), - progressScopeStart(0), - progressScopeSize(0), - progress(0), - pagesIdentical(false), - text_cols_(0), - text_rows_(0), - text_(nullptr), - text_col_(0), - text_row_(0), - text_top_(0), - show_text(false), - show_text_ever(false), - menu_(nullptr), - show_menu(false), - menu_items(0), - menu_sel(0), - file_viewer_text_(nullptr), - intro_frames(0), - loop_frames(0), - current_frame(0), - intro_done(false), - animation_fps(30), // TODO: there's currently no way to infer this. - stage(-1), - max_stage(-1), - updateMutex(PTHREAD_MUTEX_INITIALIZER), - rtl_locale(false) { -} +ScreenRecoveryUI::ScreenRecoveryUI() + : currentIcon(NONE), + progressBarType(EMPTY), + progressScopeStart(0), + progressScopeSize(0), + progress(0), + pagesIdentical(false), + text_cols_(0), + text_rows_(0), + text_(nullptr), + text_col_(0), + text_row_(0), + text_top_(0), + show_text(false), + show_text_ever(false), + menu_(nullptr), + show_menu(false), + menu_items(0), + menu_sel(0), + file_viewer_text_(nullptr), + intro_frames(0), + loop_frames(0), + current_frame(0), + intro_done(false), + animation_fps(30), // TODO: there's currently no way to infer this. + stage(-1), + max_stage(-1), + updateMutex(PTHREAD_MUTEX_INITIALIZER) {} GRSurface* ScreenRecoveryUI::GetCurrentFrame() { if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { @@ -99,7 +98,7 @@ GRSurface* ScreenRecoveryUI::GetCurrentText() { } } -int ScreenRecoveryUI::PixelsFromDp(int dp) { +int ScreenRecoveryUI::PixelsFromDp(int dp) const { return dp * density_; } @@ -174,51 +173,50 @@ void ScreenRecoveryUI::draw_background_locked() { // Does not flip pages. // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_foreground_locked() { - if (currentIcon != NONE) { - GRSurface* frame = GetCurrentFrame(); - int frame_width = gr_get_width(frame); - int frame_height = gr_get_height(frame); - int frame_x = (gr_fb_width() - frame_width) / 2; - int frame_y = GetAnimationBaseline(); - gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); - } - - if (progressBarType != EMPTY) { - int width = gr_get_width(progressBarEmpty); - int height = gr_get_height(progressBarEmpty); - - int progress_x = (gr_fb_width() - width)/2; - int progress_y = GetProgressBaseline(); - - // Erase behind the progress bar (in case this was a progress-only update) - gr_color(0, 0, 0, 255); - gr_fill(progress_x, progress_y, width, height); + if (currentIcon != NONE) { + GRSurface* frame = GetCurrentFrame(); + int frame_width = gr_get_width(frame); + int frame_height = gr_get_height(frame); + int frame_x = (gr_fb_width() - frame_width) / 2; + int frame_y = GetAnimationBaseline(); + gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); + } + + if (progressBarType != EMPTY) { + int width = gr_get_width(progressBarEmpty); + int height = gr_get_height(progressBarEmpty); + + int progress_x = (gr_fb_width() - width) / 2; + int progress_y = GetProgressBaseline(); + + // Erase behind the progress bar (in case this was a progress-only update) + gr_color(0, 0, 0, 255); + gr_fill(progress_x, progress_y, width, height); - if (progressBarType == DETERMINATE) { - float p = progressScopeStart + progress * progressScopeSize; - int pos = (int) (p * width); + if (progressBarType == DETERMINATE) { + float p = progressScopeStart + progress * progressScopeSize; + int pos = static_cast<int>(p * width); - if (rtl_locale) { - // Fill the progress bar from right to left. - if (pos > 0) { - gr_blit(progressBarFill, width-pos, 0, pos, height, - progress_x+width-pos, progress_y); - } - if (pos < width-1) { - gr_blit(progressBarEmpty, 0, 0, width-pos, height, progress_x, progress_y); - } - } else { - // Fill the progress bar from left to right. - if (pos > 0) { - gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y); - } - if (pos < width-1) { - gr_blit(progressBarEmpty, pos, 0, width-pos, height, - progress_x+pos, progress_y); - } - } + if (rtl_locale_) { + // Fill the progress bar from right to left. + if (pos > 0) { + gr_blit(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos, + progress_y); + } + if (pos < width - 1) { + gr_blit(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y); } + } else { + // Fill the progress bar from left to right. + if (pos > 0) { + gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y); + } + if (pos < width - 1) { + gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y); + } + } } + } } void ScreenRecoveryUI::SetColor(UIElement e) { @@ -258,12 +256,12 @@ void ScreenRecoveryUI::DrawHorizontalRule(int* y) { *y += 4; } -void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) { +void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const { gr_text(gr_sys_font(), x, *y, line, bold); *y += char_height_ + 4; } -void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) { +void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const { for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { DrawTextLine(x, y, lines[i], false); } @@ -292,8 +290,8 @@ void ScreenRecoveryUI::draw_screen_locked() { int y = 0; if (show_menu) { - char recovery_fingerprint[PROPERTY_VALUE_MAX]; - property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + std::string recovery_fingerprint = + android::base::GetProperty("ro.bootimage.build.fingerprint", ""); SetColor(INFO); DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true); @@ -410,22 +408,22 @@ void ScreenRecoveryUI::ProgressThreadLoop() { // minimum of 20ms delay between frames double delay = interval - (end-start); if (delay < 0.02) delay = 0.02; - usleep((long)(delay * 1000000)); + usleep(static_cast<useconds_t>(delay * 1000000)); } } void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { int result = res_create_display_surface(filename, surface); if (result < 0) { - LOGE("couldn't load bitmap %s (error %d)\n", filename, result); + LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; } } void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { - int result = res_create_localized_alpha_surface(filename, locale, surface); - if (result < 0) { - LOGE("couldn't load bitmap %s (error %d)\n", filename, result); - } + int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); + if (result < 0) { + LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; + } } static char** Alloc2d(size_t rows, size_t cols) { @@ -447,51 +445,58 @@ void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { Redraw(); } -void ScreenRecoveryUI::InitTextParams() { - gr_init(); +bool ScreenRecoveryUI::InitTextParams() { + if (gr_init() < 0) { + return false; + } gr_font_size(gr_sys_font(), &char_width_, &char_height_); text_rows_ = gr_fb_height() / char_height_; text_cols_ = gr_fb_width() / char_width_; + return true; } -void ScreenRecoveryUI::Init() { - RecoveryUI::Init(); - InitTextParams(); +bool ScreenRecoveryUI::Init(const std::string& locale) { + RecoveryUI::Init(locale); + if (!InitTextParams()) { + return false; + } - density_ = static_cast<float>(property_get_int32("ro.sf.lcd_density", 160)) / 160.f; + density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f; - // Are we portrait or landscape? - layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; - // Are we the large variant of our base layout? - if (gr_fb_height() > PixelsFromDp(800)) ++layout_; + // Are we portrait or landscape? + layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; + // Are we the large variant of our base layout? + if (gr_fb_height() > PixelsFromDp(800)) ++layout_; - text_ = Alloc2d(text_rows_, text_cols_ + 1); - file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); - menu_ = Alloc2d(text_rows_, text_cols_ + 1); + text_ = Alloc2d(text_rows_, text_cols_ + 1); + file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); + menu_ = Alloc2d(text_rows_, text_cols_ + 1); - text_col_ = text_row_ = 0; - text_top_ = 1; + text_col_ = text_row_ = 0; + text_top_ = 1; - LoadBitmap("icon_error", &error_icon); + LoadBitmap("icon_error", &error_icon); - LoadBitmap("progress_empty", &progressBarEmpty); - LoadBitmap("progress_fill", &progressBarFill); + LoadBitmap("progress_empty", &progressBarEmpty); + LoadBitmap("progress_fill", &progressBarFill); - LoadBitmap("stage_empty", &stageMarkerEmpty); - LoadBitmap("stage_fill", &stageMarkerFill); + LoadBitmap("stage_empty", &stageMarkerEmpty); + LoadBitmap("stage_fill", &stageMarkerFill); - // Background text for "installing_update" could be "installing update" - // or "installing security update". It will be set after UI init according - // to commands in BCB. - installing_text = nullptr; - LoadLocalizedBitmap("erasing_text", &erasing_text); - LoadLocalizedBitmap("no_command_text", &no_command_text); - LoadLocalizedBitmap("error_text", &error_text); + // Background text for "installing_update" could be "installing update" + // or "installing security update". It will be set after UI init according + // to commands in BCB. + installing_text = nullptr; + LoadLocalizedBitmap("erasing_text", &erasing_text); + LoadLocalizedBitmap("no_command_text", &no_command_text); + LoadLocalizedBitmap("error_text", &error_text); - LoadAnimation(); + LoadAnimation(); - pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); + pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); + + return true; } void ScreenRecoveryUI::LoadAnimation() { @@ -531,31 +536,6 @@ void ScreenRecoveryUI::LoadAnimation() { } } -void ScreenRecoveryUI::SetLocale(const char* new_locale) { - this->locale = new_locale; - this->rtl_locale = false; - - if (locale) { - char* lang = strdup(locale); - for (char* p = lang; *p; ++p) { - if (*p == '_') { - *p = '\0'; - break; - } - } - - // A bit cheesy: keep an explicit list of supported RTL languages. - if (strcmp(lang, "ar") == 0 || // Arabic - strcmp(lang, "fa") == 0 || // Persian (Farsi) - strcmp(lang, "he") == 0 || // Hebrew (new language code) - strcmp(lang, "iw") == 0 || // Hebrew (old language code) - strcmp(lang, "ur") == 0) { // Urdu - rtl_locale = true; - } - free(lang); - } -} - void ScreenRecoveryUI::SetBackground(Icon icon) { pthread_mutex_lock(&updateMutex); @@ -675,8 +655,8 @@ void ScreenRecoveryUI::ClearText() { } void ScreenRecoveryUI::ShowFile(FILE* fp) { - std::vector<long> offsets; - offsets.push_back(ftell(fp)); + std::vector<off_t> offsets; + offsets.push_back(ftello(fp)); ClearText(); struct stat sb; @@ -686,7 +666,7 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { while (true) { if (show_prompt) { PrintOnScreenOnly("--(%d%% of %d bytes)--", - static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), + static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), static_cast<int>(sb.st_size)); Redraw(); while (show_prompt) { @@ -705,7 +685,7 @@ void ScreenRecoveryUI::ShowFile(FILE* fp) { if (feof(fp)) { return; } - offsets.push_back(ftell(fp)); + offsets.push_back(ftello(fp)); } } ClearText(); diff --git a/screen_ui.h b/screen_ui.h index de7b6442e..a2322c36c 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -20,8 +20,12 @@ #include <pthread.h> #include <stdio.h> +#include <string> + #include "ui.h" -#include "minui/minui.h" + +// From minui/minui.h. +struct GRSurface; // Implementation of RecoveryUI appropriate for devices with a screen // (shows an icon + a progress bar, text logging, menu, etc.) @@ -29,8 +33,7 @@ class ScreenRecoveryUI : public RecoveryUI { public: ScreenRecoveryUI(); - void Init(); - void SetLocale(const char* locale); + bool Init(const std::string& locale) override; // overall recovery state ("background image") void SetBackground(Icon icon); @@ -71,8 +74,6 @@ class ScreenRecoveryUI : public RecoveryUI { protected: Icon currentIcon; - const char* locale; - // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. float density_; // The layout to use. @@ -135,9 +136,8 @@ class ScreenRecoveryUI : public RecoveryUI { int char_width_; int char_height_; pthread_mutex_t updateMutex; - bool rtl_locale; - virtual void InitTextParams(); + virtual bool InitTextParams(); virtual void draw_background_locked(); virtual void draw_foreground_locked(); @@ -160,14 +160,14 @@ class ScreenRecoveryUI : public RecoveryUI { void LoadBitmap(const char* filename, GRSurface** surface); void LoadLocalizedBitmap(const char* filename, GRSurface** surface); - int PixelsFromDp(int dp); + int PixelsFromDp(int dp) const; virtual int GetAnimationBaseline(); virtual int GetProgressBaseline(); virtual int GetTextBaseline(); void DrawHorizontalRule(int* y); - void DrawTextLine(int x, int* y, const char* line, bool bold); - void DrawTextLines(int x, int* y, const char* const* lines); + void DrawTextLine(int x, int* y, const char* line, bool bold) const; + void DrawTextLines(int x, int* y, const char* const* lines) const; }; #endif // RECOVERY_UI_H diff --git a/stub_ui.h b/stub_ui.h new file mode 100644 index 000000000..85dbcfd89 --- /dev/null +++ b/stub_ui.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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 RECOVERY_STUB_UI_H +#define RECOVERY_STUB_UI_H + +#include "ui.h" + +// Stub implementation of RecoveryUI for devices without screen. +class StubRecoveryUI : public RecoveryUI { + public: + StubRecoveryUI() = default; + + void SetBackground(Icon icon) override {} + void SetSystemUpdateText(bool security_update) override {} + + // progress indicator + void SetProgressType(ProgressType type) override {} + void ShowProgress(float portion, float seconds) override {} + void SetProgress(float fraction) override {} + + void SetStage(int current, int max) override {} + + // text log + void ShowText(bool visible) override {} + bool IsTextVisible() override { + return false; + } + bool WasTextEverVisible() override { + return false; + } + + // printing messages + void Print(const char* fmt, ...) override { + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + void PrintOnScreenOnly(const char* fmt, ...) override {} + void ShowFile(const char* filename) override {} + + // menu display + void StartMenu(const char* const* headers, const char* const* items, + int initial_selection) override {} + int SelectMenu(int sel) override { + return sel; + } + void EndMenu() override {} +}; + +#endif // RECOVERY_STUB_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index a66991b21..f59f486d7 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -18,52 +18,187 @@ LOCAL_PATH := $(call my-dir) # Unit tests include $(CLEAR_VARS) -LOCAL_CLANG := true +LOCAL_CFLAGS := -Werror LOCAL_MODULE := recovery_unit_test LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_STATIC_LIBRARIES := \ libverifier \ - libminui + libminui \ + libotautil \ + libziparchive \ + libutils \ + libz \ + libselinux \ + libbase + +LOCAL_SRC_FILES := \ + unit/asn1_decoder_test.cpp \ + unit/dirutil_test.cpp \ + unit/locale_test.cpp \ + unit/sysutil_test.cpp \ + unit/zip_test.cpp \ + unit/ziputil_test.cpp -LOCAL_SRC_FILES := unit/asn1_decoder_test.cpp -LOCAL_SRC_FILES += unit/recovery_test.cpp -LOCAL_SRC_FILES += unit/locale_test.cpp LOCAL_C_INCLUDES := bootable/recovery LOCAL_SHARED_LIBRARIES := liblog include $(BUILD_NATIVE_TEST) -# Component tests +# Manual tests include $(CLEAR_VARS) LOCAL_CLANG := true -LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CFLAGS := -Werror +LOCAL_MODULE := recovery_manual_test +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_STATIC_LIBRARIES := \ + libminui \ + libbase + +LOCAL_SRC_FILES := manual/recovery_test.cpp +LOCAL_SHARED_LIBRARIES := \ + liblog \ + libpng + +resource_files := $(call find-files-in-subdirs, bootable/recovery, \ + "*_text.png", \ + res-mdpi/images \ + res-hdpi/images \ + res-xhdpi/images \ + res-xxhdpi/images \ + res-xxxhdpi/images \ + ) + +# The resource image files that will go to $OUT/data/nativetest/recovery. +testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery +GEN := $(addprefix $(testimage_out_path)/, $(resource_files)) + +$(GEN): PRIVATE_PATH := $(LOCAL_PATH) +$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ +$(GEN): $(testimage_out_path)/% : bootable/recovery/% + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN) + +include $(BUILD_NATIVE_TEST) + +# Component tests +include $(CLEAR_VARS) +LOCAL_CFLAGS := \ + -Werror \ + -D_FILE_OFFSET_BITS=64 + LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +ifeq ($(AB_OTA_UPDATER),true) +LOCAL_CFLAGS += -DAB_OTA_UPDATER=1 +endif + LOCAL_MODULE := recovery_component_test LOCAL_C_INCLUDES := bootable/recovery LOCAL_SRC_FILES := \ - component/verifier_test.cpp \ - component/applypatch_test.cpp + component/applypatch_test.cpp \ + component/bootloader_message_test.cpp \ + component/edify_test.cpp \ + component/imgdiff_test.cpp \ + component/install_test.cpp \ + component/sideload_test.cpp \ + component/uncrypt_test.cpp \ + component/updater_test.cpp \ + component/verifier_test.cpp + LOCAL_FORCE_STATIC_EXECUTABLE := true + +tune2fs_static_libraries := \ + libext2_com_err \ + libext2_blkid \ + libext2_quota \ + libext2_uuid \ + libext2_e2p \ + libext2fs + LOCAL_STATIC_LIBRARIES := \ + libapplypatch_modes \ libapplypatch \ + libedify \ + libimgdiff \ + libimgpatch \ + libbsdiff \ + libbspatch \ libotafault \ - libmtdutils \ - libbase \ + librecovery \ + libupdater \ + libbootloader_message \ libverifier \ - libcrypto_static \ - libminui \ - libminzip \ - libcutils \ + libotautil \ + libmounts \ + libdivsufsort \ + libdivsufsort64 \ + libfs_mgr \ + liblog \ + libvintf_recovery \ + libvintf \ + libtinyxml2 \ + libselinux \ + libext4_utils \ + libsparse \ + libcrypto_utils \ + libcrypto \ libbz \ + libziparchive \ + libutils \ libz \ - libc + libbase \ + libtune2fs \ + libfec \ + libfec_rs \ + libsquashfs_utils \ + libcutils \ + $(tune2fs_static_libraries) -testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery testdata_files := $(call find-subdir-files, testdata/*) +# The testdata files that will go to $OUT/data/nativetest/recovery. +testdata_out_path := $(TARGET_OUT_DATA)/nativetest/recovery GEN := $(addprefix $(testdata_out_path)/, $(testdata_files)) $(GEN): PRIVATE_PATH := $(LOCAL_PATH) $(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ $(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/% $(transform-generated-source) LOCAL_GENERATED_SOURCES += $(GEN) + +# A copy of the testdata to be packed into continuous_native_tests.zip. +testdata_continuous_zip_prefix := \ + $(call intermediates-dir-for,PACKAGING,recovery_component_test)/DATA +testdata_continuous_zip_path := $(testdata_continuous_zip_prefix)/nativetest/recovery +GEN := $(addprefix $(testdata_continuous_zip_path)/, $(testdata_files)) +$(GEN): PRIVATE_PATH := $(LOCAL_PATH) +$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ +$(GEN): $(testdata_continuous_zip_path)/% : $(LOCAL_PATH)/% + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(GEN) +LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix) + include $(BUILD_NATIVE_TEST) + +# Host tests +include $(CLEAR_VARS) +LOCAL_CFLAGS := -Werror +LOCAL_MODULE := recovery_host_test +LOCAL_MODULE_HOST_OS := linux +LOCAL_C_INCLUDES := bootable/recovery +LOCAL_SRC_FILES := \ + component/imgdiff_test.cpp +LOCAL_STATIC_LIBRARIES := \ + libimgdiff \ + libimgpatch \ + libbsdiff \ + libbspatch \ + libziparchive \ + libutils \ + libbase \ + libcrypto \ + libbz \ + libdivsufsort64 \ + libdivsufsort \ + libz +LOCAL_SHARED_LIBRARIES := \ + liblog +include $(BUILD_HOST_NATIVE_TEST) diff --git a/tests/common/component_test_util.h b/tests/common/component_test_util.h new file mode 100644 index 000000000..3fee32d62 --- /dev/null +++ b/tests/common/component_test_util.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 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 agree 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 _COMPONENT_TEST_UTIL_H +#define _COMPONENT_TEST_UTIL_H + +#include <string> + +#include <android-base/properties.h> +#include <fs_mgr.h> + +// Check if the /misc entry exists in the fstab. +static bool parse_misc() { + std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(), + fs_mgr_free_fstab); + if (!fstab) { + GTEST_LOG_(INFO) << "Failed to read default fstab"; + return false; + } + + fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); + if (record == nullptr) { + GTEST_LOG_(INFO) << "Failed to find /misc in fstab."; + return false; + } + return true; +} + +#endif //_COMPONENT_TEST_UTIL_H + diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h index 3490f6805..f6b6922a4 100644 --- a/tests/common/test_constants.h +++ b/tests/common/test_constants.h @@ -13,13 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #ifndef _OTA_TEST_CONSTANTS_H #define _OTA_TEST_CONSTANTS_H -#if defined(__LP64__) -#define NATIVE_TEST_PATH "/nativetest64" -#else -#define NATIVE_TEST_PATH "/nativetest" -#endif +#include <stdlib.h> + +// Zip entries in ziptest_valid.zip. +static const std::string kATxtContents("abcdefghabcdefgh\n"); +static const std::string kBTxtContents("abcdefgh\n"); +static const std::string kCTxtContents("abcdefghabcdefgh\n"); +static const std::string kDTxtContents("abcdefgh\n"); + +// echo -n -e "abcdefghabcdefgh\n" | sha1sum +static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643"); +// echo -n -e "abcdefgh\n" | sha1sum +static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666"); + +static const char* data_root = getenv("ANDROID_DATA"); + +static std::string from_testdata_base(const std::string& fname) { + return std::string(data_root) + "/nativetest/recovery/testdata/" + fname; +} -#endif +#endif // _OTA_TEST_CONSTANTS_H diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp index b44ddd17c..5cba68f8a 100644 --- a/tests/component/applypatch_test.cpp +++ b/tests/component/applypatch_test.cpp @@ -23,176 +23,155 @@ #include <sys/types.h> #include <time.h> +#include <memory> #include <string> +#include <vector> #include <android-base/file.h> #include <android-base/stringprintf.h> #include <android-base/test_utils.h> +#include <openssl/sha.h> #include "applypatch/applypatch.h" +#include "applypatch/applypatch_modes.h" #include "common/test_constants.h" -#include "openssl/sha.h" #include "print_sha1.h" -static const std::string DATA_PATH = getenv("ANDROID_DATA"); -static const std::string TESTDATA_PATH = "/recovery/testdata"; -static const std::string WORK_FS = "/data"; +static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) { + ASSERT_NE(nullptr, sha1); -static std::string sha1sum(const std::string& fname) { - uint8_t digest[SHA_DIGEST_LENGTH]; - std::string data; - android::base::ReadFileToString(fname, &data); + std::string data; + ASSERT_TRUE(android::base::ReadFileToString(fname, &data)); - SHA1((const uint8_t*)data.c_str(), data.size(), digest); - return print_sha1(digest); -} + if (fsize != nullptr) { + *fsize = data.size(); + } -static void mangle_file(const std::string& fname) { - FILE* fh = fopen(&fname[0], "w"); - int r; - for (int i=0; i < 1024; i++) { - r = rand(); - fwrite(&r, sizeof(short), 1, fh); - } - fclose(fh); + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest); + *sha1 = print_sha1(digest); } -static bool file_cmp(std::string& f1, std::string& f2) { - std::string c1; - std::string c2; - android::base::ReadFileToString(f1, &c1); - android::base::ReadFileToString(f2, &c2); - return c1 == c2; +static void mangle_file(const std::string& fname) { + std::string content; + content.reserve(1024); + for (size_t i = 0; i < 1024; i++) { + content[i] = rand() % 256; + } + ASSERT_TRUE(android::base::WriteStringToFile(content, fname)); } -static std::string from_testdata_base(const std::string fname) { - return android::base::StringPrintf("%s%s%s/%s", - &DATA_PATH[0], - &NATIVE_TEST_PATH[0], - &TESTDATA_PATH[0], - &fname[0]); +static bool file_cmp(const std::string& f1, const std::string& f2) { + std::string c1; + android::base::ReadFileToString(f1, &c1); + std::string c2; + android::base::ReadFileToString(f2, &c2); + return c1 == c2; } class ApplyPatchTest : public ::testing::Test { - public: - static void SetUpTestCase() { - // set up files - old_file = from_testdata_base("old.file"); - new_file = from_testdata_base("new.file"); - patch_file = from_testdata_base("patch.bsdiff"); - rand_file = "/cache/applypatch_test_rand.file"; - cache_file = "/cache/saved.file"; - - // write stuff to rand_file - android::base::WriteStringToFile("hello", rand_file); - - // set up SHA constants - old_sha1 = sha1sum(old_file); - new_sha1 = sha1sum(new_file); - srand(time(NULL)); - bad_sha1_a = android::base::StringPrintf("%040x", rand()); - bad_sha1_b = android::base::StringPrintf("%040x", rand()); - - struct stat st; - stat(&new_file[0], &st); - new_size = st.st_size; - } - - static std::string old_file; - static std::string new_file; - static std::string rand_file; - static std::string cache_file; - static std::string patch_file; - - static std::string old_sha1; - static std::string new_sha1; - static std::string bad_sha1_a; - static std::string bad_sha1_b; - - static size_t new_size; + public: + static void SetUpTestCase() { + // set up files + old_file = from_testdata_base("old.file"); + new_file = from_testdata_base("new.file"); + patch_file = from_testdata_base("patch.bsdiff"); + rand_file = "/cache/applypatch_test_rand.file"; + cache_file = "/cache/saved.file"; + + // write stuff to rand_file + ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file)); + + // set up SHA constants + sha1sum(old_file, &old_sha1, &old_size); + sha1sum(new_file, &new_sha1, &new_size); + srand(time(nullptr)); + bad_sha1_a = android::base::StringPrintf("%040x", rand()); + bad_sha1_b = android::base::StringPrintf("%040x", rand()); + } + + static std::string old_file; + static std::string new_file; + static std::string rand_file; + static std::string cache_file; + static std::string patch_file; + + static std::string old_sha1; + static std::string new_sha1; + static std::string bad_sha1_a; + static std::string bad_sha1_b; + + static size_t old_size; + static size_t new_size; }; std::string ApplyPatchTest::old_file; std::string ApplyPatchTest::new_file; -static void cp(std::string src, std::string tgt) { - std::string cmd = android::base::StringPrintf("cp %s %s", - &src[0], - &tgt[0]); - system(&cmd[0]); +static void cp(const std::string& src, const std::string& tgt) { + std::string cmd = "cp " + src + " " + tgt; + system(cmd.c_str()); } static void backup_old() { - cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file); + cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file); } static void restore_old() { - cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file); + cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file); } class ApplyPatchCacheTest : public ApplyPatchTest { - public: - virtual void SetUp() { - backup_old(); - } - - virtual void TearDown() { - restore_old(); - } + public: + virtual void SetUp() { + backup_old(); + } + + virtual void TearDown() { + restore_old(); + } }; class ApplyPatchFullTest : public ApplyPatchCacheTest { - public: - static void SetUpTestCase() { - ApplyPatchTest::SetUpTestCase(); - unsigned long free_kb = FreeSpaceForFile(&WORK_FS[0]); - ASSERT_GE(free_kb * 1024, new_size * 3 / 2); - output_f = new TemporaryFile(); - output_loc = std::string(output_f->path); - - struct FileContents fc; - - ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc)); - Value* patch1 = new Value(); - patch1->type = VAL_BLOB; - patch1->size = fc.data.size(); - patch1->data = static_cast<char*>(malloc(fc.data.size())); - memcpy(patch1->data, fc.data.data(), fc.data.size()); - patches.push_back(patch1); - - ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc)); - Value* patch2 = new Value(); - patch2->type = VAL_BLOB; - patch2->size = fc.st.st_size; - patch2->data = static_cast<char*>(malloc(fc.data.size())); - memcpy(patch2->data, fc.data.data(), fc.data.size()); - patches.push_back(patch2); - } - static void TearDownTestCase() { - delete output_f; - for (auto it = patches.begin(); it != patches.end(); ++it) { - free((*it)->data); - delete *it; - } - patches.clear(); - } - - static std::vector<Value*> patches; - static TemporaryFile* output_f; - static std::string output_loc; + public: + static void SetUpTestCase() { + ApplyPatchTest::SetUpTestCase(); + + output_f = new TemporaryFile(); + output_loc = std::string(output_f->path); + + struct FileContents fc; + + ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc)); + patches.push_back( + std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end()))); + + ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc)); + patches.push_back( + std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end()))); + } + + static void TearDownTestCase() { + delete output_f; + patches.clear(); + } + + static std::vector<std::unique_ptr<Value>> patches; + static TemporaryFile* output_f; + static std::string output_loc; }; class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest { - public: - virtual void SetUp() { - ApplyPatchCacheTest::SetUp(); - cp(cache_file, "/cache/reallysaved.file"); - } - - virtual void TearDown() { - cp("/cache/reallysaved.file", cache_file); - ApplyPatchCacheTest::TearDown(); - } + public: + virtual void SetUp() { + ApplyPatchCacheTest::SetUp(); + cp(cache_file, "/cache/reallysaved.file"); + } + + virtual void TearDown() { + cp("/cache/reallysaved.file", cache_file); + ApplyPatchCacheTest::TearDown(); + } }; std::string ApplyPatchTest::rand_file; @@ -202,191 +181,263 @@ std::string ApplyPatchTest::old_sha1; std::string ApplyPatchTest::new_sha1; std::string ApplyPatchTest::bad_sha1_a; std::string ApplyPatchTest::bad_sha1_b; - +size_t ApplyPatchTest::old_size; size_t ApplyPatchTest::new_size; -std::vector<Value*> ApplyPatchFullTest::patches; +std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches; TemporaryFile* ApplyPatchFullTest::output_f; std::string ApplyPatchFullTest::output_loc; +TEST_F(ApplyPatchTest, CheckModeSkip) { + std::vector<std::string> sha1s; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); +} + TEST_F(ApplyPatchTest, CheckModeSingle) { - char* s = &old_sha1[0]; - ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); + std::vector<std::string> sha1s = { old_sha1 }; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchTest, CheckModeMultiple) { - char* argv[3] = { - &bad_sha1_a[0], - &old_sha1[0], - &bad_sha1_b[0] - }; - ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); + std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b }; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchTest, CheckModeFailure) { - char* argv[2] = { - &bad_sha1_a[0], - &bad_sha1_b[0] - }; - ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); + std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b }; + ASSERT_NE(0, applypatch_check(&old_file[0], sha1s)); +} + +TEST_F(ApplyPatchTest, CheckModeEmmcTarget) { + // EMMC:old_file:size:sha1 should pass the check. + std::string src_file = + "EMMC:" + old_file + ":" + std::to_string(old_size) + ":" + old_sha1; + std::vector<std::string> sha1s; + ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); + + // EMMC:old_file:(size-1):sha1:(size+1):sha1 should fail the check. + src_file = "EMMC:" + old_file + ":" + std::to_string(old_size - 1) + ":" + old_sha1 + ":" + + std::to_string(old_size + 1) + ":" + old_sha1; + ASSERT_EQ(1, applypatch_check(src_file.c_str(), sha1s)); + + // EMMC:old_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check. + src_file = "EMMC:" + old_file + ":" + + std::to_string(old_size - 1) + ":" + old_sha1 + ":" + + std::to_string(old_size) + ":" + old_sha1 + ":" + + std::to_string(old_size + 1) + ":" + old_sha1; + ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); + + // EMMC:old_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check. + src_file = "EMMC:" + old_file + ":" + + std::to_string(old_size + 1) + ":" + old_sha1 + ":" + + std::to_string(old_size - 1) + ":" + old_sha1 + ":" + + std::to_string(old_size) + ":" + old_sha1; + ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); + + // EMMC:new_file:(size+1):old_sha1:(size-1):old_sha1:size:old_sha1:size:new_sha1 + // should pass the check. + src_file = "EMMC:" + new_file + ":" + + std::to_string(old_size + 1) + ":" + old_sha1 + ":" + + std::to_string(old_size - 1) + ":" + old_sha1 + ":" + + std::to_string(old_size) + ":" + old_sha1 + ":" + + std::to_string(new_size) + ":" + new_sha1; + ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s)); } TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) { - mangle_file(old_file); - char* s = &old_sha1[0]; - ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); + mangle_file(old_file); + std::vector<std::string> sha1s = { old_sha1 }; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) { - mangle_file(old_file); - char* argv[3] = { - &bad_sha1_a[0], - &old_sha1[0], - &bad_sha1_b[0] - }; - ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); + mangle_file(old_file); + std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b }; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) { - mangle_file(old_file); - char* argv[2] = { - &bad_sha1_a[0], - &bad_sha1_b[0] - }; - ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); + mangle_file(old_file); + std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b }; + ASSERT_NE(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) { - unlink(&old_file[0]); - char* s = &old_sha1[0]; - ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s)); + unlink(&old_file[0]); + std::vector<std::string> sha1s = { old_sha1 }; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) { - unlink(&old_file[0]); - char* argv[3] = { - &bad_sha1_a[0], - &old_sha1[0], - &bad_sha1_b[0] - }; - ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv)); + unlink(&old_file[0]); + std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b }; + ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s)); } TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) { - unlink(&old_file[0]); - char* argv[2] = { - &bad_sha1_a[0], - &bad_sha1_b[0] - }; - ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv)); + unlink(&old_file[0]); + std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b }; + ASSERT_NE(0, applypatch_check(&old_file[0], sha1s)); +} + +TEST(ApplyPatchModesTest, InvalidArgs) { + // At least two args (including the filename). + ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" })); + + // Unrecognized args. + ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" })); } -TEST_F(ApplyPatchFullTest, ApplyInPlace) { - std::vector<char*> sha1s; - sha1s.push_back(&bad_sha1_a[0]); - sha1s.push_back(&old_sha1[0]); - - int ap_result = applypatch(&old_file[0], - "-", - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_EQ(0, ap_result); - ASSERT_TRUE(file_cmp(old_file, new_file)); - // reapply, applypatch is idempotent so it should succeed - ap_result = applypatch(&old_file[0], - "-", - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_EQ(0, ap_result); - ASSERT_TRUE(file_cmp(old_file, new_file)); +TEST(ApplyPatchModesTest, PatchModeEmmcTarget) { + std::string boot_img = from_testdata_base("boot.img"); + size_t boot_img_size; + std::string boot_img_sha1; + sha1sum(boot_img, &boot_img_sha1, &boot_img_size); + + std::string recovery_img = from_testdata_base("recovery.img"); + size_t size; + std::string recovery_img_sha1; + sha1sum(recovery_img, &recovery_img_sha1, &size); + std::string recovery_img_size = std::to_string(size); + + std::string bonus_file = from_testdata_base("bonus.file"); + + // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch> + TemporaryFile tmp1; + std::string src_file = + "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1; + std::string tgt_file = "EMMC:" + std::string(tmp1.path); + std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p"); + std::vector<const char*> args = { + "applypatch", + "-b", + bonus_file.c_str(), + src_file.c_str(), + tgt_file.c_str(), + recovery_img_sha1.c_str(), + recovery_img_size.c_str(), + patch.c_str() + }; + ASSERT_EQ(0, applypatch_modes(args.size(), args.data())); + + // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch> + TemporaryFile tmp2; + patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p"); + tgt_file = "EMMC:" + std::string(tmp2.path); + std::vector<const char*> args2 = { + "applypatch", + src_file.c_str(), + tgt_file.c_str(), + recovery_img_sha1.c_str(), + recovery_img_size.c_str(), + patch.c_str() + }; + ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data())); + + // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \ + // <src-sha1-fake>:<patch1> <src-sha1>:<patch2> + TemporaryFile tmp3; + tgt_file = "EMMC:" + std::string(tmp3.path); + std::string bad_sha1_a = android::base::StringPrintf("%040x", rand()); + std::string bad_sha1_b = android::base::StringPrintf("%040x", rand()); + std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p"); + std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p"); + std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p"); + std::vector<const char*> args3 = { + "applypatch", + "-b", + bonus_file.c_str(), + src_file.c_str(), + tgt_file.c_str(), + recovery_img_sha1.c_str(), + recovery_img_size.c_str(), + patch1.c_str(), + patch2.c_str(), + patch3.c_str() + }; + ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data())); } -TEST_F(ApplyPatchFullTest, ApplyInNewLocation) { - std::vector<char*> sha1s; - sha1s.push_back(&bad_sha1_a[0]); - sha1s.push_back(&old_sha1[0]); - int ap_result = applypatch(&old_file[0], - &output_loc[0], - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_EQ(0, ap_result); - ASSERT_TRUE(file_cmp(output_loc, new_file)); - ap_result = applypatch(&old_file[0], - &output_loc[0], - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_EQ(0, ap_result); - ASSERT_TRUE(file_cmp(output_loc, new_file)); +TEST(ApplyPatchModesTest, PatchModeInvalidArgs) { + // Invalid bonus file. + ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" })); + + std::string bonus_file = from_testdata_base("bonus.file"); + // With bonus file, but missing args. + ASSERT_EQ(2, applypatch_modes(3, (const char* []){ "applypatch", "-b", bonus_file.c_str() })); + + std::string boot_img = from_testdata_base("boot.img"); + size_t boot_img_size; + std::string boot_img_sha1; + sha1sum(boot_img, &boot_img_sha1, &boot_img_size); + + std::string recovery_img = from_testdata_base("recovery.img"); + size_t size; + std::string recovery_img_sha1; + sha1sum(recovery_img, &recovery_img_sha1, &size); + std::string recovery_img_size = std::to_string(size); + + // Bonus file is not supported in flash mode. + // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> + TemporaryFile tmp4; + std::vector<const char*> args4 = { + "applypatch", + "-b", + bonus_file.c_str(), + boot_img.c_str(), + tmp4.path, + recovery_img_sha1.c_str(), + recovery_img_size.c_str() + }; + ASSERT_NE(0, applypatch_modes(args4.size(), args4.data())); + + // Failed to parse patch args. + TemporaryFile tmp5; + std::string bad_arg1 = + "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p"); + std::vector<const char*> args5 = { + "applypatch", + boot_img.c_str(), + tmp5.path, + recovery_img_sha1.c_str(), + recovery_img_size.c_str(), + bad_arg1.c_str() + }; + ASSERT_NE(0, applypatch_modes(args5.size(), args5.data())); + + // Target size cannot be zero. + TemporaryFile tmp6; + std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p"); + std::vector<const char*> args6 = { + "applypatch", + boot_img.c_str(), + tmp6.path, + recovery_img_sha1.c_str(), + "0", // target size + patch.c_str() + }; + ASSERT_NE(0, applypatch_modes(args6.size(), args6.data())); } -TEST_F(ApplyPatchFullTest, ApplyCorruptedInNewLocation) { - mangle_file(old_file); - std::vector<char*> sha1s; - sha1s.push_back(&bad_sha1_a[0]); - sha1s.push_back(&old_sha1[0]); - int ap_result = applypatch(&old_file[0], - &output_loc[0], - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_EQ(0, ap_result); - ASSERT_TRUE(file_cmp(output_loc, new_file)); - ap_result = applypatch(&old_file[0], - &output_loc[0], - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_EQ(0, ap_result); - ASSERT_TRUE(file_cmp(output_loc, new_file)); +TEST(ApplyPatchModesTest, CheckModeInvalidArgs) { + // Insufficient args. + ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" })); +} + +TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) { + // Insufficient args. + ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" })); + + // Invalid bytes arg. + ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" })); + + // 0 is invalid. + ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" })); + + // 0x10 is fine. + ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" })); } -TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) { - mangle_file(old_file); - mangle_file(cache_file); - - std::vector<char*> sha1s; - sha1s.push_back(&bad_sha1_a[0]); - sha1s.push_back(&old_sha1[0]); - int ap_result = applypatch(&old_file[0], - &output_loc[0], - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_NE(0, ap_result); - ASSERT_FALSE(file_cmp(output_loc, new_file)); - ap_result = applypatch(&old_file[0], - &output_loc[0], - &new_sha1[0], - new_size, - 2, - sha1s.data(), - patches.data(), - nullptr); - ASSERT_NE(0, ap_result); - ASSERT_FALSE(file_cmp(output_loc, new_file)); +TEST(ApplyPatchModesTest, ShowLicenses) { + ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" })); } diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp new file mode 100644 index 000000000..0357accfe --- /dev/null +++ b/tests/component/bootloader_message_test.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <vector> + +#include <android-base/strings.h> +#include <bootloader_message/bootloader_message.h> +#include <gtest/gtest.h> + +#include "common/component_test_util.h" + +class BootloaderMessageTest : public ::testing::Test { + protected: + BootloaderMessageTest() : has_misc(true) {} + + virtual void SetUp() override { + has_misc = parse_misc(); + } + + virtual void TearDown() override { + // Clear the BCB. + if (has_misc) { + std::string err; + ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; + } + } + + bool has_misc; +}; + +TEST_F(BootloaderMessageTest, clear_bootloader_message) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Clear the BCB. + std::string err; + ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; + + // Verify the content. + bootloader_message boot; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + + // All the bytes should be cleared. + ASSERT_EQ(std::string(sizeof(boot), '\0'), + std::string(reinterpret_cast<const char*>(&boot), sizeof(boot))); +} + +TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Write the BCB. + bootloader_message boot = {}; + strlcpy(boot.command, "command", sizeof(boot.command)); + strlcpy(boot.recovery, "message1\nmessage2\n", sizeof(boot.recovery)); + strlcpy(boot.status, "status1", sizeof(boot.status)); + + std::string err; + ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err; + + // Read and verify. + bootloader_message boot_verify; + ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err; + + ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)), + std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify))); +} + +TEST_F(BootloaderMessageTest, write_bootloader_message_options) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Write the options to BCB. + std::vector<std::string> options = { "option1", "option2" }; + std::string err; + ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err; + + // Inject some bytes into boot, which should be overwritten while reading. + bootloader_message boot; + strlcpy(boot.recovery, "random message", sizeof(boot.recovery)); + strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved)); + + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + + // Verify that command and recovery fields should be set. + ASSERT_EQ("boot-recovery", std::string(boot.command)); + std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n"; + ASSERT_EQ(expected, std::string(boot.recovery)); + + // The rest should be cleared. + ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status))); + ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage))); + ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'), + std::string(boot.reserved, sizeof(boot.reserved))); +} + +TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Write empty vector. + std::vector<std::string> options; + std::string err; + ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err; + + // Read and verify. + bootloader_message boot; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + + // command and recovery fields should be set. + ASSERT_EQ("boot-recovery", std::string(boot.command)); + ASSERT_EQ("recovery\n", std::string(boot.recovery)); + + // The rest should be cleared. + ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status))); + ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage))); + ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'), + std::string(boot.reserved, sizeof(boot.reserved))); +} + +TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Write super long message. + std::vector<std::string> options; + for (int i = 0; i < 100; i++) { + options.push_back("option: " + std::to_string(i)); + } + + std::string err; + ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err; + + // Read and verify. + bootloader_message boot; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + + // Make sure it's long enough. + std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n"; + ASSERT_GE(expected.size(), sizeof(boot.recovery)); + + // command and recovery fields should be set. + ASSERT_EQ("boot-recovery", std::string(boot.command)); + ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery)); + ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]); + + // The rest should be cleared. + ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status))); + ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage))); + ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'), + std::string(boot.reserved, sizeof(boot.reserved))); +} + +TEST_F(BootloaderMessageTest, update_bootloader_message) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Inject some bytes into boot, which should be not overwritten later. + bootloader_message boot; + strlcpy(boot.recovery, "random message", sizeof(boot.recovery)); + strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved)); + std::string err; + ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err; + + // Update the BCB message. + std::vector<std::string> options = { "option1", "option2" }; + ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err; + + bootloader_message boot_verify; + ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err; + + // Verify that command and recovery fields should be set. + ASSERT_EQ("boot-recovery", std::string(boot_verify.command)); + std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n"; + ASSERT_EQ(expected, std::string(boot_verify.recovery)); + + // The rest should be intact. + ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status)); + ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage)); + ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved)); +} diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp new file mode 100644 index 000000000..61a1e6b64 --- /dev/null +++ b/tests/component/edify_test.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2009 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 <memory> +#include <string> + +#include <gtest/gtest.h> + +#include "edify/expr.h" + +static void expect(const char* expr_str, const char* expected) { + std::unique_ptr<Expr> e; + int error_count = 0; + EXPECT_EQ(0, parse_string(expr_str, &e, &error_count)); + EXPECT_EQ(0, error_count); + + State state(expr_str, nullptr); + + std::string result; + bool status = Evaluate(&state, e, &result); + + if (expected == nullptr) { + EXPECT_FALSE(status); + } else { + EXPECT_STREQ(expected, result.c_str()); + } + +} + +class EdifyTest : public ::testing::Test { + protected: + virtual void SetUp() { + RegisterBuiltins(); + } +}; + +TEST_F(EdifyTest, parsing) { + expect("a", "a"); + expect("\"a\"", "a"); + expect("\"\\x61\"", "a"); + expect("# this is a comment\n" + " a\n" + " \n", + "a"); +} + +TEST_F(EdifyTest, sequence) { + // sequence operator + expect("a; b; c", "c"); +} + +TEST_F(EdifyTest, concat) { + // string concat operator + expect("a + b", "ab"); + expect("a + \n \"b\"", "ab"); + expect("a + b +\nc\n", "abc"); + + // string concat function + expect("concat(a, b)", "ab"); + expect("concat(a,\n \"b\")", "ab"); + expect("concat(a + b,\nc,\"d\")", "abcd"); + expect("\"concat\"(a + b,\nc,\"d\")", "abcd"); +} + +TEST_F(EdifyTest, logical) { + // logical and + expect("a && b", "b"); + expect("a && \"\"", ""); + expect("\"\" && b", ""); + expect("\"\" && \"\"", ""); + expect("\"\" && abort()", ""); // test short-circuiting + expect("t && abort()", nullptr); + + // logical or + expect("a || b", "a"); + expect("a || \"\"", "a"); + expect("\"\" || b", "b"); + expect("\"\" || \"\"", ""); + expect("a || abort()", "a"); // test short-circuiting + expect("\"\" || abort()", NULL); + + // logical not + expect("!a", ""); + expect("! \"\"", "t"); + expect("!!a", "t"); +} + +TEST_F(EdifyTest, precedence) { + // precedence + expect("\"\" == \"\" && b", "b"); + expect("a + b == ab", "t"); + expect("ab == a + b", "t"); + expect("a + (b == ab)", "a"); + expect("(ab == a) + b", "b"); +} + +TEST_F(EdifyTest, substring) { + // substring function + expect("is_substring(cad, abracadabra)", "t"); + expect("is_substring(abrac, abracadabra)", "t"); + expect("is_substring(dabra, abracadabra)", "t"); + expect("is_substring(cad, abracxadabra)", ""); + expect("is_substring(abrac, axbracadabra)", ""); + expect("is_substring(dabra, abracadabrxa)", ""); +} + +TEST_F(EdifyTest, ifelse) { + // ifelse function + expect("ifelse(t, yes, no)", "yes"); + expect("ifelse(!t, yes, no)", "no"); + expect("ifelse(t, yes, abort())", "yes"); + expect("ifelse(!t, abort(), no)", "no"); +} + +TEST_F(EdifyTest, if_statement) { + // if "statements" + expect("if t then yes else no endif", "yes"); + expect("if \"\" then yes else no endif", "no"); + expect("if \"\" then yes endif", ""); + expect("if \"\"; t then yes endif", "yes"); +} + +TEST_F(EdifyTest, comparison) { + // numeric comparisons + expect("less_than_int(3, 14)", "t"); + expect("less_than_int(14, 3)", ""); + expect("less_than_int(x, 3)", ""); + expect("less_than_int(3, x)", ""); + expect("greater_than_int(3, 14)", ""); + expect("greater_than_int(14, 3)", "t"); + expect("greater_than_int(x, 3)", ""); + expect("greater_than_int(3, x)", ""); +} + +TEST_F(EdifyTest, big_string) { + // big string + expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str()); +} + +TEST_F(EdifyTest, unknown_function) { + // unknown function + const char* script1 = "unknown_function()"; + std::unique_ptr<Expr> expr; + int error_count = 0; + EXPECT_EQ(1, parse_string(script1, &expr, &error_count)); + EXPECT_EQ(1, error_count); + + const char* script2 = "abc; unknown_function()"; + error_count = 0; + EXPECT_EQ(1, parse_string(script2, &expr, &error_count)); + EXPECT_EQ(1, error_count); + + const char* script3 = "unknown_function1() || yes"; + error_count = 0; + EXPECT_EQ(1, parse_string(script3, &expr, &error_count)); + EXPECT_EQ(1, error_count); +} diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp new file mode 100644 index 000000000..2f648501c --- /dev/null +++ b/tests/component/imgdiff_test.cpp @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/memory.h> +#include <android-base/test_utils.h> +#include <applypatch/imgdiff.h> +#include <applypatch/imgpatch.h> +#include <gtest/gtest.h> +#include <ziparchive/zip_writer.h> + +using android::base::get_unaligned; + +static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { + std::string* s = static_cast<std::string*>(token); + s->append(reinterpret_cast<const char*>(data), len); + return len; +} + +// Sanity check for the given imgdiff patch header. +static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw, + size_t* num_deflate) { + const size_t size = patch.size(); + const char* data = patch.data(); + + ASSERT_GE(size, 12U); + ASSERT_EQ("IMGDIFF2", std::string(data, 8)); + + const int num_chunks = get_unaligned<int32_t>(data + 8); + ASSERT_GE(num_chunks, 0); + + size_t normal = 0; + size_t raw = 0; + size_t deflate = 0; + + size_t pos = 12; + for (int i = 0; i < num_chunks; ++i) { + ASSERT_LE(pos + 4, size); + int type = get_unaligned<int32_t>(data + pos); + pos += 4; + if (type == CHUNK_NORMAL) { + pos += 24; + ASSERT_LE(pos, size); + normal++; + } else if (type == CHUNK_RAW) { + ASSERT_LE(pos + 4, size); + ssize_t data_len = get_unaligned<int32_t>(data + pos); + ASSERT_GT(data_len, 0); + pos += 4 + data_len; + ASSERT_LE(pos, size); + raw++; + } else if (type == CHUNK_DEFLATE) { + pos += 60; + ASSERT_LE(pos, size); + deflate++; + } else { + FAIL() << "Invalid patch type: " << type; + } + } + + if (num_normal != nullptr) *num_normal = normal; + if (num_raw != nullptr) *num_raw = raw; + if (num_deflate != nullptr) *num_deflate = deflate; +} + +TEST(ImgdiffTest, invalid_args) { + // Insufficient inputs. + ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" })); + ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" })); + ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" })); + ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" })); + + // Failed to read bonus file. + ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" })); + + // Failed to read input files. + ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" })); + ASSERT_EQ( + 1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" })); +} + +TEST(ImgdiffTest, image_mode_smoke) { + // Random bytes. + const std::string src("abcdefg"); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + const std::string tgt("abcdefgxyz"); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect one CHUNK_RAW entry. + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(0U, num_deflate); + ASSERT_EQ(1U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, zip_mode_smoke_store) { + // Construct src and tgt zip files. + TemporaryFile src_file; + FILE* src_file_ptr = fdopen(src_file.fd, "wb"); + ZipWriter src_writer(src_file_ptr); + ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0)); // Store mode. + const std::string src_content("abcdefg"); + ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); + ASSERT_EQ(0, src_writer.FinishEntry()); + ASSERT_EQ(0, src_writer.Finish()); + ASSERT_EQ(0, fclose(src_file_ptr)); + + TemporaryFile tgt_file; + FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb"); + ZipWriter tgt_writer(tgt_file_ptr); + ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0)); // Store mode. + const std::string tgt_content("abcdefgxyz"); + ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); + ASSERT_EQ(0, tgt_writer.FinishEntry()); + ASSERT_EQ(0, tgt_writer.Finish()); + ASSERT_EQ(0, fclose(tgt_file_ptr)); + + // Compute patch. + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string tgt; + ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); + std::string src; + ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect one CHUNK_RAW entry. + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(0U, num_deflate); + ASSERT_EQ(1U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, zip_mode_smoke_compressed) { + // Construct src and tgt zip files. + TemporaryFile src_file; + FILE* src_file_ptr = fdopen(src_file.fd, "wb"); + ZipWriter src_writer(src_file_ptr); + ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); + const std::string src_content("abcdefg"); + ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); + ASSERT_EQ(0, src_writer.FinishEntry()); + ASSERT_EQ(0, src_writer.Finish()); + ASSERT_EQ(0, fclose(src_file_ptr)); + + TemporaryFile tgt_file; + FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb"); + ZipWriter tgt_writer(tgt_file_ptr); + ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); + const std::string tgt_content("abcdefgxyz"); + ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); + ASSERT_EQ(0, tgt_writer.FinishEntry()); + ASSERT_EQ(0, tgt_writer.Finish()); + ASSERT_EQ(0, fclose(tgt_file_ptr)); + + // Compute patch. + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string tgt; + ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); + std::string src; + ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(1U, num_deflate); + ASSERT_EQ(2U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { + // Construct src and tgt zip files. + TemporaryFile src_file; + FILE* src_file_ptr = fdopen(src_file.fd, "wb"); + ZipWriter src_writer(src_file_ptr); + ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); + const std::string src_content("abcdefg"); + ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); + ASSERT_EQ(0, src_writer.FinishEntry()); + ASSERT_EQ(0, src_writer.Finish()); + ASSERT_EQ(0, fclose(src_file_ptr)); + + TemporaryFile tgt_file; + FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb"); + ZipWriter tgt_writer(tgt_file_ptr); + ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); + const std::string tgt_content("abcdefgxyz"); + ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); + ASSERT_EQ(0, tgt_writer.FinishEntry()); + ASSERT_EQ(0, tgt_writer.Finish()); + // Add trailing zeros to the target zip file. + std::vector<uint8_t> zeros(10); + ASSERT_EQ(zeros.size(), fwrite(zeros.data(), sizeof(uint8_t), zeros.size(), tgt_file_ptr)); + ASSERT_EQ(0, fclose(tgt_file_ptr)); + + // Compute patch. + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string tgt; + ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); + std::string src; + ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(1U, num_deflate); + ASSERT_EQ(2U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, image_mode_simple) { + // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd). + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', + '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', + '\x00', '\x00', '\x00' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz" + gzipped "xxyyzz". + const std::vector<char> tgt_data = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b', + '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac', + '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00' + }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(1U, num_deflate); + ASSERT_EQ(2U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, image_mode_different_num_chunks) { + // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test". + const std::vector<char> src_data = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\x1f', '\x8b', '\x08', + '\x00', '\xc4', '\x1e', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', '\x02', + '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', '\x00', '\x00', '\x00', '\x1f', '\x8b', + '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d', + '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00' + }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz" + gzipped "xxyyzz". + const std::vector<char> tgt_data = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b', + '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac', + '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00' + }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(1, imgdiff(args.size(), args.data())); +} + +TEST(ImgdiffTest, image_mode_merge_chunks) { + // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd). + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', + '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', + '\x00', '\x00', '\x00' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: gzipped "xyz" + "abcdefgh". + const std::vector<char> tgt_data = { + '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', + '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', + '\x00', '\x00', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' + }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) + + // CHUNK_RAW (footer), they both should contain the same chunk types after merging. + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(1U, num_deflate); + ASSERT_EQ(2U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, image_mode_spurious_magic) { + // src: "abcdefgh" + '0x1f8b0b00' + some bytes. + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', 't', 'e', 's', 't' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz". + const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect one CHUNK_RAW (header) entry. + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(0U, num_deflate); + ASSERT_EQ(1U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, image_mode_short_input1) { + // src: "abcdefgh" + '0x1f8b0b'. + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', '\x1f', '\x8b', '\x08' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz". + const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect one CHUNK_RAW (header) entry. + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(0U, num_deflate); + ASSERT_EQ(1U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, image_mode_short_input2) { + // src: "abcdefgh" + '0x1f8b0b00'. + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', '\x1f', '\x8b', '\x08', '\x00' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz". + const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect one CHUNK_RAW (header) entry. + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(0U, num_normal); + ASSERT_EQ(0U, num_deflate); + ASSERT_EQ(1U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} + +TEST(ImgdiffTest, image_mode_single_entry_long) { + // src: "abcdefgh" + '0x1f8b0b00' + some bytes. + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', 't', 'e', 's', 't' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // tgt: "abcdefgxyz" + 200 bytes. + std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; + tgt_data.resize(tgt_data.size() + 200); + + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + + // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW. + size_t num_normal; + size_t num_raw; + size_t num_deflate; + verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); + ASSERT_EQ(1U, num_normal); + ASSERT_EQ(0U, num_deflate); + ASSERT_EQ(0U, num_raw); + + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + MemorySink, &patched)); + ASSERT_EQ(tgt, patched); +} diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp new file mode 100644 index 000000000..a5c0c1025 --- /dev/null +++ b/tests/component/install_test.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2017 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 agree 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 <stdio.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <android-base/test_utils.h> +#include <gtest/gtest.h> +#include <vintf/VintfObjectRecovery.h> +#include <ziparchive/zip_archive.h> +#include <ziparchive/zip_writer.h> + +#include "install.h" +#include "private/install.h" + +TEST(InstallTest, verify_package_compatibility_no_entry) { + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + // The archive must have something to be opened correctly. + ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + // Doesn't contain compatibility zip entry. + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + ASSERT_TRUE(verify_package_compatibility(zip)); + CloseArchive(zip); +} + +TEST(InstallTest, verify_package_compatibility_invalid_entry) { + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + // Empty compatibility zip entry. + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + ASSERT_FALSE(verify_package_compatibility(zip)); + CloseArchive(zip); +} + +TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) { + TemporaryFile compatibility_zip_file; + FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w"); + ZipWriter compatibility_zip_writer(compatibility_zip); + ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated)); + std::string malformed_xml = "malformed"; + ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size())); + ASSERT_EQ(0, compatibility_zip_writer.FinishEntry()); + ASSERT_EQ(0, compatibility_zip_writer.Finish()); + ASSERT_EQ(0, fclose(compatibility_zip)); + + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored)); + std::string compatibility_zip_content; + ASSERT_TRUE( + android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content)); + ASSERT_EQ(0, + writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size())); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + std::vector<std::string> compatibility_info; + compatibility_info.push_back(malformed_xml); + // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to + // libvintf. + std::string err; + bool result = + android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0; + ASSERT_EQ(result, verify_package_compatibility(zip)); + CloseArchive(zip); +} + +TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) { + static constexpr const char* system_manifest_xml_path = "/system/manifest.xml"; + if (access(system_manifest_xml_path, R_OK) == -1) { + GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml."; + return; + } + std::string system_manifest_xml_content; + ASSERT_TRUE( + android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content)); + TemporaryFile compatibility_zip_file; + FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w"); + ZipWriter compatibility_zip_writer(compatibility_zip); + ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated)); + ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(), + system_manifest_xml_content.size())); + ASSERT_EQ(0, compatibility_zip_writer.FinishEntry()); + ASSERT_EQ(0, compatibility_zip_writer.Finish()); + ASSERT_EQ(0, fclose(compatibility_zip)); + + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored)); + std::string compatibility_zip_content; + ASSERT_TRUE( + android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content)); + ASSERT_EQ(0, + writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size())); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + std::vector<std::string> compatibility_info; + compatibility_info.push_back(system_manifest_xml_content); + std::string err; + bool result = + android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0; + // Make sure the result is consistent with libvintf library. + ASSERT_EQ(result, verify_package_compatibility(zip)); + CloseArchive(zip); +} + +TEST(InstallTest, update_binary_command_smoke) { +#ifdef AB_OTA_UPDATER + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored)); + const std::string properties = "some_properties"; + ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size())); + ASSERT_EQ(0, writer.FinishEntry()); + // A metadata entry is mandatory. + ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored)); + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + std::string timestamp = android::base::GetProperty("ro.build.date.utc", ""); + ASSERT_NE("", timestamp); + std::string metadata = android::base::Join( + std::vector<std::string>{ + "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp, + }, + "\n"); + ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size())); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + int status_fd = 10; + std::string path = "/path/to/update.zip"; + std::vector<std::string> cmd; + ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd)); + ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]); + ASSERT_EQ("--payload=file://" + path, cmd[1]); + ASSERT_EQ("--headers=" + properties, cmd[3]); + ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]); + CloseArchive(zip); +#else + // Cannot test update_binary_command() because it tries to extract update-binary to /tmp. + GTEST_LOG_(INFO) << "Test skipped on non-A/B device."; +#endif // AB_OTA_UPDATER +} + +TEST(InstallTest, update_binary_command_invalid) { +#ifdef AB_OTA_UPDATER + TemporaryFile temp_file; + FILE* zip_file = fdopen(temp_file.fd, "w"); + ZipWriter writer(zip_file); + // Missing payload_properties.txt. + ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored)); + ASSERT_EQ(0, writer.FinishEntry()); + // A metadata entry is mandatory. + ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored)); + std::string device = android::base::GetProperty("ro.product.device", ""); + ASSERT_NE("", device); + std::string timestamp = android::base::GetProperty("ro.build.date.utc", ""); + ASSERT_NE("", timestamp); + std::string metadata = android::base::Join( + std::vector<std::string>{ + "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp, + }, + "\n"); + ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size())); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + ASSERT_EQ(0, fclose(zip_file)); + + ZipArchiveHandle zip; + ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); + int status_fd = 10; + std::string path = "/path/to/update.zip"; + std::vector<std::string> cmd; + ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd)); + CloseArchive(zip); +#else + // Cannot test update_binary_command() because it tries to extract update-binary to /tmp. + GTEST_LOG_(INFO) << "Test skipped on non-A/B device."; +#endif // AB_OTA_UPDATER +} diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp new file mode 100644 index 000000000..ea93e9b84 --- /dev/null +++ b/tests/component/sideload_test.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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 <unistd.h> +#include <gtest/gtest.h> + +TEST(SideloadTest, fusedevice) { + ASSERT_NE(-1, access("/dev/fuse", R_OK | W_OK)); +} diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp new file mode 100644 index 000000000..4f2b8164f --- /dev/null +++ b/tests/component/uncrypt_test.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2016 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 <arpa/inet.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android-base/unique_fd.h> +#include <bootloader_message/bootloader_message.h> +#include <gtest/gtest.h> + +#include "common/component_test_util.h" + +static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt"; +static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb"; +static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb"; +static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt"; +static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30; + +class UncryptTest : public ::testing::Test { + protected: + UncryptTest() : has_misc(true) {} + + virtual void SetUp() override { + ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb")); + ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb")); + ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt")); + + bool success = false; + for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { + std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, ""); + std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, ""); + std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, ""); + LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: [" + << uncrypt << "]"; + if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") { + success = true; + break; + } + sleep(1); + } + + ASSERT_TRUE(success) << "uncrypt service is not available."; + + has_misc = parse_misc(); + } + + bool has_misc; +}; + +TEST_F(UncryptTest, setup_bcb) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Trigger the setup-bcb service. + ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb")); + + // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). + sleep(1); + + struct sockaddr_un un = {}; + un.sun_family = AF_UNIX; + strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); + + int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_NE(-1, sockfd); + + // Connect to the uncrypt socket. + bool success = false; + for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { + if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) { + success = true; + break; + } + sleep(1); + } + ASSERT_TRUE(success); + + // Send out the BCB message. + std::string message = "--update_message=abc value"; + std::string message_in_bcb = "recovery\n--update_message=abc value\n"; + int length = static_cast<int>(message.size()); + int length_out = htonl(length); + ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int))) + << "Failed to write length: " << strerror(errno); + ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length)) + << "Failed to write message: " << strerror(errno); + + // Check the status code from uncrypt. + int status; + ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); + ASSERT_EQ(100U, ntohl(status)); + + // Ack having received the status code. + int code = 0; + ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); + + ASSERT_EQ(0, close(sockfd)); + + ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb")); + + // Verify the message by reading from BCB directly. + bootloader_message boot; + std::string err; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + + ASSERT_EQ("boot-recovery", std::string(boot.command)); + ASSERT_EQ(message_in_bcb, std::string(boot.recovery)); + + // The rest of the boot.recovery message should be zero'd out. + ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery)); + size_t left = sizeof(boot.recovery) - message_in_bcb.size(); + ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left)); + + // Clear the BCB. + ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; +} + +TEST_F(UncryptTest, clear_bcb) { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; + } + + // Trigger the clear-bcb service. + ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb")); + + // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). + sleep(1); + + struct sockaddr_un un = {}; + un.sun_family = AF_UNIX; + strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); + + int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_NE(-1, sockfd); + + // Connect to the uncrypt socket. + bool success = false; + for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { + if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) { + success = true; + break; + } + sleep(1); + } + ASSERT_TRUE(success); + + // Check the status code from uncrypt. + int status; + ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); + ASSERT_EQ(100U, ntohl(status)); + + // Ack having received the status code. + int code = 0; + ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); + + ASSERT_EQ(0, close(sockfd)); + + ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb")); + + // Verify the content by reading from BCB directly. + bootloader_message boot; + std::string err; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + + // All the bytes should be cleared. + ASSERT_EQ(std::string(sizeof(boot), '\0'), + std::string(reinterpret_cast<const char*>(&boot), sizeof(boot))); +} diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp new file mode 100644 index 000000000..5652ddf46 --- /dev/null +++ b/tests/component/updater_test.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2016 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 <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <memory> +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/properties.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <android-base/test_utils.h> +#include <bootloader_message/bootloader_message.h> +#include <bsdiff.h> +#include <gtest/gtest.h> +#include <ziparchive/zip_archive.h> +#include <ziparchive/zip_writer.h> + +#include "common/test_constants.h" +#include "edify/expr.h" +#include "error_code.h" +#include "otautil/SysUtil.h" +#include "print_sha1.h" +#include "updater/blockimg.h" +#include "updater/install.h" +#include "updater/updater.h" + +struct selabel_handle *sehandle = nullptr; + +static void expect(const char* expected, const char* expr_str, CauseCode cause_code, + UpdaterInfo* info = nullptr) { + std::unique_ptr<Expr> e; + int error_count = 0; + ASSERT_EQ(0, parse_string(expr_str, &e, &error_count)); + ASSERT_EQ(0, error_count); + + State state(expr_str, info); + + std::string result; + bool status = Evaluate(&state, e, &result); + + if (expected == nullptr) { + ASSERT_FALSE(status); + } else { + ASSERT_TRUE(status); + ASSERT_STREQ(expected, result.c_str()); + } + + // Error code is set in updater/updater.cpp only, by parsing State.errmsg. + ASSERT_EQ(kNoError, state.error_code); + + // Cause code should always be available. + ASSERT_EQ(cause_code, state.cause_code); +} + +static std::string get_sha1(const std::string& content) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest); + return print_sha1(digest); +} + +class UpdaterTest : public ::testing::Test { + protected: + virtual void SetUp() override { + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + } +}; + +TEST_F(UpdaterTest, getprop) { + expect(android::base::GetProperty("ro.product.device", "").c_str(), + "getprop(\"ro.product.device\")", + kNoCause); + + expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(), + "getprop(\"ro.build.fingerprint\")", + kNoCause); + + // getprop() accepts only one parameter. + expect(nullptr, "getprop()", kArgsParsingFailure); + expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure); +} + +TEST_F(UpdaterTest, sha1_check) { + // sha1_check(data) returns the SHA-1 of the data. + expect("81fe8bfe87576c3ecb22426f8e57847382917acf", "sha1_check(\"abcd\")", kNoCause); + expect("da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha1_check(\"\")", kNoCause); + + // sha1_check(data, sha1_hex, [sha1_hex, ...]) returns the matched SHA-1. + expect("81fe8bfe87576c3ecb22426f8e57847382917acf", + "sha1_check(\"abcd\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")", + kNoCause); + + expect("81fe8bfe87576c3ecb22426f8e57847382917acf", + "sha1_check(\"abcd\", \"wrong_sha1\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")", + kNoCause); + + // Or "" if there's no match. + expect("", + "sha1_check(\"abcd\", \"wrong_sha1\")", + kNoCause); + + expect("", + "sha1_check(\"abcd\", \"wrong_sha1\", \"wrong_sha2\")", + kNoCause); + + // sha1_check() expects at least one argument. + expect(nullptr, "sha1_check()", kArgsParsingFailure); +} + +TEST_F(UpdaterTest, apply_patch_check) { + // Zero-argument is not valid. + expect(nullptr, "apply_patch_check()", kArgsParsingFailure); + + // File not found. + expect("", "apply_patch_check(\"/doesntexist\")", kNoCause); + + std::string src_file = from_testdata_base("old.file"); + std::string src_content; + ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content)); + size_t src_size = src_content.size(); + std::string src_hash = get_sha1(src_content); + + // One-argument with EMMC:file:size:sha1 should pass the check. + std::string filename = android::base::Join( + std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":"); + std::string cmd = "apply_patch_check(\"" + filename + "\")"; + expect("t", cmd.c_str(), kNoCause); + + // EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check. + std::string filename_bad = android::base::Join( + std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), src_hash, + std::to_string(src_size + 1), src_hash }, + ":"); + cmd = "apply_patch_check(\"" + filename_bad + "\")"; + expect("", cmd.c_str(), kNoCause); + + // EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check. + filename_bad = + android::base::Join(std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), + src_hash, std::to_string(src_size), src_hash, + std::to_string(src_size + 1), src_hash }, + ":"); + cmd = "apply_patch_check(\"" + filename_bad + "\")"; + expect("t", cmd.c_str(), kNoCause); + + // Multiple arguments. + cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")"; + expect("", cmd.c_str(), kNoCause); + + cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash + + "\", \"wrong_sha2\")"; + expect("t", cmd.c_str(), kNoCause); + + cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash + + "\", \"wrong_sha2\")"; + expect("t", cmd.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, file_getprop) { + // file_getprop() expects two arguments. + expect(nullptr, "file_getprop()", kArgsParsingFailure); + expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // File doesn't exist. + expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFileGetPropFailure); + + // Reject too large files (current limit = 65536). + TemporaryFile temp_file1; + std::string buffer(65540, '\0'); + ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path)); + + // Read some keys. + TemporaryFile temp_file2; + std::string content("ro.product.name=tardis\n" + "# comment\n\n\n" + "ro.product.model\n" + "ro.product.board = magic \n"); + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path)); + + std::string script1("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.name\")"); + expect("tardis", script1.c_str(), kNoCause); + + std::string script2("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.board\")"); + expect("magic", script2.c_str(), kNoCause); + + // No match. + std::string script3("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.wrong\")"); + expect("", script3.c_str(), kNoCause); + + std::string script4("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.name=\")"); + expect("", script4.c_str(), kNoCause); + + std::string script5("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.nam\")"); + expect("", script5.c_str(), kNoCause); + + std::string script6("file_getprop(\"" + std::string(temp_file2.path) + + "\", \"ro.product.model\")"); + expect("", script6.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, package_extract_dir) { + // package_extract_dir expects 2 arguments. + expect(nullptr, "package_extract_dir()", kArgsParsingFailure); + expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Need to set up the ziphandle. + UpdaterInfo updater_info; + updater_info.package_zip = handle; + + // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>"). + TemporaryDir td; + std::string temp_dir(td.path); + std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")"); + expect("t", script.c_str(), kNoCause, &updater_info); + + // Verify. + std::string data; + std::string file_c = temp_dir + "/c.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + + std::string file_d = temp_dir + "/d.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + // Modify the contents in order to retry. It's expected to be overwritten. + ASSERT_TRUE(android::base::WriteStringToFile("random", file_c)); + ASSERT_TRUE(android::base::WriteStringToFile("random", file_d)); + + // Extract again and verify. + expect("t", script.c_str(), kNoCause, &updater_info); + + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + // Extracting "b/" (with slash) should give the same result. + script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + // Extracting "" is allowed. The entries will carry the path name. + script = "package_extract_dir(\"\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + std::string file_a = temp_dir + "/a.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_a, &data)); + ASSERT_EQ(kATxtContents, data); + std::string file_b = temp_dir + "/b.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b, &data)); + ASSERT_EQ(kBTxtContents, data); + std::string file_b_c = temp_dir + "/b/c.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data)); + ASSERT_EQ(kCTxtContents, data); + std::string file_b_d = temp_dir + "/b/d.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + ASSERT_EQ(0, unlink(file_a.c_str())); + ASSERT_EQ(0, unlink(file_b.c_str())); + ASSERT_EQ(0, unlink(file_b_c.c_str())); + ASSERT_EQ(0, unlink(file_b_d.c_str())); + ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str())); + + // Extracting non-existent entry should still give "t". + script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + // Only relative zip_path is allowed. + script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")"; + expect("", script.c_str(), kNoCause, &updater_info); + + // Only absolute dest_path is allowed. + script = "package_extract_dir(\"b\", \"path\")"; + expect("", script.c_str(), kNoCause, &updater_info); + + CloseArchive(handle); +} + +// TODO: Test extracting to block device. +TEST_F(UpdaterTest, package_extract_file) { + // package_extract_file expects 1 or 2 arguments. + expect(nullptr, "package_extract_file()", kArgsParsingFailure); + expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Need to set up the ziphandle. + UpdaterInfo updater_info; + updater_info.package_zip = handle; + + // Two-argument version. + TemporaryFile temp_file1; + std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")"); + expect("t", script.c_str(), kNoCause, &updater_info); + + // Verify the extracted entry. + std::string data; + ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); + ASSERT_EQ(kATxtContents, data); + + // Now extract another entry to the same location, which should overwrite. + script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); + ASSERT_EQ(kBTxtContents, data); + + // Missing zip entry. The two-argument version doesn't abort. + script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")"; + expect("", script.c_str(), kNoCause, &updater_info); + + // Extract to /dev/full should fail. + script = "package_extract_file(\"a.txt\", \"/dev/full\")"; + expect("", script.c_str(), kNoCause, &updater_info); + + // One-argument version. + script = "sha1_check(package_extract_file(\"a.txt\"))"; + expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info); + + script = "sha1_check(package_extract_file(\"b.txt\"))"; + expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info); + + // Missing entry. The one-argument version aborts the evaluation. + script = "package_extract_file(\"doesntexist\")"; + expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info); + + CloseArchive(handle); +} + +TEST_F(UpdaterTest, write_value) { + // write_value() expects two arguments. + expect(nullptr, "write_value()", kArgsParsingFailure); + expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // filename cannot be empty. + expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure); + + // Write some value to file. + TemporaryFile temp_file; + std::string value = "magicvalue"; + std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")"); + expect("t", script.c_str(), kNoCause); + + // Verify the content. + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); + ASSERT_EQ(value, content); + + // Allow writing empty string. + script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")"; + expect("t", script.c_str(), kNoCause); + + // Verify the content. + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); + ASSERT_EQ("", content); + + // It should fail gracefully when write fails. + script = "write_value(\"value\", \"/proc/0/file1\")"; + expect("", script.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, get_stage) { + // get_stage() expects one argument. + expect(nullptr, "get_stage()", kArgsParsingFailure); + expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure); + expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Set up a local file as BCB. + TemporaryFile tf; + std::string temp_file(tf.path); + bootloader_message boot; + strlcpy(boot.stage, "2/3", sizeof(boot.stage)); + std::string err; + ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); + + // Can read the stage value. + std::string script("get_stage(\"" + temp_file + "\")"); + expect("2/3", script.c_str(), kNoCause); + + // Bad BCB path. + script = "get_stage(\"doesntexist\")"; + expect("", script.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, set_stage) { + // set_stage() expects two arguments. + expect(nullptr, "set_stage()", kArgsParsingFailure); + expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Set up a local file as BCB. + TemporaryFile tf; + std::string temp_file(tf.path); + bootloader_message boot; + strlcpy(boot.command, "command", sizeof(boot.command)); + strlcpy(boot.stage, "2/3", sizeof(boot.stage)); + std::string err; + ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); + + // Write with set_stage(). + std::string script("set_stage(\"" + temp_file + "\", \"1/3\")"); + expect(tf.path, script.c_str(), kNoCause); + + // Verify. + bootloader_message boot_verify; + ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err)); + + // Stage should be updated, with command part untouched. + ASSERT_STREQ("1/3", boot_verify.stage); + ASSERT_STREQ(boot.command, boot_verify.command); + + // Bad BCB path. + script = "set_stage(\"doesntexist\", \"1/3\")"; + expect("", script.c_str(), kNoCause); + + script = "set_stage(\"/dev/full\", \"1/3\")"; + expect("", script.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, set_progress) { + // set_progress() expects one argument. + expect(nullptr, "set_progress()", kArgsParsingFailure); + expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); + + // Invalid progress argument. + expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure); + expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure); + + TemporaryFile tf; + UpdaterInfo updater_info; + updater_info.cmd_pipe = fdopen(tf.fd, "w"); + expect(".52", "set_progress(\".52\")", kNoCause, &updater_info); + fflush(updater_info.cmd_pipe); + + std::string cmd; + ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); + ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd); + // recovery-updater protocol expects 2 tokens ("set_progress <frac>"). + ASSERT_EQ(2U, android::base::Split(cmd, " ").size()); +} + +TEST_F(UpdaterTest, show_progress) { + // show_progress() expects two arguments. + expect(nullptr, "show_progress()", kArgsParsingFailure); + expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Invalid progress arguments. + expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); + expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure); + expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure); + + TemporaryFile tf; + UpdaterInfo updater_info; + updater_info.cmd_pipe = fdopen(tf.fd, "w"); + expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info); + fflush(updater_info.cmd_pipe); + + std::string cmd; + ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); + ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd); + // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>"). + ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); +} + +TEST_F(UpdaterTest, block_image_update) { + // Create a zip file with new_data and patch_data. + TemporaryFile zip_file; + FILE* zip_file_ptr = fdopen(zip_file.fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + // Add a dummy new data. + ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + // Generate and add the patch data. + std::string src_content = std::string(4096, 'a') + std::string(4096, 'c'); + std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd'); + TemporaryFile patch_file; + ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), + src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()), + tgt_content.size(), patch_file.path, nullptr)); + std::string patch_content; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content)); + ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0)); + ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed. + std::string src_hash = get_sha1(src_content); + std::string tgt_hash = get_sha1(tgt_content); + std::vector<std::string> transfer_list = { + "4", + "2", + "0", + "2", + "stash " + src_hash + " 2,0,2", + android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(), + src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()), + "free " + src_hash, + }; + ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0)); + std::string commands = android::base::Join(transfer_list, '\n'); + ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + // Stash and free some blocks, then fail the 2nd update intentionally. + std::vector<std::string> fail_transfer_list = { + "4", + "2", + "0", + "2", + "stash " + tgt_hash + " 2,0,2", + "free " + tgt_hash, + "fail", + }; + ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0)); + std::string fail_commands = android::base::Join(fail_transfer_list, '\n'); + ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); + + MemMapping map; + ASSERT_EQ(0, sysMapFile(zip_file.path, &map)); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); + + // Set up the handler, command_pipe, patch offset & length. + UpdaterInfo updater_info; + updater_info.package_zip = handle; + TemporaryFile temp_pipe; + updater_info.cmd_pipe = fopen(temp_pipe.path, "wb"); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + // Execute the commands in the 1st transfer list. + TemporaryFile update_file; + ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path)); + std::string script = "block_image_update(\"" + std::string(update_file.path) + + R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))"; + expect("t", script.c_str(), kNoCause, &updater_info); + // The update_file should be patched correctly. + std::string updated_content; + ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content)); + ASSERT_EQ(tgt_hash, get_sha1(updated_content)); + + // Expect the 2nd update to fail, but expect the stashed blocks to be freed. + script = "block_image_update(\"" + std::string(update_file.path) + + R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))"; + expect("", script.c_str(), kNoCause, &updater_info); + // Updater generates the stash name based on the input file name. + std::string name_digest = get_sha1(update_file.path); + std::string stash_base = "/cache/recovery/" + name_digest; + ASSERT_EQ(0, access(stash_base.c_str(), F_OK)); + ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK)); + ASSERT_EQ(0, rmdir(stash_base.c_str())); + + ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); + CloseArchive(handle); +} diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index 780ff2816..4c0648714 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -22,108 +22,36 @@ #include <sys/stat.h> #include <sys/types.h> -#include <memory> #include <string> #include <vector> -#include <openssl/sha.h> - +#include <android-base/file.h> #include <android-base/stringprintf.h> +#include <android-base/test_utils.h> -#include "common.h" #include "common/test_constants.h" -#include "minzip/SysUtil.h" -#include "ui.h" +#include "otautil/SysUtil.h" #include "verifier.h" -static const char* DATA_PATH = getenv("ANDROID_DATA"); -static const char* TESTDATA_PATH = "/recovery/testdata/"; - -RecoveryUI* ui = NULL; - -class MockUI : public RecoveryUI { - void Init() { } - void SetStage(int, int) { } - void SetLocale(const char*) { } - void SetBackground(Icon icon) { } - void SetSystemUpdateText(bool security_update) { } - - void SetProgressType(ProgressType determinate) { } - void ShowProgress(float portion, float seconds) { } - void SetProgress(float fraction) { } - - void ShowText(bool visible) { } - bool IsTextVisible() { return false; } - bool WasTextEverVisible() { return false; } - void Print(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - } - void PrintOnScreenOnly(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - } - void ShowFile(const char*) { } - - void StartMenu(const char* const * headers, const char* const * items, - int initial_selection) { } - int SelectMenu(int sel) { return 0; } - void EndMenu() { } -}; - -void -ui_print(const char* format, ...) { - va_list ap; - va_start(ap, format); - vfprintf(stdout, format, ap); - va_end(ap); -} +using namespace std::string_literals; class VerifierTest : public testing::TestWithParam<std::vector<std::string>> { - public: - MemMapping memmap; - std::vector<Certificate> certs; - - virtual void SetUp() { - std::vector<std::string> args = GetParam(); - std::string package = - android::base::StringPrintf("%s%s%s%s", DATA_PATH, NATIVE_TEST_PATH, - TESTDATA_PATH, args[0].c_str()); - if (sysMapFile(package.c_str(), &memmap) != 0) { - FAIL() << "Failed to mmap " << package << ": " << strerror(errno) - << "\n"; - } - - for (auto it = ++(args.cbegin()); it != args.cend(); ++it) { - if (it->substr(it->length() - 3, it->length()) == "256") { - if (certs.empty()) { - FAIL() << "May only specify -sha256 after key type\n"; - } - certs.back().hash_len = SHA256_DIGEST_LENGTH; - } else { - std::string public_key_file = android::base::StringPrintf( - "%s%s%stest_key_%s.txt", DATA_PATH, NATIVE_TEST_PATH, - TESTDATA_PATH, it->c_str()); - ASSERT_TRUE(load_keys(public_key_file.c_str(), certs)); - certs.back().hash_len = SHA_DIGEST_LENGTH; - } - } - if (certs.empty()) { - std::string public_key_file = android::base::StringPrintf( - "%s%s%stest_key_e3.txt", DATA_PATH, NATIVE_TEST_PATH, - TESTDATA_PATH); - ASSERT_TRUE(load_keys(public_key_file.c_str(), certs)); - certs.back().hash_len = SHA_DIGEST_LENGTH; - } + protected: + void SetUp() override { + std::vector<std::string> args = GetParam(); + std::string package = from_testdata_base(args[0]); + if (sysMapFile(package.c_str(), &memmap) != 0) { + FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; } - static void SetUpTestCase() { - ui = new MockUI(); + for (auto it = ++args.cbegin(); it != args.cend(); ++it) { + std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt"); + ASSERT_TRUE(load_keys(public_key_file.c_str(), certs)); } + } + + MemMapping memmap; + std::vector<Certificate> certs; }; class VerifierSuccessTest : public VerifierTest { @@ -132,47 +60,120 @@ class VerifierSuccessTest : public VerifierTest { class VerifierFailureTest : public VerifierTest { }; +TEST(VerifierTest, load_keys_multiple_keys) { + std::string testkey_v4; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4)); + + std::string testkey_v3; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); + + std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4; + TemporaryFile key_file1; + ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path)); + std::vector<Certificate> certs; + ASSERT_TRUE(load_keys(key_file1.path, certs)); + ASSERT_EQ(3U, certs.size()); +} + +TEST(VerifierTest, load_keys_invalid_keys) { + std::vector<Certificate> certs; + ASSERT_FALSE(load_keys("/doesntexist", certs)); + + // Empty file. + TemporaryFile key_file1; + ASSERT_FALSE(load_keys(key_file1.path, certs)); + + // Invalid contents. + ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path)); + ASSERT_FALSE(load_keys(key_file1.path, certs)); + + std::string testkey_v4; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4)); + + // Invalid key version: "v4 ..." => "v6 ...". + std::string invalid_key2(testkey_v4); + invalid_key2[1] = '6'; + TemporaryFile key_file2; + ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path)); + ASSERT_FALSE(load_keys(key_file2.path, certs)); + + // Invalid key content: inserted extra bytes ",2209831334". + std::string invalid_key3(testkey_v4); + invalid_key3.insert(invalid_key2.size() - 2, ",2209831334"); + TemporaryFile key_file3; + ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path)); + ASSERT_FALSE(load_keys(key_file3.path, certs)); + + // Invalid key: the last key must not end with an extra ','. + std::string invalid_key4 = testkey_v4 + ","; + TemporaryFile key_file4; + ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path)); + ASSERT_FALSE(load_keys(key_file4.path, certs)); + + // Invalid key separator. + std::string invalid_key5 = testkey_v4 + ";" + testkey_v4; + TemporaryFile key_file5; + ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path)); + ASSERT_FALSE(load_keys(key_file5.path, certs)); +} + +TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) { + std::string testkey_v3; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); + + TemporaryFile key_file; + ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path)); + std::vector<Certificate> certs; + ASSERT_TRUE(load_keys(key_file.path, certs)); + + // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369). + std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s; + ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast<const unsigned char*>(package.data()), + package.size(), certs)); +} + TEST_P(VerifierSuccessTest, VerifySucceed) { - ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_SUCCESS); + ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS); } TEST_P(VerifierFailureTest, VerifyFailure) { - ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_FAILURE); + ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE); } INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest, - ::testing::Values( - std::vector<std::string>({"otasigned.zip", "e3"}), - std::vector<std::string>({"otasigned_f4.zip", "f4"}), - std::vector<std::string>({"otasigned_sha256.zip", "e3", "sha256"}), - std::vector<std::string>({"otasigned_f4_sha256.zip", "f4", "sha256"}), - std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "ec", "sha256"}))); + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v1"}), + std::vector<std::string>({"otasigned_v2.zip", "v2"}), + std::vector<std::string>({"otasigned_v3.zip", "v3"}), + std::vector<std::string>({"otasigned_v4.zip", "v4"}), + std::vector<std::string>({"otasigned_v5.zip", "v5"}))); INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest, - ::testing::Values( - std::vector<std::string>({"otasigned.zip", "f4", "e3"}), - std::vector<std::string>({"otasigned_f4.zip", "ec", "f4"}), - std::vector<std::string>({"otasigned_sha256.zip", "ec", "e3", "e3", "sha256"}), - std::vector<std::string>({"otasigned_f4_sha256.zip", "ec", "sha256", "e3", "f4", "sha256"}), - std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "f4", "sha256", "e3", "ec", "sha256"}))); + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}), + std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}), + std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}), + std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}), + std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"}))); INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest, - ::testing::Values( - std::vector<std::string>({"otasigned.zip", "f4"}), - std::vector<std::string>({"otasigned_f4.zip", "e3"}), - std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "e3", "sha256"}))); + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v2"}), + std::vector<std::string>({"otasigned_v2.zip", "v1"}), + std::vector<std::string>({"otasigned_v3.zip", "v5"}), + std::vector<std::string>({"otasigned_v4.zip", "v5"}), + std::vector<std::string>({"otasigned_v5.zip", "v3"}))); INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest, - ::testing::Values( - std::vector<std::string>({"otasigned.zip", "e3", "sha256"}), - std::vector<std::string>({"otasigned_f4.zip", "f4", "sha256"}), - std::vector<std::string>({"otasigned_sha256.zip"}), - std::vector<std::string>({"otasigned_f4_sha256.zip", "f4"}), - std::vector<std::string>({"otasigned_ecdsa_sha256.zip"}))); + ::testing::Values( + std::vector<std::string>({"otasigned_v1.zip", "v3"}), + std::vector<std::string>({"otasigned_v2.zip", "v4"}), + std::vector<std::string>({"otasigned_v3.zip", "v1"}), + std::vector<std::string>({"otasigned_v4.zip", "v2"}))); INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest, - ::testing::Values( - std::vector<std::string>({"random.zip"}), - std::vector<std::string>({"fake-eocd.zip"}), - std::vector<std::string>({"alter-metadata.zip"}), - std::vector<std::string>({"alter-footer.zip"}))); + ::testing::Values( + std::vector<std::string>({"random.zip", "v1"}), + std::vector<std::string>({"fake-eocd.zip", "v1"}), + std::vector<std::string>({"alter-metadata.zip", "v1"}), + std::vector<std::string>({"alter-footer.zip", "v1"}))); diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp new file mode 100644 index 000000000..d36dd331e --- /dev/null +++ b/tests/manual/recovery_test.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2016 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 <dirent.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/strings.h> +#include <android/log.h> +#include <gtest/gtest.h> +#include <png.h> +#include <private/android_logger.h> + +#include "minui/minui.h" + +static const std::string myFilename = "/data/misc/recovery/inject.txt"; +static const std::string myContent = "Hello World\nWelcome to my recovery\n"; +static const std::string kLocale = "zu"; +static const std::string kResourceTestDir = "/data/nativetest/recovery/"; + +// Failure is expected on systems that do not deliver either the +// recovery-persist or recovery-refresh executables. Tests also require +// a reboot sequence of test to truly verify. + +static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename, + const char *buf, size_t len, void *arg) { + EXPECT_EQ(LOG_ID_SYSTEM, logId); + EXPECT_EQ(ANDROID_LOG_INFO, prio); + EXPECT_NE(std::string::npos, myFilename.find(filename)); + EXPECT_EQ(myContent, buf); + EXPECT_EQ(myContent.size(), len); + EXPECT_EQ(nullptr, arg); + return len; +} + +// recovery.refresh - May fail. Requires recovery.inject, two reboots, +// then expect success after second reboot. +TEST(recovery, refresh) { + EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK)); + + ssize_t ret = __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr); + if (ret == -ENOENT) { + EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, + myFilename.c_str(), myContent.c_str(), myContent.size())); + + fprintf(stderr, "injected test data, requires two intervening reboots " + "to check for replication\n"); + } + EXPECT_EQ(static_cast<ssize_t>(myContent.size()), ret); +} + +// recovery.persist - Requires recovery.inject, then a reboot, then +// expect success after for this test on that boot. +TEST(recovery, persist) { + EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK)); + + ssize_t ret = __android_log_pmsg_file_read( + LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr); + if (ret == -ENOENT) { + EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, + myFilename.c_str(), myContent.c_str(), myContent.size())); + + fprintf(stderr, "injected test data, requires intervening reboot " + "to check for storage\n"); + } + + std::string buf; + EXPECT_TRUE(android::base::ReadFileToString(myFilename, &buf)); + EXPECT_EQ(myContent, buf); + if (access(myFilename.c_str(), F_OK) == 0) { + fprintf(stderr, "Removing persistent test data, " + "check if reconstructed on reboot\n"); + } + EXPECT_EQ(0, unlink(myFilename.c_str())); +} + +std::vector<std::string> image_dir { + "res-mdpi/images/", + "res-hdpi/images/", + "res-xhdpi/images/", + "res-xxhdpi/images/", + "res-xxxhdpi/images/" +}; + +static int png_filter(const dirent* de) { + if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) { + return 0; + } + return 1; +} + +// Find out all png files to test under /data/nativetest/recovery/. +static std::vector<std::string> add_files() { + std::vector<std::string> files; + for (const std::string& str : image_dir) { + std::string dir_path = kResourceTestDir + str; + dirent** namelist; + int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort); + if (n == -1) { + printf("Failed to scan dir %s: %s\n", kResourceTestDir.c_str(), strerror(errno)); + return files; + } + if (n == 0) { + printf("No file is added for test in %s\n", kResourceTestDir.c_str()); + } + + while (n--) { + std::string file_path = dir_path + namelist[n]->d_name; + files.push_back(file_path); + free(namelist[n]); + } + free(namelist); + } + return files; +} + +class ResourceTest : public testing::TestWithParam<std::string> { + public: + static std::vector<std::string> png_list; + + // Parse a png file and test if it's qualified for the background text image + // under recovery. + void SetUp() override { + std::string file_path = GetParam(); + fp = fopen(file_path.c_str(), "rb"); + ASSERT_NE(nullptr, fp); + + unsigned char header[8]; + size_t bytesRead = fread(header, 1, sizeof(header), fp); + ASSERT_EQ(sizeof(header), bytesRead); + ASSERT_EQ(0, png_sig_cmp(header, 0, sizeof(header))); + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + ASSERT_NE(nullptr, png_ptr); + + info_ptr = png_create_info_struct(png_ptr); + ASSERT_NE(nullptr, info_ptr); + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, sizeof(header)); + png_read_info(png_ptr, info_ptr); + + int color_type, bit_depth; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, + nullptr); + ASSERT_EQ(PNG_COLOR_TYPE_GRAY, color_type) << "Recovery expects grayscale PNG file."; + ASSERT_LT(static_cast<png_uint_32>(5), width); + ASSERT_LT(static_cast<png_uint_32>(0), height); + if (bit_depth <= 8) { + // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + + png_byte channels = png_get_channels(png_ptr, info_ptr); + ASSERT_EQ(1, channels) << "Recovery background text images expects 1-channel PNG file."; + } + + void TearDown() override { + if (png_ptr != nullptr && info_ptr != nullptr) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + } + + if (fp != nullptr) { + fclose(fp); + } + } + + protected: + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + + FILE* fp; +}; + +std::vector<std::string> ResourceTest::png_list = add_files(); + +TEST_P(ResourceTest, ValidateLocale) { + std::vector<unsigned char> row(width); + for (png_uint_32 y = 0; y < height; ++y) { + png_read_row(png_ptr, row.data(), nullptr); + int w = (row[1] << 8) | row[0]; + int h = (row[3] << 8) | row[2]; + int len = row[4]; + EXPECT_LT(0, w); + EXPECT_LT(0, h); + EXPECT_LT(0, len) << "Locale string should be non-empty."; + EXPECT_NE(0, row[5]) << "Locale string is missing."; + + ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; + char* loc = reinterpret_cast<char*>(&row[5]); + if (matches_locale(loc, kLocale.c_str())) { + EXPECT_TRUE(android::base::StartsWith(loc, kLocale.c_str())); + break; + } else { + for (int i = 0; i < h; ++i, ++y) { + png_read_row(png_ptr, row.data(), nullptr); + } + } + } +} + +INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourceTest, + ::testing::ValuesIn(ResourceTest::png_list.cbegin(), + ResourceTest::png_list.cend())); diff --git a/tests/testdata/bonus.file b/tests/testdata/bonus.file Binary files differnew file mode 100644 index 000000000..918ef8ac5 --- /dev/null +++ b/tests/testdata/bonus.file diff --git a/tests/testdata/boot.img b/tests/testdata/boot.img Binary files differnew file mode 100644 index 000000000..dd4897510 --- /dev/null +++ b/tests/testdata/boot.img diff --git a/tests/testdata/otasigned.zip b/tests/testdata/otasigned_v1.zip Binary files differindex a6bc53e41..a6bc53e41 100644 --- a/tests/testdata/otasigned.zip +++ b/tests/testdata/otasigned_v1.zip diff --git a/tests/testdata/otasigned_f4.zip b/tests/testdata/otasigned_v2.zip Binary files differindex dd1e4dd40..dd1e4dd40 100644 --- a/tests/testdata/otasigned_f4.zip +++ b/tests/testdata/otasigned_v2.zip diff --git a/tests/testdata/otasigned_sha256.zip b/tests/testdata/otasigned_v3.zip Binary files differindex 0ed4409b3..0ed4409b3 100644 --- a/tests/testdata/otasigned_sha256.zip +++ b/tests/testdata/otasigned_v3.zip diff --git a/tests/testdata/otasigned_f4_sha256.zip b/tests/testdata/otasigned_v4.zip Binary files differindex 3af408c40..3af408c40 100644 --- a/tests/testdata/otasigned_f4_sha256.zip +++ b/tests/testdata/otasigned_v4.zip diff --git a/tests/testdata/otasigned_ecdsa_sha256.zip b/tests/testdata/otasigned_v5.zip Binary files differindex 999fcdd0f..999fcdd0f 100644 --- a/tests/testdata/otasigned_ecdsa_sha256.zip +++ b/tests/testdata/otasigned_v5.zip diff --git a/tests/testdata/recovery-from-boot-with-bonus.p b/tests/testdata/recovery-from-boot-with-bonus.p Binary files differnew file mode 100644 index 000000000..08b6f55e4 --- /dev/null +++ b/tests/testdata/recovery-from-boot-with-bonus.p diff --git a/tests/testdata/recovery-from-boot.p b/tests/testdata/recovery-from-boot.p Binary files differnew file mode 100644 index 000000000..06f6c299f --- /dev/null +++ b/tests/testdata/recovery-from-boot.p diff --git a/tests/testdata/recovery.img b/tests/testdata/recovery.img Binary files differnew file mode 100644 index 000000000..b862e6f0c --- /dev/null +++ b/tests/testdata/recovery.img diff --git a/tests/testdata/testkey.pk8 b/tests/testdata/testkey_v1.pk8 Binary files differindex 586c1bd5c..586c1bd5c 100644 --- a/tests/testdata/testkey.pk8 +++ b/tests/testdata/testkey_v1.pk8 diff --git a/tests/testdata/test_key_e3.txt b/tests/testdata/testkey_v1.txt index 53f5297bd..53f5297bd 100644 --- a/tests/testdata/test_key_e3.txt +++ b/tests/testdata/testkey_v1.txt diff --git a/tests/testdata/testkey.x509.pem b/tests/testdata/testkey_v1.x509.pem index e242d83e2..e242d83e2 100644 --- a/tests/testdata/testkey.x509.pem +++ b/tests/testdata/testkey_v1.x509.pem diff --git a/tests/testdata/test_f4.pk8 b/tests/testdata/testkey_v2.pk8 Binary files differindex 3052613c5..3052613c5 100644 --- a/tests/testdata/test_f4.pk8 +++ b/tests/testdata/testkey_v2.pk8 diff --git a/tests/testdata/test_key_f4.txt b/tests/testdata/testkey_v2.txt index 54ddbbad1..54ddbbad1 100644 --- a/tests/testdata/test_key_f4.txt +++ b/tests/testdata/testkey_v2.txt diff --git a/tests/testdata/test_f4.x509.pem b/tests/testdata/testkey_v2.x509.pem index 814abcf99..814abcf99 100644 --- a/tests/testdata/test_f4.x509.pem +++ b/tests/testdata/testkey_v2.x509.pem diff --git a/tests/testdata/testkey_v3.pk8 b/tests/testdata/testkey_v3.pk8 new file mode 120000 index 000000000..18ecf9815 --- /dev/null +++ b/tests/testdata/testkey_v3.pk8 @@ -0,0 +1 @@ +testkey_v1.pk8
\ No newline at end of file diff --git a/tests/testdata/testkey_v3.txt b/tests/testdata/testkey_v3.txt new file mode 100644 index 000000000..3208571a5 --- /dev/null +++ b/tests/testdata/testkey_v3.txt @@ -0,0 +1 @@ +v3 {64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}} diff --git a/tests/testdata/testkey_sha256.x509.pem b/tests/testdata/testkey_v3.x509.pem index 002ce8968..002ce8968 100644 --- a/tests/testdata/testkey_sha256.x509.pem +++ b/tests/testdata/testkey_v3.x509.pem diff --git a/tests/testdata/testkey_v4.pk8 b/tests/testdata/testkey_v4.pk8 new file mode 120000 index 000000000..683b9a3f1 --- /dev/null +++ b/tests/testdata/testkey_v4.pk8 @@ -0,0 +1 @@ +testkey_v2.pk8
\ No newline at end of file diff --git a/tests/testdata/testkey_v4.txt b/tests/testdata/testkey_v4.txt new file mode 100644 index 000000000..532cbd51a --- /dev/null +++ b/tests/testdata/testkey_v4.txt @@ -0,0 +1 @@ +v4 {64,0xc9bd1f21,{293133087,3210546773,865313125,250921607,3158780490,943703457,1242806226,2986289859,2942743769,2457906415,2719374299,1783459420,149579627,3081531591,3440738617,2788543742,2758457512,1146764939,3699497403,2446203424,1744968926,1159130537,2370028300,3978231572,3392699980,1487782451,1180150567,2841334302,3753960204,961373345,3333628321,748825784,2978557276,1566596926,1613056060,2600292737,1847226629,50398611,1890374404,2878700735,2286201787,1401186359,619285059,731930817,2340993166,1156490245,2992241729,151498140,318782170,3480838990,2100383433,4223552555,3628927011,4247846280,1759029513,4215632601,2719154626,3490334597,1751299340,3487864726,3668753795,4217506054,3748782284,3150295088},{1772626313,445326068,3477676155,1758201194,2986784722,491035581,3922936562,702212696,2979856666,3324974564,2488428922,3056318590,1626954946,664714029,398585816,3964097931,3356701905,2298377729,2040082097,3025491477,539143308,3348777868,2995302452,3602465520,212480763,2691021393,1307177300,704008044,2031136606,1054106474,3838318865,2441343869,1477566916,700949900,2534790355,3353533667,336163563,4106790558,2701448228,1571536379,1103842411,3623110423,1635278839,1577828979,910322800,715583630,138128831,1017877531,2289162787,447994798,1897243165,4121561445,4150719842,2131821093,2262395396,3305771534,980753571,3256525190,3128121808,1072869975,3507939515,4229109952,118381341,2209831334}} diff --git a/tests/testdata/test_f4_sha256.x509.pem b/tests/testdata/testkey_v4.x509.pem index 9d5376b45..9d5376b45 100644 --- a/tests/testdata/test_f4_sha256.x509.pem +++ b/tests/testdata/testkey_v4.x509.pem diff --git a/tests/testdata/testkey_ecdsa.pk8 b/tests/testdata/testkey_v5.pk8 Binary files differindex 9a521c8cf..9a521c8cf 100644 --- a/tests/testdata/testkey_ecdsa.pk8 +++ b/tests/testdata/testkey_v5.pk8 diff --git a/tests/testdata/test_key_ec.txt b/tests/testdata/testkey_v5.txt index 72b4395d9..72b4395d9 100644 --- a/tests/testdata/test_key_ec.txt +++ b/tests/testdata/testkey_v5.txt diff --git a/tests/testdata/testkey_ecdsa.x509.pem b/tests/testdata/testkey_v5.x509.pem index b12283645..b12283645 100644 --- a/tests/testdata/testkey_ecdsa.x509.pem +++ b/tests/testdata/testkey_v5.x509.pem diff --git a/tests/testdata/ziptest_dummy-update.zip b/tests/testdata/ziptest_dummy-update.zip Binary files differnew file mode 100644 index 000000000..6976bf155 --- /dev/null +++ b/tests/testdata/ziptest_dummy-update.zip diff --git a/tests/testdata/ziptest_valid.zip b/tests/testdata/ziptest_valid.zip Binary files differnew file mode 100644 index 000000000..9e7cb7800 --- /dev/null +++ b/tests/testdata/ziptest_valid.zip diff --git a/tests/unit/asn1_decoder_test.cpp b/tests/unit/asn1_decoder_test.cpp index af96d87d2..b334a655b 100644 --- a/tests/unit/asn1_decoder_test.cpp +++ b/tests/unit/asn1_decoder_test.cpp @@ -14,225 +14,188 @@ * limitations under the License. */ -#define LOG_TAG "asn1_decoder_test" +#include <stdint.h> + +#include <memory> -#include <cutils/log.h> #include <gtest/gtest.h> -#include <stdint.h> -#include <unistd.h> #include "asn1_decoder.h" -namespace android { - -class Asn1DecoderTest : public testing::Test { -}; - -TEST_F(Asn1DecoderTest, Empty_Failure) { - uint8_t empty[] = { }; - asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty)); - - EXPECT_EQ(NULL, asn1_constructed_get(ctx)); - EXPECT_FALSE(asn1_constructed_skip_all(ctx)); - EXPECT_EQ(0, asn1_constructed_type(ctx)); - EXPECT_EQ(NULL, asn1_sequence_get(ctx)); - EXPECT_EQ(NULL, asn1_set_get(ctx)); - EXPECT_FALSE(asn1_sequence_next(ctx)); - - uint8_t* junk; - size_t length; - EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length)); - EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length)); - - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) { - uint8_t truncated[] = { 0xA0, 0x82, }; - asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); - EXPECT_EQ(NULL, asn1_constructed_get(ctx)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) { - uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, - 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, }; - asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); - EXPECT_EQ(NULL, asn1_constructed_get(ctx)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) { - uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - asn1_context_t* ptr = asn1_constructed_get(ctx); - ASSERT_NE((asn1_context_t*)NULL, ptr); - EXPECT_EQ(5, asn1_constructed_type(ptr)); - uint8_t* oid; - size_t length; - EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); - asn1_context_free(ptr); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, ConstructedGet_Success) { - uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - asn1_context_t* ptr = asn1_constructed_get(ctx); - ASSERT_NE((asn1_context_t*)NULL, ptr); - EXPECT_EQ(5, asn1_constructed_type(ptr)); - uint8_t* oid; - size_t length; - ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); - EXPECT_EQ(1U, length); - EXPECT_EQ(0x01U, *oid); - asn1_context_free(ptr); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) { - uint8_t truncated[] = { 0xA2, 0x82, }; - asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); - EXPECT_FALSE(asn1_constructed_skip_all(ctx)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) { - uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, - 0xA1, 0x03, 0x02, 0x01, 0x01, - 0x06, 0x01, 0xA5, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - ASSERT_TRUE(asn1_constructed_skip_all(ctx)); - uint8_t* oid; - size_t length; - ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length)); - EXPECT_EQ(1U, length); - EXPECT_EQ(0xA5U, *oid); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) { - uint8_t truncated[] = { 0x30, 0x82, }; - asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); - EXPECT_EQ(NULL, asn1_sequence_get(ctx)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) { - uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - asn1_context_t* ptr = asn1_sequence_get(ctx); - ASSERT_NE((asn1_context_t*)NULL, ptr); - uint8_t* oid; - size_t length; - EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); - asn1_context_free(ptr); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, SequenceGet_Success) { - uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - asn1_context_t* ptr = asn1_sequence_get(ctx); - ASSERT_NE((asn1_context_t*)NULL, ptr); - uint8_t* oid; - size_t length; - ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); - EXPECT_EQ(1U, length); - EXPECT_EQ(0x01U, *oid); - asn1_context_free(ptr); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) { - uint8_t truncated[] = { 0x31, 0x82, }; - asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated)); - EXPECT_EQ(NULL, asn1_set_get(ctx)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) { - uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - asn1_context_t* ptr = asn1_set_get(ctx); - ASSERT_NE((asn1_context_t*)NULL, ptr); - uint8_t* oid; - size_t length; - EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length)); - asn1_context_free(ptr); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, SetGet_Success) { - uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - asn1_context_t* ptr = asn1_set_get(ctx); - ASSERT_NE((asn1_context_t*)NULL, ptr); - uint8_t* oid; - size_t length; - ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length)); - EXPECT_EQ(1U, length); - EXPECT_EQ(0xBAU, *oid); - asn1_context_free(ptr); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) { - uint8_t data[] = { 0x06, 0x00, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - uint8_t* oid; - size_t length; - EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) { - uint8_t data[] = { 0x06, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - uint8_t* oid; - size_t length; - EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, OidGet_Success) { - uint8_t data[] = { 0x06, 0x01, 0x99, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - uint8_t* oid; - size_t length; - ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length)); - EXPECT_EQ(1U, length); - EXPECT_EQ(0x99U, *oid); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) { - uint8_t data[] = { 0x04, 0x00, 0x55, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - uint8_t* string; - size_t length; - ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) { - uint8_t data[] = { 0x04, 0x01, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - uint8_t* string; - size_t length; - ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length)); - asn1_context_free(ctx); -} - -TEST_F(Asn1DecoderTest, OctetStringGet_Success) { - uint8_t data[] = { 0x04, 0x01, 0xAA, }; - asn1_context_t* ctx = asn1_context_new(data, sizeof(data)); - uint8_t* string; - size_t length; - ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length)); - EXPECT_EQ(1U, length); - EXPECT_EQ(0xAAU, *string); - asn1_context_free(ctx); -} - -} // namespace android +TEST(Asn1DecoderTest, Empty_Failure) { + uint8_t empty[] = {}; + asn1_context ctx(empty, sizeof(empty)); + + ASSERT_EQ(nullptr, ctx.asn1_constructed_get()); + ASSERT_FALSE(ctx.asn1_constructed_skip_all()); + ASSERT_EQ(0, ctx.asn1_constructed_type()); + ASSERT_EQ(nullptr, ctx.asn1_sequence_get()); + ASSERT_EQ(nullptr, ctx.asn1_set_get()); + ASSERT_FALSE(ctx.asn1_sequence_next()); + + const uint8_t* junk; + size_t length; + ASSERT_FALSE(ctx.asn1_oid_get(&junk, &length)); + ASSERT_FALSE(ctx.asn1_octet_string_get(&junk, &length)); +} + +TEST(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0xA0, 0x82 }; + asn1_context ctx(truncated, sizeof(truncated)); + ASSERT_EQ(nullptr, ctx.asn1_constructed_get()); +} + +TEST(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) { + uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A }; + asn1_context ctx(truncated, sizeof(truncated)); + ASSERT_EQ(nullptr, ctx.asn1_constructed_get()); +} + +TEST(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01 }; + asn1_context ctx(data, sizeof(data)); + std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get()); + ASSERT_NE(nullptr, ptr); + ASSERT_EQ(5, ptr->asn1_constructed_type()); + const uint8_t* oid; + size_t length; + ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length)); +} + +TEST(Asn1DecoderTest, ConstructedGet_Success) { + uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01 }; + asn1_context ctx(data, sizeof(data)); + std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get()); + ASSERT_NE(nullptr, ptr); + ASSERT_EQ(5, ptr->asn1_constructed_type()); + const uint8_t* oid; + size_t length; + ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length)); + ASSERT_EQ(1U, length); + ASSERT_EQ(0x01U, *oid); +} + +TEST(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) { + uint8_t truncated[] = { 0xA2, 0x82 }; + asn1_context ctx(truncated, sizeof(truncated)); + ASSERT_FALSE(ctx.asn1_constructed_skip_all()); +} + +TEST(Asn1DecoderTest, ConstructedSkipAll_Success) { + uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, 0xA1, 0x03, 0x02, 0x01, 0x01, 0x06, 0x01, 0xA5 }; + asn1_context ctx(data, sizeof(data)); + ASSERT_TRUE(ctx.asn1_constructed_skip_all()); + const uint8_t* oid; + size_t length; + ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length)); + ASSERT_EQ(1U, length); + ASSERT_EQ(0xA5U, *oid); +} + +TEST(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0x30, 0x82 }; + asn1_context ctx(truncated, sizeof(truncated)); + ASSERT_EQ(nullptr, ctx.asn1_sequence_get()); +} + +TEST(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01 }; + asn1_context ctx(data, sizeof(data)); + std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get()); + ASSERT_NE(nullptr, ptr); + const uint8_t* oid; + size_t length; + ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length)); +} + +TEST(Asn1DecoderTest, SequenceGet_Success) { + uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01 }; + asn1_context ctx(data, sizeof(data)); + std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get()); + ASSERT_NE(nullptr, ptr); + const uint8_t* oid; + size_t length; + ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length)); + ASSERT_EQ(1U, length); + ASSERT_EQ(0x01U, *oid); +} + +TEST(Asn1DecoderTest, SetGet_TruncatedLength_Failure) { + uint8_t truncated[] = { 0x31, 0x82 }; + asn1_context ctx(truncated, sizeof(truncated)); + ASSERT_EQ(nullptr, ctx.asn1_set_get()); +} + +TEST(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) { + uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01 }; + asn1_context ctx(data, sizeof(data)); + std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get()); + ASSERT_NE(nullptr, ptr); + const uint8_t* oid; + size_t length; + ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length)); +} + +TEST(Asn1DecoderTest, SetGet_Success) { + uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA }; + asn1_context ctx(data, sizeof(data)); + std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get()); + ASSERT_NE(nullptr, ptr); + const uint8_t* oid; + size_t length; + ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length)); + ASSERT_EQ(1U, length); + ASSERT_EQ(0xBAU, *oid); +} + +TEST(Asn1DecoderTest, OidGet_LengthZero_Failure) { + uint8_t data[] = { 0x06, 0x00, 0x01 }; + asn1_context ctx(data, sizeof(data)); + const uint8_t* oid; + size_t length; + ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length)); +} + +TEST(Asn1DecoderTest, OidGet_TooSmall_Failure) { + uint8_t data[] = { 0x06, 0x01 }; + asn1_context ctx(data, sizeof(data)); + const uint8_t* oid; + size_t length; + ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length)); +} + +TEST(Asn1DecoderTest, OidGet_Success) { + uint8_t data[] = { 0x06, 0x01, 0x99 }; + asn1_context ctx(data, sizeof(data)); + const uint8_t* oid; + size_t length; + ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length)); + ASSERT_EQ(1U, length); + ASSERT_EQ(0x99U, *oid); +} + +TEST(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) { + uint8_t data[] = { 0x04, 0x00, 0x55 }; + asn1_context ctx(data, sizeof(data)); + const uint8_t* string; + size_t length; + ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length)); +} + +TEST(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) { + uint8_t data[] = { 0x04, 0x01 }; + asn1_context ctx(data, sizeof(data)); + const uint8_t* string; + size_t length; + ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length)); +} + +TEST(Asn1DecoderTest, OctetStringGet_Success) { + uint8_t data[] = { 0x04, 0x01, 0xAA }; + asn1_context ctx(data, sizeof(data)); + const uint8_t* string; + size_t length; + ASSERT_TRUE(ctx.asn1_octet_string_get(&string, &length)); + ASSERT_EQ(1U, length); + ASSERT_EQ(0xAAU, *string); +} diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp new file mode 100644 index 000000000..5e2ae4fb5 --- /dev/null +++ b/tests/unit/dirutil_test.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 2016 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 <errno.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include <android-base/test_utils.h> +#include <gtest/gtest.h> +#include <otautil/DirUtil.h> + +TEST(DirUtilTest, create_invalid) { + // Requesting to create an empty dir is invalid. + ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, false, nullptr)); + ASSERT_EQ(ENOENT, errno); + + // Requesting to strip the name with no slash present. + ASSERT_EQ(-1, dirCreateHierarchy("abc", 0755, nullptr, true, nullptr)); + ASSERT_EQ(ENOENT, errno); + + // Creating a dir that already exists. + TemporaryDir td; + ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr)); + + // "///" is a valid dir. + ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, false, nullptr)); + + // Request to create a dir, but a file with the same name already exists. + TemporaryFile tf; + ASSERT_EQ(-1, dirCreateHierarchy(tf.path, 0755, nullptr, false, nullptr)); + ASSERT_EQ(ENOTDIR, errno); +} + +TEST(DirUtilTest, create_smoke) { + TemporaryDir td; + std::string prefix(td.path); + std::string path = prefix + "/a/b"; + constexpr mode_t mode = 0755; + ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, nullptr, false, nullptr)); + + // Verify. + struct stat sb; + ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno); + ASSERT_TRUE(S_ISDIR(sb.st_mode)); + constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO; + ASSERT_EQ(mode, sb.st_mode & mask); + + // Clean up. + ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str())); + ASSERT_EQ(0, rmdir((prefix + "/a").c_str())); +} + +TEST(DirUtilTest, create_strip_filename) { + TemporaryDir td; + std::string prefix(td.path); + std::string path = prefix + "/a/b"; + ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr)); + + // Verify that "../a" exists but not "../a/b". + struct stat sb; + ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno); + ASSERT_TRUE(S_ISDIR(sb.st_mode)); + + ASSERT_EQ(-1, stat(path.c_str(), &sb)); + ASSERT_EQ(ENOENT, errno); + + // Clean up. + ASSERT_EQ(0, rmdir((prefix + "/a").c_str())); +} + +TEST(DirUtilTest, create_mode_and_timestamp) { + TemporaryDir td; + std::string prefix(td.path); + std::string path = prefix + "/a/b"; + // Set the timestamp to 8/1/2008. + constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; + constexpr mode_t mode = 0751; + ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, ×tamp, false, nullptr)); + + // Verify the mode and timestamp for "../a/b". + struct stat sb; + ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno); + ASSERT_TRUE(S_ISDIR(sb.st_mode)); + constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO; + ASSERT_EQ(mode, sb.st_mode & mask); + + timespec time; + time.tv_sec = 1217592000; + time.tv_nsec = 0; + + ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime)); + ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime)); + + // Verify the mode for "../a". Note that the timestamp for intermediate directories (e.g. "../a") + // may not be 'timestamp' according to the current implementation. + ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno); + ASSERT_TRUE(S_ISDIR(sb.st_mode)); + ASSERT_EQ(mode, sb.st_mode & mask); + + // Clean up. + ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str())); + ASSERT_EQ(0, rmdir((prefix + "/a").c_str())); +} + +TEST(DirUtilTest, unlink_invalid) { + // File doesn't exist. + ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist")); + + // Nonexistent directory. + TemporaryDir td; + std::string path(td.path); + ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str())); + ASSERT_EQ(ENOENT, errno); +} + +TEST(DirUtilTest, unlink_smoke) { + // Unlink a file. + TemporaryFile tf; + ASSERT_EQ(0, dirUnlinkHierarchy(tf.path)); + ASSERT_EQ(-1, access(tf.path, F_OK)); + + TemporaryDir td; + std::string path(td.path); + constexpr mode_t mode = 0700; + ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode)); + ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode)); + ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode)); + ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode)); + + // Remove "../a" recursively. + ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str())); + + // Verify it's gone. + ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK)); +} diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp index 0e515f8c1..cdaba0e8b 100644 --- a/tests/unit/locale_test.cpp +++ b/tests/unit/locale_test.cpp @@ -19,11 +19,15 @@ #include "minui/minui.h" TEST(LocaleTest, Misc) { - EXPECT_TRUE(matches_locale("zh_CN", "zh_CN_#Hans")); - EXPECT_TRUE(matches_locale("zh", "zh_CN_#Hans")); - EXPECT_FALSE(matches_locale("zh_HK", "zh_CN_#Hans")); - EXPECT_TRUE(matches_locale("en_GB", "en_GB")); - EXPECT_TRUE(matches_locale("en", "en_GB")); - EXPECT_FALSE(matches_locale("en_GB", "en")); - EXPECT_FALSE(matches_locale("en_GB", "en_US")); + EXPECT_TRUE(matches_locale("zh-CN", "zh-Hans-CN")); + EXPECT_TRUE(matches_locale("zh", "zh-Hans-CN")); + EXPECT_FALSE(matches_locale("zh-HK", "zh-Hans-CN")); + EXPECT_TRUE(matches_locale("en-GB", "en-GB")); + EXPECT_TRUE(matches_locale("en", "en-GB")); + EXPECT_FALSE(matches_locale("en-GB", "en")); + EXPECT_FALSE(matches_locale("en-GB", "en-US")); + EXPECT_FALSE(matches_locale("en-US", "")); + // Empty locale prefix in the PNG file will match the input locale. + EXPECT_TRUE(matches_locale("", "en-US")); + EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA")); } diff --git a/tests/unit/recovery_test.cpp b/tests/unit/recovery_test.cpp deleted file mode 100644 index f397f258e..000000000 --- a/tests/unit/recovery_test.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2016 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 <fcntl.h> -#include <string.h> -#include <sys/types.h> -#include <unistd.h> - -#include <android/log.h> -#include <gtest/gtest.h> -#include <log/logger.h> -#include <private/android_logger.h> - -static const char myFilename[] = "/data/misc/recovery/inject.txt"; -static const char myContent[] = "Hello World\nWelcome to my recovery\n"; - -// Failure is expected on systems that do not deliver either the -// recovery-persist or recovery-refresh executables. Tests also require -// a reboot sequence of test to truly verify. - -static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename, - const char *buf, size_t len, void *arg) { - EXPECT_EQ(LOG_ID_SYSTEM, logId); - EXPECT_EQ(ANDROID_LOG_INFO, prio); - EXPECT_EQ(0, NULL == strstr(myFilename,filename)); - EXPECT_EQ(0, strcmp(myContent, buf)); - EXPECT_EQ(sizeof(myContent), len); - EXPECT_EQ(0, NULL != arg); - return len; -} - -// recovery.refresh - May fail. Requires recovery.inject, two reboots, -// then expect success after second reboot. -TEST(recovery, refresh) { - EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK)); - - ssize_t ret = __android_log_pmsg_file_read( - LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL); - if (ret == -ENOENT) { - EXPECT_LT(0, __android_log_pmsg_file_write( - LOG_ID_SYSTEM, ANDROID_LOG_INFO, - myFilename, myContent, sizeof(myContent))); - fprintf(stderr, "injected test data, " - "requires two intervening reboots " - "to check for replication\n"); - } - EXPECT_EQ((ssize_t)sizeof(myContent), ret); -} - -// recovery.persist - Requires recovery.inject, then a reboot, then -// expect success after for this test on that boot. -TEST(recovery, persist) { - EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK)); - - ssize_t ret = __android_log_pmsg_file_read( - LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL); - if (ret == -ENOENT) { - EXPECT_LT(0, __android_log_pmsg_file_write( - LOG_ID_SYSTEM, ANDROID_LOG_INFO, - myFilename, myContent, sizeof(myContent))); - fprintf(stderr, "injected test data, " - "requires intervening reboot " - "to check for storage\n"); - } - - int fd = open(myFilename, O_RDONLY); - EXPECT_LE(0, fd); - - char buf[sizeof(myContent) + 32]; - ret = read(fd, buf, sizeof(buf)); - close(fd); - EXPECT_EQ(ret, (ssize_t)sizeof(myContent)); - EXPECT_EQ(0, strcmp(myContent, buf)); - if (fd >= 0) { - fprintf(stderr, "Removing persistent test data, " - "check if reconstructed on reboot\n"); - } - EXPECT_EQ(0, unlink(myFilename)); -} diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp new file mode 100644 index 000000000..f4699664b --- /dev/null +++ b/tests/unit/sysutil_test.cpp @@ -0,0 +1,140 @@ +/* + * Copyright 2016 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 <gtest/gtest.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/test_utils.h> + +#include "otautil/SysUtil.h" + +TEST(SysUtilTest, InvalidArgs) { + MemMapping mapping; + + // Invalid argument. + ASSERT_EQ(-1, sysMapFile(nullptr, &mapping)); + ASSERT_EQ(-1, sysMapFile("/somefile", nullptr)); +} + +TEST(SysUtilTest, sysMapFileRegularFile) { + TemporaryFile temp_file1; + std::string content = "abc"; + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file1.path)); + + // sysMapFile() should map the file to one range. + MemMapping mapping; + ASSERT_EQ(0, sysMapFile(temp_file1.path, &mapping)); + ASSERT_NE(nullptr, mapping.addr); + ASSERT_EQ(content.size(), mapping.length); + ASSERT_EQ(1U, mapping.ranges.size()); + + sysReleaseMap(&mapping); + ASSERT_EQ(0U, mapping.ranges.size()); +} + +TEST(SysUtilTest, sysMapFileBlockMap) { + // Create a file that has 10 blocks. + TemporaryFile package; + std::string content; + constexpr size_t file_size = 4096 * 10; + content.reserve(file_size); + ASSERT_TRUE(android::base::WriteStringToFile(content, package.path)); + + TemporaryFile block_map_file; + std::string filename = std::string("@") + block_map_file.path; + MemMapping mapping; + + // One range. + std::string block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n"; + ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); + + ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_EQ(file_size, mapping.length); + ASSERT_EQ(1U, mapping.ranges.size()); + + // It's okay to not have the trailing '\n'. + block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10"; + ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); + + ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_EQ(file_size, mapping.length); + ASSERT_EQ(1U, mapping.ranges.size()); + + // Or having multiple trailing '\n's. + block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n\n\n"; + ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); + + ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_EQ(file_size, mapping.length); + ASSERT_EQ(1U, mapping.ranges.size()); + + // Multiple ranges. + block_map_content = std::string(package.path) + "\n40960 4096\n3\n0 3\n3 5\n5 10\n"; + ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path)); + + ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping)); + ASSERT_EQ(file_size, mapping.length); + ASSERT_EQ(3U, mapping.ranges.size()); + + sysReleaseMap(&mapping); + ASSERT_EQ(0U, mapping.ranges.size()); +} + +TEST(SysUtilTest, sysMapFileBlockMapInvalidBlockMap) { + MemMapping mapping; + TemporaryFile temp_file; + std::string filename = std::string("@") + temp_file.path; + + // Block map file is too short. + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + // Block map file has unexpected number of lines. + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n2\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + // Invalid size/blksize/range_count. + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\nabc 4096\n1\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + // size/blksize/range_count don't match. + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n0 4096\n1\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 0\n1\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + // Invalid block dev path. + ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path)); + ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping)); + + sysReleaseMap(&mapping); + ASSERT_EQ(0U, mapping.ranges.size()); +} diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp new file mode 100644 index 000000000..4a1a49b97 --- /dev/null +++ b/tests/unit/zip_test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 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 <errno.h> +#include <unistd.h> + +#include <memory> +#include <vector> + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gtest/gtest.h> +#include <otautil/SysUtil.h> +#include <otautil/ZipUtil.h> +#include <ziparchive/zip_archive.h> + +#include "common/test_constants.h" + +TEST(ZipTest, ExtractPackageRecursive) { + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Extract the whole package into a temp directory. + TemporaryDir td; + ASSERT_NE(nullptr, td.path); + ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr); + + // Make sure all the files are extracted correctly. + std::string path(td.path); + ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK)); + + // The content of the file is the same as expected. + std::string content1; + ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1)); + ASSERT_EQ(kATxtContents, content1); + + std::string content2; + ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2)); + ASSERT_EQ(kDTxtContents, content2); + + CloseArchive(handle); + + // Clean up. + ASSERT_EQ(0, unlink((path + "/a.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/b.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str())); + ASSERT_EQ(0, rmdir((path + "/b").c_str())); +} + +TEST(ZipTest, OpenFromMemory) { + MemMapping map; + std::string zip_path = from_testdata_base("ziptest_dummy-update.zip"); + ASSERT_EQ(0, sysMapFile(zip_path.c_str(), &map)); + + // Map an update package into memory and open the archive from there. + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle)); + + static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary"; + ZipString binary_path(BINARY_PATH); + ZipEntry binary_entry; + // Make sure the package opens correctly and its entry can be read. + ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry)); + + TemporaryFile tmp_binary; + ASSERT_NE(-1, tmp_binary.fd); + ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd)); + + CloseArchive(handle); + sysReleaseMap(&map); +} + diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp new file mode 100644 index 000000000..14e541690 --- /dev/null +++ b/tests/unit/ziputil_test.cpp @@ -0,0 +1,191 @@ +/* + * Copyright 2016 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 <errno.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <gtest/gtest.h> +#include <otautil/ZipUtil.h> +#include <ziparchive/zip_archive.h> + +#include "common/test_constants.h" + +TEST(ZipUtilTest, invalid_args) { + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // zip_path must be a relative path. + ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr)); + + // dest_path must be an absolute path. + ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr)); + ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr)); + + CloseArchive(handle); +} + +TEST(ZipUtilTest, extract_all) { + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Extract the whole package into a temp directory. + TemporaryDir td; + ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr); + + // Make sure all the files are extracted correctly. + std::string path(td.path); + ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK)); + + // The content of the file is the same as expected. + std::string content1; + ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1)); + ASSERT_EQ(kATxtContents, content1); + + std::string content2; + ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2)); + ASSERT_EQ(kDTxtContents, content2); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink((path + "/a.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/b.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str())); + ASSERT_EQ(0, rmdir((path + "/b").c_str())); + + CloseArchive(handle); +} + +TEST(ZipUtilTest, extract_prefix_with_slash) { + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Extract all the entries starting with "b/". + TemporaryDir td; + ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr); + + // Make sure all the files with "b/" prefix are extracted correctly. + std::string path(td.path); + ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK)); + + // And the rest are not extracted. + ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK)); + ASSERT_EQ(ENOENT, errno); + ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK)); + ASSERT_EQ(ENOENT, errno); + + // The content of the file is the same as expected. + std::string content1; + ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1)); + ASSERT_EQ(kCTxtContents, content1); + + std::string content2; + ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2)); + ASSERT_EQ(kDTxtContents, content2); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink((path + "/c.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/d.txt").c_str())); + + CloseArchive(handle); +} + +TEST(ZipUtilTest, extract_prefix_without_slash) { + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Extract all the file entries starting with "b/". + TemporaryDir td; + ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr); + + // Make sure all the files with "b/" prefix are extracted correctly. + std::string path(td.path); + ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK)); + ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK)); + + // And the rest are not extracted. + ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK)); + ASSERT_EQ(ENOENT, errno); + ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK)); + ASSERT_EQ(ENOENT, errno); + + // The content of the file is the same as expected. + std::string content1; + ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1)); + ASSERT_EQ(kCTxtContents, content1); + + std::string content2; + ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2)); + ASSERT_EQ(kDTxtContents, content2); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink((path + "/c.txt").c_str())); + ASSERT_EQ(0, unlink((path + "/d.txt").c_str())); + + CloseArchive(handle); +} + +TEST(ZipUtilTest, set_timestamp) { + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Set the timestamp to 8/1/2008. + constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; + + // Extract all the entries starting with "b/". + TemporaryDir td; + ExtractPackageRecursive(handle, "b", td.path, ×tamp, nullptr); + + // Make sure all the files with "b/" prefix are extracted correctly. + std::string path(td.path); + std::string file_c = path + "/c.txt"; + std::string file_d = path + "/d.txt"; + ASSERT_EQ(0, access(file_c.c_str(), F_OK)); + ASSERT_EQ(0, access(file_d.c_str(), F_OK)); + + // Verify the timestamp. + timespec time; + time.tv_sec = 1217592000; + time.tv_nsec = 0; + + struct stat sb; + ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno); + ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime)); + ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime)); + + ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno); + ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime)); + ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime)); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + CloseArchive(handle); +} diff --git a/tools/ota/Android.mk b/tools/dumpkey/Android.mk index 142c3b257..31549146d 100644 --- a/tools/ota/Android.mk +++ b/tools/dumpkey/Android.mk @@ -15,19 +15,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_MODULE := add-property-tag -LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) -LOCAL_MODULE_TAGS := debug -LOCAL_SRC_FILES := add-property-tag.c -LOCAL_STATIC_LIBRARIES := libc -include $(BUILD_EXECUTABLE) - -include $(CLEAR_VARS) -LOCAL_FORCE_STATIC_EXECUTABLE := true -LOCAL_MODULE := check-lost+found -LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) -LOCAL_MODULE_TAGS := debug -LOCAL_SRC_FILES := check-lost+found.c -LOCAL_STATIC_LIBRARIES := libcutils libc -include $(BUILD_EXECUTABLE) +LOCAL_MODULE := dumpkey +LOCAL_SRC_FILES := DumpPublicKey.java +LOCAL_JAR_MANIFEST := DumpPublicKey.mf +LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/dumpkey/DumpPublicKey.java b/tools/dumpkey/DumpPublicKey.java new file mode 100644 index 000000000..3eb139842 --- /dev/null +++ b/tools/dumpkey/DumpPublicKey.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dumpkey; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.FileInputStream; +import java.math.BigInteger; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.KeyStore; +import java.security.Key; +import java.security.PublicKey; +import java.security.Security; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.ECPoint; + +/** + * Command line tool to extract RSA public keys from X.509 certificates + * and output source code with data initializers for the keys. + * @hide + */ +class DumpPublicKey { + /** + * @param key to perform sanity checks on + * @return version number of key. Supported versions are: + * 1: 2048-bit RSA key with e=3 and SHA-1 hash + * 2: 2048-bit RSA key with e=65537 and SHA-1 hash + * 3: 2048-bit RSA key with e=3 and SHA-256 hash + * 4: 2048-bit RSA key with e=65537 and SHA-256 hash + * @throws Exception if the key has the wrong size or public exponent + */ + static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception { + BigInteger pubexp = key.getPublicExponent(); + BigInteger modulus = key.getModulus(); + int version; + + if (pubexp.equals(BigInteger.valueOf(3))) { + version = useSHA256 ? 3 : 1; + } else if (pubexp.equals(BigInteger.valueOf(65537))) { + version = useSHA256 ? 4 : 2; + } else { + throw new Exception("Public exponent should be 3 or 65537 but is " + + pubexp.toString(10) + "."); + } + + if (modulus.bitLength() != 2048) { + throw new Exception("Modulus should be 2048 bits long but is " + + modulus.bitLength() + " bits."); + } + + return version; + } + + /** + * @param key to perform sanity checks on + * @return version number of key. Supported versions are: + * 5: 256-bit EC key with curve NIST P-256 + * @throws Exception if the key has the wrong size or public exponent + */ + static int checkEC(ECPublicKey key) throws Exception { + if (key.getParams().getCurve().getField().getFieldSize() != 256) { + throw new Exception("Curve must be NIST P-256"); + } + + return 5; + } + + /** + * Perform sanity check on public key. + */ + static int check(PublicKey key, boolean useSHA256) throws Exception { + if (key instanceof RSAPublicKey) { + return checkRSA((RSAPublicKey) key, useSHA256); + } else if (key instanceof ECPublicKey) { + if (!useSHA256) { + throw new Exception("Must use SHA-256 with EC keys!"); + } + return checkEC((ECPublicKey) key); + } else { + throw new Exception("Unsupported key class: " + key.getClass().getName()); + } + } + + /** + * @param key to output + * @return a String representing this public key. If the key is a + * version 1 key, the string will be a C initializer; this is + * not true for newer key versions. + */ + static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception { + int version = check(key, useSHA256); + + BigInteger N = key.getModulus(); + + StringBuilder result = new StringBuilder(); + + int nwords = N.bitLength() / 32; // # of 32 bit integers in modulus + + if (version > 1) { + result.append("v"); + result.append(Integer.toString(version)); + result.append(" "); + } + + result.append("{"); + result.append(nwords); + + BigInteger B = BigInteger.valueOf(0x100000000L); // 2^32 + BigInteger N0inv = B.subtract(N.modInverse(B)); // -1 / N[0] mod 2^32 + + result.append(",0x"); + result.append(N0inv.toString(16)); + + BigInteger R = BigInteger.valueOf(2).pow(N.bitLength()); + BigInteger RR = R.multiply(R).mod(N); // 2^4096 mod N + + // Write out modulus as little endian array of integers. + result.append(",{"); + for (int i = 0; i < nwords; ++i) { + long n = N.mod(B).longValue(); + result.append(n); + + if (i != nwords - 1) { + result.append(","); + } + + N = N.divide(B); + } + result.append("}"); + + // Write R^2 as little endian array of integers. + result.append(",{"); + for (int i = 0; i < nwords; ++i) { + long rr = RR.mod(B).longValue(); + result.append(rr); + + if (i != nwords - 1) { + result.append(","); + } + + RR = RR.divide(B); + } + result.append("}"); + + result.append("}"); + return result.toString(); + } + + /** + * @param key to output + * @return a String representing this public key. If the key is a + * version 1 key, the string will be a C initializer; this is + * not true for newer key versions. + */ + static String printEC(ECPublicKey key) throws Exception { + int version = checkEC(key); + + StringBuilder result = new StringBuilder(); + + result.append("v"); + result.append(Integer.toString(version)); + result.append(" "); + + BigInteger X = key.getW().getAffineX(); + BigInteger Y = key.getW().getAffineY(); + int nbytes = key.getParams().getCurve().getField().getFieldSize() / 8; // # of 32 bit integers in X coordinate + + result.append("{"); + result.append(nbytes); + + BigInteger B = BigInteger.valueOf(0x100L); // 2^8 + + // Write out Y coordinate as array of characters. + result.append(",{"); + for (int i = 0; i < nbytes; ++i) { + long n = X.mod(B).longValue(); + result.append(n); + + if (i != nbytes - 1) { + result.append(","); + } + + X = X.divide(B); + } + result.append("}"); + + // Write out Y coordinate as array of characters. + result.append(",{"); + for (int i = 0; i < nbytes; ++i) { + long n = Y.mod(B).longValue(); + result.append(n); + + if (i != nbytes - 1) { + result.append(","); + } + + Y = Y.divide(B); + } + result.append("}"); + + result.append("}"); + return result.toString(); + } + + static String print(PublicKey key, boolean useSHA256) throws Exception { + if (key instanceof RSAPublicKey) { + return printRSA((RSAPublicKey) key, useSHA256); + } else if (key instanceof ECPublicKey) { + return printEC((ECPublicKey) key); + } else { + throw new Exception("Unsupported key class: " + key.getClass().getName()); + } + } + + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: DumpPublicKey certfile ... > source.c"); + System.exit(1); + } + Security.addProvider(new BouncyCastleProvider()); + try { + for (int i = 0; i < args.length; i++) { + FileInputStream input = new FileInputStream(args[i]); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(input); + + boolean useSHA256 = false; + String sigAlg = cert.getSigAlgName(); + if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) { + // SignApk has historically accepted "MD5withRSA" + // certificates, but treated them as "SHA1withRSA" + // anyway. Continue to do so for backwards + // compatibility. + useSHA256 = false; + } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) { + useSHA256 = true; + } else { + System.err.println(args[i] + ": unsupported signature algorithm \"" + + sigAlg + "\""); + System.exit(1); + } + + PublicKey key = cert.getPublicKey(); + check(key, useSHA256); + System.out.print(print(key, useSHA256)); + System.out.println(i < args.length - 1 ? "," : ""); + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.exit(0); + } +} diff --git a/tools/dumpkey/DumpPublicKey.mf b/tools/dumpkey/DumpPublicKey.mf new file mode 100644 index 000000000..7bb3bc88d --- /dev/null +++ b/tools/dumpkey/DumpPublicKey.mf @@ -0,0 +1 @@ +Main-Class: com.android.dumpkey.DumpPublicKey diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c deleted file mode 100644 index aab30b2d0..000000000 --- a/tools/ota/add-property-tag.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2008 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 <ctype.h> -#include <errno.h> -#include <getopt.h> -#include <limits.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -/* - * Append a tag to a property value in a .prop file if it isn't already there. - * Normally used to modify build properties to record incremental updates. - */ - -// Return nonzero if the tag should be added to this line. -int should_tag(const char *line, const char *propname) { - const char *prop = strstr(line, propname); - if (prop == NULL) return 0; - - // Make sure this is actually the property name (not an accidental hit) - const char *ptr; - for (ptr = line; ptr < prop && isspace(*ptr); ++ptr) ; - if (ptr != prop) return 0; // Must be at the beginning of the line - - for (ptr += strlen(propname); *ptr != '\0' && isspace(*ptr); ++ptr) ; - return (*ptr == '='); // Must be followed by a '=' -} - -// Remove existing tags from the line, return the following number (if any) -int remove_tag(char *line, const char *tag) { - char *pos = strstr(line, tag); - if (pos == NULL) return 0; - - char *end; - int num = strtoul(pos + strlen(tag), &end, 10); - strcpy(pos, end); - return num; -} - -// Write line to output with the tag added, adding a number (if >0) -void write_tagged(FILE *out, const char *line, const char *tag, int number) { - const char *end = line + strlen(line); - while (end > line && isspace(end[-1])) --end; - if (number > 0) { - fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end); - } else { - fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end); - } -} - -int main(int argc, char **argv) { - const char *filename = "/system/build.prop"; - const char *propname = "ro.build.fingerprint"; - const char *tag = NULL; - int do_remove = 0, do_number = 0; - - int opt; - while ((opt = getopt(argc, argv, "f:p:rn")) != -1) { - switch (opt) { - case 'f': filename = optarg; break; - case 'p': propname = optarg; break; - case 'r': do_remove = 1; break; - case 'n': do_number = 1; break; - case '?': return 2; - } - } - - if (argc != optind + 1) { - fprintf(stderr, - "usage: add-property-tag [flags] tag-to-add\n" - "flags: -f /dir/file.prop (default /system/build.prop)\n" - " -p prop.name (default ro.build.fingerprint)\n" - " -r (if set, remove the tag rather than adding it)\n" - " -n (if set, add and increment a number after the tag)\n"); - return 2; - } - - tag = argv[optind]; - FILE *input = fopen(filename, "r"); - if (input == NULL) { - fprintf(stderr, "can't read %s: %s\n", filename, strerror(errno)); - return 1; - } - - char tmpname[PATH_MAX]; - snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename); - FILE *output = fopen(tmpname, "w"); - if (output == NULL) { - fprintf(stderr, "can't write %s: %s\n", tmpname, strerror(errno)); - return 1; - } - - int found = 0; - char line[4096]; - while (fgets(line, sizeof(line), input)) { - if (!should_tag(line, propname)) { - fputs(line, output); // Pass through unmodified - } else { - found = 1; - int number = remove_tag(line, tag); - if (do_remove) { - fputs(line, output); // Remove the tag but don't re-add it - } else { - write_tagged(output, line, tag, number + do_number); - } - } - } - - fclose(input); - fclose(output); - - if (!found) { - fprintf(stderr, "property %s not found in %s\n", propname, filename); - remove(tmpname); - return 1; - } - - if (rename(tmpname, filename)) { - fprintf(stderr, "can't rename %s to %s: %s\n", - tmpname, filename, strerror(errno)); - remove(tmpname); - return 1; - } - - return 0; -} diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c deleted file mode 100644 index 8ce12d39f..000000000 --- a/tools/ota/check-lost+found.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2008 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 <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/klog.h> -#include <sys/reboot.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> - -#include "private/android_filesystem_config.h" - -// Sentinel file used to track whether we've forced a reboot -static const char *kMarkerFile = "/data/misc/check-lost+found-rebooted-2"; - -// Output file in tombstones directory (first 8K will be uploaded) -static const char *kOutputDir = "/data/tombstones"; -static const char *kOutputFile = "/data/tombstones/check-lost+found-log"; - -// Partitions to check -static const char *kPartitions[] = { "/system", "/data", "/cache", NULL }; - -/* - * 1. If /data/misc/forced-reboot is missing, touch it & force "unclean" boot. - * 2. Write a log entry with the number of files in lost+found directories. - */ - -int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { - mkdir(kOutputDir, 0755); - chown(kOutputDir, AID_SYSTEM, AID_SYSTEM); - FILE *out = fopen(kOutputFile, "a"); - if (out == NULL) { - fprintf(stderr, "Can't write %s: %s\n", kOutputFile, strerror(errno)); - return 1; - } - - // Note: only the first 8K of log will be uploaded, so be terse. - time_t start = time(NULL); - fprintf(out, "*** check-lost+found ***\nStarted: %s", ctime(&start)); - - struct stat st; - if (stat(kMarkerFile, &st)) { - // No reboot marker -- need to force an unclean reboot. - // But first, try to create the marker file. If that fails, - // skip the reboot, so we don't get caught in an infinite loop. - - int fd = open(kMarkerFile, O_WRONLY|O_CREAT, 0444); - if (fd >= 0 && close(fd) == 0) { - fprintf(out, "Wrote %s, rebooting\n", kMarkerFile); - fflush(out); - sync(); // Make sure the marker file is committed to disk - - // If possible, dirty each of these partitions before rebooting, - // to make sure the filesystem has to do a scan on mount. - int i; - for (i = 0; kPartitions[i] != NULL; ++i) { - char fn[PATH_MAX]; - snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "dirty"); - fd = open(fn, O_WRONLY|O_CREAT, 0444); - if (fd >= 0) { // Don't sweat it if we can't write the file. - TEMP_FAILURE_RETRY(write(fd, fn, sizeof(fn))); // write, you know, some data - close(fd); - unlink(fn); - } - } - - reboot(RB_AUTOBOOT); // reboot immediately, with dirty filesystems - fprintf(out, "Reboot failed?!\n"); - exit(1); - } else { - fprintf(out, "Can't write %s: %s\n", kMarkerFile, strerror(errno)); - } - } else { - fprintf(out, "Found %s\n", kMarkerFile); - } - - int i; - for (i = 0; kPartitions[i] != NULL; ++i) { - char fn[PATH_MAX]; - snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "lost+found"); - DIR *dir = opendir(fn); - if (dir == NULL) { - fprintf(out, "Can't open %s: %s\n", fn, strerror(errno)); - } else { - int count = 0; - struct dirent *ent; - while ((ent = readdir(dir))) { - if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) - ++count; - } - closedir(dir); - if (count > 0) { - fprintf(out, "OMGZ FOUND %d FILES IN %s\n", count, fn); - } else { - fprintf(out, "%s is clean\n", fn); - } - } - } - - char dmesg[131073]; - int len = klogctl(KLOG_READ_ALL, dmesg, sizeof(dmesg) - 1); - if (len < 0) { - fprintf(out, "Can't read kernel log: %s\n", strerror(errno)); - } else { // To conserve space, only write lines with certain keywords - fprintf(out, "--- Kernel log ---\n"); - dmesg[len] = '\0'; - char *saveptr, *line; - int in_yaffs = 0; - for (line = strtok_r(dmesg, "\n", &saveptr); line != NULL; - line = strtok_r(NULL, "\n", &saveptr)) { - if (strstr(line, "yaffs: dev is")) in_yaffs = 1; - - if (in_yaffs || - strstr(line, "yaffs") || - strstr(line, "mtd") || - strstr(line, "msm_nand")) { - fprintf(out, "%s\n", line); - } - - if (strstr(line, "yaffs_read_super: isCheckpointed")) in_yaffs = 0; - } - } - - return 0; -} diff --git a/tools/ota/convert-to-bmp.py b/tools/ota/convert-to-bmp.py deleted file mode 100644 index 446c09da8..000000000 --- a/tools/ota/convert-to-bmp.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/python2.4 - -"""A simple script to convert asset images to BMP files, that supports -RGBA image.""" - -import struct -import Image -import sys - -infile = sys.argv[1] -outfile = sys.argv[2] - -if not outfile.endswith(".bmp"): - print >> sys.stderr, "Warning: I'm expecting to write BMP files." - -im = Image.open(infile) -if im.mode == 'RGB': - im.save(outfile) -elif im.mode == 'RGBA': - # Python Imaging Library doesn't write RGBA BMP files, so we roll - # our own. - - BMP_HEADER_FMT = ("<" # little-endian - "H" # signature - "L" # file size - "HH" # reserved (set to 0) - "L" # offset to start of bitmap data) - ) - - BITMAPINFO_HEADER_FMT= ("<" # little-endian - "L" # size of this struct - "L" # width - "L" # height - "H" # planes (set to 1) - "H" # bit count - "L" # compression (set to 0 for minui) - "L" # size of image data (0 if uncompressed) - "L" # x pixels per meter (1) - "L" # y pixels per meter (1) - "L" # colors used (0) - "L" # important colors (0) - ) - - fileheadersize = struct.calcsize(BMP_HEADER_FMT) - infoheadersize = struct.calcsize(BITMAPINFO_HEADER_FMT) - - header = struct.pack(BMP_HEADER_FMT, - 0x4d42, # "BM" in little-endian - (fileheadersize + infoheadersize + - im.size[0] * im.size[1] * 4), - 0, 0, - fileheadersize + infoheadersize) - - info = struct.pack(BITMAPINFO_HEADER_FMT, - infoheadersize, - im.size[0], - im.size[1], - 1, - 32, - 0, - 0, - 1, - 1, - 0, - 0) - - f = open(outfile, "wb") - f.write(header) - f.write(info) - data = im.tostring() - for j in range(im.size[1]-1, -1, -1): # rows bottom-to-top - for i in range(j*im.size[0]*4, (j+1)*im.size[0]*4, 4): - f.write(data[i+2]) # B - f.write(data[i+1]) # G - f.write(data[i+0]) # R - f.write(data[i+3]) # A - f.close() -else: - print >> sys.stderr, "Don't know how to handle image mode '%s'." % (im.mode,) diff --git a/tools/recovery_l10n/README.md b/tools/recovery_l10n/README.md new file mode 100644 index 000000000..e9e85d65f --- /dev/null +++ b/tools/recovery_l10n/README.md @@ -0,0 +1,36 @@ +# Steps to regenerate background text images under res-*dpi/images/ + +1. Build the recovery_l10n app: + + cd bootable/recovery && mma -j32 + +2. Install the app on the device (or emulator) with the intended dpi. + + * For example, we can use Nexus 5 to generate the text images under + res-xxhdpi. + * We can set up the maximum width of the final png image in res/layout/main.xml + Currently, the image width is 1200px for xxxhdpi, 900px for xxhdpi and + 480px for xhdpi/hdpi/mdpi. + * When using the emulator, make sure the NDK version matches the current + repository. Otherwise, the app may not work properly. + + adb install $PATH_TO_APP + +3. Run the app, select the string to translate and press the 'go' button. + +4. After the app goes through the strings for all locales, pull the output png + file from the device. + + adb root && adb pull /data/data/com.android.recovery_l10n/files/text-out.png + +5. Compress the output file put it under the corresponding directory. + + * "pngcrush -c 0 ..." converts "text-out.png" into a 1-channel image, + which is accepted by Recovery. This also compresses the image file by + ~60%. + * zopflipng could further compress the png files by ~10%, more details + in https://github.com/google/zopfli/blob/master/README.zopflipng + * If you're using other png compression tools, make sure the final text + image works by running graphic tests under the recovery mode. + + pngcrush -c 0 text-out.png $OUTPUT_PNG diff --git a/tools/recovery_l10n/res/values-az-rAZ/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml index c6765a9ea..c6765a9ea 100644 --- a/tools/recovery_l10n/res/values-az-rAZ/strings.xml +++ b/tools/recovery_l10n/res/values-az/strings.xml diff --git a/tools/recovery_l10n/res/values-be-rBY/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml index 7c0954d31..7c0954d31 100644 --- a/tools/recovery_l10n/res/values-be-rBY/strings.xml +++ b/tools/recovery_l10n/res/values-be/strings.xml diff --git a/tools/recovery_l10n/res/values-bn-rBD/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml index 0a481faf1..0a481faf1 100644 --- a/tools/recovery_l10n/res/values-bn-rBD/strings.xml +++ b/tools/recovery_l10n/res/values-bn/strings.xml diff --git a/tools/recovery_l10n/res/values-bs-rBA/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml index 412cf0276..412cf0276 100644 --- a/tools/recovery_l10n/res/values-bs-rBA/strings.xml +++ b/tools/recovery_l10n/res/values-bs/strings.xml diff --git a/tools/recovery_l10n/res/values-et-rEE/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml index 072a9ef80..072a9ef80 100644 --- a/tools/recovery_l10n/res/values-et-rEE/strings.xml +++ b/tools/recovery_l10n/res/values-et/strings.xml diff --git a/tools/recovery_l10n/res/values-eu-rES/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml index 5540469d0..5540469d0 100644 --- a/tools/recovery_l10n/res/values-eu-rES/strings.xml +++ b/tools/recovery_l10n/res/values-eu/strings.xml diff --git a/tools/recovery_l10n/res/values-gl-rES/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml index 42b2016c2..42b2016c2 100644 --- a/tools/recovery_l10n/res/values-gl-rES/strings.xml +++ b/tools/recovery_l10n/res/values-gl/strings.xml diff --git a/tools/recovery_l10n/res/values-gu-rIN/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml index 2355a0f4f..2355a0f4f 100644 --- a/tools/recovery_l10n/res/values-gu-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-gu/strings.xml diff --git a/tools/recovery_l10n/res/values-hy-rAM/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml index 9d62bb763..9d62bb763 100644 --- a/tools/recovery_l10n/res/values-hy-rAM/strings.xml +++ b/tools/recovery_l10n/res/values-hy/strings.xml diff --git a/tools/recovery_l10n/res/values-is-rIS/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml index 5065b6522..5065b6522 100644 --- a/tools/recovery_l10n/res/values-is-rIS/strings.xml +++ b/tools/recovery_l10n/res/values-is/strings.xml diff --git a/tools/recovery_l10n/res/values-ka-rGE/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml index 6a46b3677..6a46b3677 100644 --- a/tools/recovery_l10n/res/values-ka-rGE/strings.xml +++ b/tools/recovery_l10n/res/values-ka/strings.xml diff --git a/tools/recovery_l10n/res/values-kk-rKZ/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml index a4bd86e66..a4bd86e66 100644 --- a/tools/recovery_l10n/res/values-kk-rKZ/strings.xml +++ b/tools/recovery_l10n/res/values-kk/strings.xml diff --git a/tools/recovery_l10n/res/values-km-rKH/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml index 313c0f457..313c0f457 100644 --- a/tools/recovery_l10n/res/values-km-rKH/strings.xml +++ b/tools/recovery_l10n/res/values-km/strings.xml diff --git a/tools/recovery_l10n/res/values-kn-rIN/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml index 5bf6260ee..5bf6260ee 100644 --- a/tools/recovery_l10n/res/values-kn-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-kn/strings.xml diff --git a/tools/recovery_l10n/res/values-ky-rKG/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml index 0a6bd783a..0a6bd783a 100644 --- a/tools/recovery_l10n/res/values-ky-rKG/strings.xml +++ b/tools/recovery_l10n/res/values-ky/strings.xml diff --git a/tools/recovery_l10n/res/values-lo-rLA/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml index d3dbb3970..d3dbb3970 100644 --- a/tools/recovery_l10n/res/values-lo-rLA/strings.xml +++ b/tools/recovery_l10n/res/values-lo/strings.xml diff --git a/tools/recovery_l10n/res/values-mk-rMK/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml index 351459730..351459730 100644 --- a/tools/recovery_l10n/res/values-mk-rMK/strings.xml +++ b/tools/recovery_l10n/res/values-mk/strings.xml diff --git a/tools/recovery_l10n/res/values-ml-rIN/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml index b506e2530..b506e2530 100644 --- a/tools/recovery_l10n/res/values-ml-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-ml/strings.xml diff --git a/tools/recovery_l10n/res/values-mn-rMN/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml index e3dd2e90e..e3dd2e90e 100644 --- a/tools/recovery_l10n/res/values-mn-rMN/strings.xml +++ b/tools/recovery_l10n/res/values-mn/strings.xml diff --git a/tools/recovery_l10n/res/values-mr-rIN/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml index 8cf86f773..8cf86f773 100644 --- a/tools/recovery_l10n/res/values-mr-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-mr/strings.xml diff --git a/tools/recovery_l10n/res/values-ms-rMY/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml index 0e24ac4e1..0e24ac4e1 100644 --- a/tools/recovery_l10n/res/values-ms-rMY/strings.xml +++ b/tools/recovery_l10n/res/values-ms/strings.xml diff --git a/tools/recovery_l10n/res/values-my-rMM/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml index f13752461..f13752461 100644 --- a/tools/recovery_l10n/res/values-my-rMM/strings.xml +++ b/tools/recovery_l10n/res/values-my/strings.xml diff --git a/tools/recovery_l10n/res/values-ne-rNP/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml index 1880e807b..1880e807b 100644 --- a/tools/recovery_l10n/res/values-ne-rNP/strings.xml +++ b/tools/recovery_l10n/res/values-ne/strings.xml diff --git a/tools/recovery_l10n/res/values-pa-rIN/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml index 8564c9c36..8564c9c36 100644 --- a/tools/recovery_l10n/res/values-pa-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-pa/strings.xml diff --git a/tools/recovery_l10n/res/values-si-rLK/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml index 456cdc567..456cdc567 100644 --- a/tools/recovery_l10n/res/values-si-rLK/strings.xml +++ b/tools/recovery_l10n/res/values-si/strings.xml diff --git a/tools/recovery_l10n/res/values-sq-rAL/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml index 1156931fb..1156931fb 100644 --- a/tools/recovery_l10n/res/values-sq-rAL/strings.xml +++ b/tools/recovery_l10n/res/values-sq/strings.xml diff --git a/tools/recovery_l10n/res/values-ta-rIN/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml index d49186d8d..d49186d8d 100644 --- a/tools/recovery_l10n/res/values-ta-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-ta/strings.xml diff --git a/tools/recovery_l10n/res/values-te-rIN/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml index cfb02c915..cfb02c915 100644 --- a/tools/recovery_l10n/res/values-te-rIN/strings.xml +++ b/tools/recovery_l10n/res/values-te/strings.xml diff --git a/tools/recovery_l10n/res/values-ur-rPK/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml index 12e32fbc1..12e32fbc1 100644 --- a/tools/recovery_l10n/res/values-ur-rPK/strings.xml +++ b/tools/recovery_l10n/res/values-ur/strings.xml diff --git a/tools/recovery_l10n/res/values-uz-rUZ/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml index 2c309d646..2c309d646 100644 --- a/tools/recovery_l10n/res/values-uz-rUZ/strings.xml +++ b/tools/recovery_l10n/res/values-uz/strings.xml diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml index 971e038d3..d56d0733c 100644 --- a/tools/recovery_l10n/res/values/strings.xml +++ b/tools/recovery_l10n/res/values/strings.xml @@ -9,6 +9,7 @@ <item>erasing</item> <item>no_command</item> <item>error</item> + <item>installing_security</item> </string-array> <!-- Displayed on the screen beneath the animated android while the diff --git a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java index 817a3ad7d..30d45f6ae 100644 --- a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java +++ b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java @@ -38,6 +38,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.Locale; @@ -139,6 +140,7 @@ public class Main extends Activity { case 1: mStringId = R.string.recovery_erasing; break; case 2: mStringId = R.string.recovery_no_command; break; case 3: mStringId = R.string.recovery_error; break; + case 4: mStringId = R.string.recovery_installing_security; break; } } @Override public void onNothingSelected(AdapterView parent) { } @@ -147,11 +149,28 @@ public class Main extends Activity { mText = (TextView) findViewById(R.id.text); String[] localeNames = getAssets().getLocales(); - Arrays.sort(localeNames); + Arrays.sort(localeNames, new Comparator<String>() { + // Override the string comparator so that en is sorted behind en_US. + // As a result, en_US will be matched first in recovery. + @Override + public int compare(String s1, String s2) { + if (s1.equals(s2)) { + return 0; + } else if (s1.startsWith(s2)) { + return -1; + } else if (s2.startsWith(s1)) { + return 1; + } + return s1.compareTo(s2); + } + }); + ArrayList<Locale> locales = new ArrayList<Locale>(); for (String localeName : localeNames) { Log.i(TAG, "locale = " + localeName); - locales.add(Locale.forLanguageTag(localeName)); + if (!localeName.isEmpty()) { + locales.add(Locale.forLanguageTag(localeName)); + } } final Runnable seq = buildSequence(locales.toArray(new Locale[0])); @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "ui.h" + #include <errno.h> #include <fcntl.h> #include <linux/input.h> @@ -28,32 +30,44 @@ #include <time.h> #include <unistd.h> -#include <cutils/properties.h> +#include <functional> +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/properties.h> +#include <android-base/strings.h> #include <cutils/android_reboot.h> +#include <minui/minui.h> #include "common.h" #include "roots.h" #include "device.h" -#include "minui/minui.h" -#include "screen_ui.h" -#include "ui.h" -#define UI_WAIT_KEY_TIMEOUT_SEC 120 +static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120; +static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness"; +static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness"; RecoveryUI::RecoveryUI() - : key_queue_len(0), - key_last_down(-1), - key_long_press(false), - key_down_count(0), - enable_reboot(true), - consecutive_power_keys(0), - last_key(-1), - has_power_key(false), - has_up_key(false), - has_down_key(false) { - pthread_mutex_init(&key_queue_mutex, nullptr); - pthread_cond_init(&key_queue_cond, nullptr); - memset(key_pressed, 0, sizeof(key_pressed)); + : locale_(""), + rtl_locale_(false), + brightness_normal_(50), + brightness_dimmed_(25), + key_queue_len(0), + key_last_down(-1), + key_long_press(false), + key_down_count(0), + enable_reboot(true), + consecutive_power_keys(0), + last_key(-1), + has_power_key(false), + has_up_key(false), + has_down_key(false), + screensaver_state_(ScreensaverState::DISABLED) { + pthread_mutex_init(&key_queue_mutex, nullptr); + pthread_cond_init(&key_queue_cond, nullptr); + memset(key_pressed, 0, sizeof(key_pressed)); } void RecoveryUI::OnKeyDetected(int key_code) { @@ -66,10 +80,6 @@ void RecoveryUI::OnKeyDetected(int key_code) { } } -int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) { - return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents); -} - // Reads input events, handles special hot keys, and adds to the key queue. static void* InputThreadLoop(void*) { while (true) { @@ -80,12 +90,54 @@ static void* InputThreadLoop(void*) { return nullptr; } -void RecoveryUI::Init() { - ev_init(InputCallback, this); +bool RecoveryUI::InitScreensaver() { + // Disabled. + if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) { + return false; + } + + // Set the initial brightness level based on the max brightness. Note that reading the initial + // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so + // we don't have a good way to query the default value. + std::string content; + if (!android::base::ReadFileToString(MAX_BRIGHTNESS_FILE, &content)) { + PLOG(WARNING) << "Failed to read max brightness"; + return false; + } + + unsigned int max_value; + if (!android::base::ParseUint(android::base::Trim(content), &max_value)) { + LOG(WARNING) << "Failed to parse max brightness: " << content; + return false; + } + + brightness_normal_value_ = max_value * brightness_normal_ / 100.0; + brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0; + if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_), + BRIGHTNESS_FILE)) { + PLOG(WARNING) << "Failed to set brightness"; + return false; + } + + LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)"; + screensaver_state_ = ScreensaverState::NORMAL; + return true; +} + +bool RecoveryUI::Init(const std::string& locale) { + // Set up the locale info. + SetLocale(locale); + + ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2)); - ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); + ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); - pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); + if (!InitScreensaver()) { + LOG(INFO) << "Screensaver disabled"; + } + + pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr); + return true; } int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { @@ -175,7 +227,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { case RecoveryUI::REBOOT: if (reboot_enabled) { - property_set(ANDROID_RB_PROPERTY, "reboot,"); + reboot("reboot,"); while (true) { pause(); } } break; @@ -188,7 +240,7 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { } void* RecoveryUI::time_key_helper(void* cookie) { - key_timer_t* info = (key_timer_t*) cookie; + key_timer_t* info = static_cast<key_timer_t*>(cookie); info->ui->time_key(info->key_code, info->count); delete info; return nullptr; @@ -216,31 +268,65 @@ void RecoveryUI::EnqueueKey(int key_code) { } int RecoveryUI::WaitKey() { - pthread_mutex_lock(&key_queue_mutex); + pthread_mutex_lock(&key_queue_mutex); + + // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is + // plugged in. + do { + struct timeval now; + struct timespec timeout; + gettimeofday(&now, nullptr); + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = now.tv_usec * 1000; + timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; + + int rc = 0; + while (key_queue_len == 0 && rc != ETIMEDOUT) { + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + } - // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is - // plugged in. - do { - struct timeval now; - struct timespec timeout; - gettimeofday(&now, nullptr); - timeout.tv_sec = now.tv_sec; - timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; - - int rc = 0; - while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + if (screensaver_state_ != ScreensaverState::DISABLED) { + if (rc == ETIMEDOUT) { + // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF. + if (screensaver_state_ == ScreensaverState::NORMAL) { + if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_), + BRIGHTNESS_FILE)) { + LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_ + << "%)"; + screensaver_state_ = ScreensaverState::DIMMED; + } + } else if (screensaver_state_ == ScreensaverState::DIMMED) { + if (android::base::WriteStringToFile("0", BRIGHTNESS_FILE)) { + LOG(INFO) << "Brightness: 0 (off)"; + screensaver_state_ = ScreensaverState::OFF; + } + } + } else if (screensaver_state_ != ScreensaverState::NORMAL) { + // Drop the first key if it's changing from OFF to NORMAL. + if (screensaver_state_ == ScreensaverState::OFF) { + if (key_queue_len > 0) { + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } } - } while (IsUsbConnected() && key_queue_len == 0); - int key = -1; - if (key_queue_len > 0) { - key = key_queue[0]; - memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + // Reset the brightness to normal. + if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_), + BRIGHTNESS_FILE)) { + screensaver_state_ = ScreensaverState::NORMAL; + LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ + << "%)"; + } + } } - pthread_mutex_unlock(&key_queue_mutex); - return key; + } while (IsUsbConnected() && key_queue_len == 0); + + int key = -1; + if (key_queue_len > 0) { + key = key_queue[0]; + memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len); + } + pthread_mutex_unlock(&key_queue_mutex); + return key; } bool RecoveryUI::IsUsbConnected() { @@ -326,7 +412,7 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { } last_key = key; - return IsTextVisible() ? ENQUEUE : IGNORE; + return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; } void RecoveryUI::KeyLongPress(int) { @@ -337,3 +423,23 @@ void RecoveryUI::SetEnableReboot(bool enabled) { enable_reboot = enabled; pthread_mutex_unlock(&key_queue_mutex); } + +void RecoveryUI::SetLocale(const std::string& new_locale) { + this->locale_ = new_locale; + this->rtl_locale_ = false; + + if (!new_locale.empty()) { + size_t underscore = new_locale.find('_'); + // lang has the language prefix prior to '_', or full string if '_' doesn't exist. + std::string lang = new_locale.substr(0, underscore); + + // A bit cheesy: keep an explicit list of supported RTL languages. + if (lang == "ar" || // Arabic + lang == "fa" || // Persian (Farsi) + lang == "he" || // Hebrew (new language code) + lang == "iw" || // Hebrew (old language code) + lang == "ur") { // Urdu + rtl_locale_ = true; + } + } +} @@ -21,6 +21,8 @@ #include <pthread.h> #include <time.h> +#include <string> + // Abstract class for controlling the user interface during recovery. class RecoveryUI { public: @@ -28,14 +30,13 @@ class RecoveryUI { virtual ~RecoveryUI() { } - // Initialize the object; called before anything else. - virtual void Init(); + // Initialize the object; called before anything else. UI texts will be + // initialized according to the given locale. Returns true on success. + virtual bool Init(const std::string& locale); + // Show a stage indicator. Call immediately after Init(). virtual void SetStage(int current, int max) = 0; - // After calling Init(), you can tell the UI what locale it is operating in. - virtual void SetLocale(const char* locale) = 0; - // Set the overall recovery state ("background image"). enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; virtual void SetBackground(Icon icon) = 0; @@ -122,10 +123,21 @@ class RecoveryUI { // statements will be displayed. virtual void EndMenu() = 0; -protected: + protected: void EnqueueKey(int key_code); -private: + // The locale that's used to show the rendered texts. + std::string locale_; + bool rtl_locale_; + + // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% + // of the max_brightness). Because the absolute values may vary across devices. These two + // values can be configured via subclassing. Setting brightness_normal_ to 0 to disable + // screensaver. + unsigned int brightness_normal_; + unsigned int brightness_dimmed_; + + private: // Key event input queue pthread_mutex_t key_queue_mutex; pthread_cond_t key_queue_cond; @@ -153,8 +165,6 @@ private: pthread_t input_thread_; void OnKeyDetected(int key_code); - - static int InputCallback(int fd, uint32_t epevents, void* data); int OnInputEvent(int fd, uint32_t epevents); void ProcessKey(int key_code, int updown); @@ -162,6 +172,16 @@ private: static void* time_key_helper(void* cookie); void time_key(int key_code, int count); + + void SetLocale(const std::string&); + + enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF }; + ScreensaverState screensaver_state_; + // The following two contain the absolute values computed from brightness_normal_ and + // brightness_dimmed_ respectively. + unsigned int brightness_normal_value_; + unsigned int brightness_dimmed_value_; + bool InitScreensaver(); }; #endif // RECOVERY_UI_H diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk index bb276edd5..59084b0bb 100644 --- a/uncrypt/Android.mk +++ b/uncrypt/Android.mk @@ -17,16 +17,16 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_CLANG := true - LOCAL_SRC_FILES := uncrypt.cpp - LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. - LOCAL_MODULE := uncrypt - -LOCAL_STATIC_LIBRARIES := libbootloader_message libbase \ - liblog libfs_mgr libcutils \ - +LOCAL_STATIC_LIBRARIES := \ + libbootloader_message \ + libbase \ + liblog \ + libfs_mgr \ + libcutils +LOCAL_CFLAGS := -Werror LOCAL_INIT_RC := uncrypt.rc include $(BUILD_EXECUTABLE) diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp index 280568d23..ad3bdce7a 100644 --- a/uncrypt/uncrypt.cpp +++ b/uncrypt/uncrypt.cpp @@ -107,21 +107,19 @@ #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> +#include <android-base/unique_fd.h> #include <bootloader_message/bootloader_message.h> #include <cutils/android_reboot.h> -#include <cutils/properties.h> #include <cutils/sockets.h> #include <fs_mgr.h> -#define LOG_TAG "uncrypt" -#include <log/log.h> - #include "error_code.h" -#include "unique_fd.h" -#define WINDOW_SIZE 5 +static constexpr int WINDOW_SIZE = 5; +static constexpr int FIBMAP_RETRY_LIMIT = 3; // uncrypt provides three services: SETUP_BCB, CLEAR_BCB and UNCRYPT. // @@ -142,11 +140,11 @@ static struct fstab* fstab = nullptr; static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) { if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) { - ALOGE("error seeking to offset %" PRId64 ": %s", offset, strerror(errno)); + PLOG(ERROR) << "error seeking to offset " << offset; return -1; } if (!android::base::WriteFully(wfd, buffer, size)) { - ALOGE("error writing offset %" PRId64 ": %s", offset, strerror(errno)); + PLOG(ERROR) << "error writing offset " << offset; return -1; } return 0; @@ -165,18 +163,9 @@ static void add_block_to_ranges(std::vector<int>& ranges, int new_block) { } static struct fstab* read_fstab() { - fstab = NULL; - - // The fstab path is always "/fstab.${ro.hardware}". - char fstab_path[PATH_MAX+1] = "/fstab."; - if (!property_get("ro.hardware", fstab_path+strlen(fstab_path), "")) { - ALOGE("failed to get ro.hardware"); - return NULL; - } - - fstab = fs_mgr_read_fstab(fstab_path); + fstab = fs_mgr_read_fstab_default(); if (!fstab) { - ALOGE("failed to read %s", fstab_path); + LOG(ERROR) << "failed to read default fstab"; return NULL; } @@ -199,9 +188,7 @@ static const char* find_block_device(const char* path, bool* encryptable, bool* *encryptable = false; if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) { *encryptable = true; - char buffer[PROPERTY_VALUE_MAX+1]; - if (property_get("ro.crypto.state", buffer, "") && - strcmp(buffer, "encrypted") == 0) { + if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") { *encrypted = true; } } @@ -213,6 +200,11 @@ static const char* find_block_device(const char* path, bool* encryptable, bool* } static bool write_status_to_socket(int status, int socket) { + // If socket equals -1, uncrypt is in debug mode without socket communication. + // Skip writing and return success. + if (socket == -1) { + return true; + } int status_out = htonl(status); return android::base::WriteFully(socket, &status_out, sizeof(int)); } @@ -222,7 +214,7 @@ static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::stri CHECK(package_name != nullptr); std::string uncrypt_path; if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) { - ALOGE("failed to open \"%s\": %s", uncrypt_path_file.c_str(), strerror(errno)); + PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\""; return false; } @@ -231,43 +223,65 @@ static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::stri return true; } +static int retry_fibmap(const int fd, const char* name, int* block, const int head_block) { + CHECK(block != nullptr); + for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) { + if (fsync(fd) == -1) { + PLOG(ERROR) << "failed to fsync \"" << name << "\""; + return kUncryptFileSyncError; + } + if (ioctl(fd, FIBMAP, block) != 0) { + PLOG(ERROR) << "failed to find block " << head_block; + return kUncryptIoctlError; + } + if (*block != 0) { + return kUncryptNoError; + } + sleep(1); + } + LOG(ERROR) << "fibmap of " << head_block << "always returns 0"; + return kUncryptIoctlError; +} + static int produce_block_map(const char* path, const char* map_file, const char* blk_dev, bool encrypted, int socket) { std::string err; if (!android::base::RemoveFileIfExists(map_file, &err)) { - ALOGE("failed to remove the existing map file %s: %s", map_file, err.c_str()); + LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; return kUncryptFileRemoveError; } std::string tmp_map_file = std::string(map_file) + ".tmp"; - unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); - if (!mapfd) { - ALOGE("failed to open %s: %s\n", tmp_map_file.c_str(), strerror(errno)); + android::base::unique_fd mapfd(open(tmp_map_file.c_str(), + O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); + if (mapfd == -1) { + PLOG(ERROR) << "failed to open " << tmp_map_file; return kUncryptFileOpenError; } // Make sure we can write to the socket. if (!write_status_to_socket(0, socket)) { - ALOGE("failed to write to socket %d\n", socket); + LOG(ERROR) << "failed to write to socket " << socket; return kUncryptSocketWriteError; } struct stat sb; if (stat(path, &sb) != 0) { - ALOGE("failed to stat %s", path); + LOG(ERROR) << "failed to stat " << path; return kUncryptFileStatError; } - ALOGI(" block size: %ld bytes", static_cast<long>(sb.st_blksize)); + LOG(INFO) << " block size: " << sb.st_blksize << " bytes"; int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; - ALOGI(" file size: %" PRId64 " bytes, %d blocks", sb.st_size, blocks); + LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks"; std::vector<int> ranges; - std::string s = android::base::StringPrintf("%s\n%" PRId64 " %ld\n", - blk_dev, sb.st_size, static_cast<long>(sb.st_blksize)); - if (!android::base::WriteStringToFd(s, mapfd.get())) { - ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno)); + std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", + blk_dev, static_cast<int64_t>(sb.st_size), + static_cast<int64_t>(sb.st_blksize)); + if (!android::base::WriteStringToFd(s, mapfd)) { + PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } @@ -278,17 +292,17 @@ static int produce_block_map(const char* path, const char* map_file, const char* int head_block = 0; int head = 0, tail = 0; - unique_fd fd(open(path, O_RDONLY)); - if (!fd) { - ALOGE("failed to open %s for reading: %s", path, strerror(errno)); + android::base::unique_fd fd(open(path, O_RDONLY)); + if (fd == -1) { + PLOG(ERROR) << "failed to open " << path << " for reading"; return kUncryptFileOpenError; } - unique_fd wfd(-1); + android::base::unique_fd wfd; if (encrypted) { - wfd = open(blk_dev, O_WRONLY); - if (!wfd) { - ALOGE("failed to open fd for writing: %s", strerror(errno)); + wfd.reset(open(blk_dev, O_WRONLY)); + if (wfd == -1) { + PLOG(ERROR) << "failed to open " << blk_dev << " for writing"; return kUncryptBlockOpenError; } } @@ -306,14 +320,23 @@ static int produce_block_map(const char* path, const char* map_file, const char* if ((tail+1) % WINDOW_SIZE == head) { // write out head buffer int block = head_block; - if (ioctl(fd.get(), FIBMAP, &block) != 0) { - ALOGE("failed to find block %d", head_block); + if (ioctl(fd, FIBMAP, &block) != 0) { + PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } + + if (block == 0) { + LOG(ERROR) << "failed to find block " << head_block << ", retrying"; + int error = retry_fibmap(fd, path, &block, head_block); + if (error != kUncryptNoError) { + return error; + } + } + add_block_to_ranges(ranges, block); if (encrypted) { - if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd.get(), - static_cast<off64_t>(sb.st_blksize) * block) != 0) { + if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, + static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } @@ -325,8 +348,8 @@ static int produce_block_map(const char* path, const char* map_file, const char* if (encrypted) { size_t to_read = static_cast<size_t>( std::min(static_cast<off64_t>(sb.st_blksize), sb.st_size - pos)); - if (!android::base::ReadFully(fd.get(), buffers[tail].data(), to_read)) { - ALOGE("failed to read: %s", strerror(errno)); + if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) { + PLOG(ERROR) << "failed to read " << path; return kUncryptReadError; } pos += to_read; @@ -342,14 +365,23 @@ static int produce_block_map(const char* path, const char* map_file, const char* while (head != tail) { // write out head buffer int block = head_block; - if (ioctl(fd.get(), FIBMAP, &block) != 0) { - ALOGE("failed to find block %d", head_block); + if (ioctl(fd, FIBMAP, &block) != 0) { + PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } + + if (block == 0) { + LOG(ERROR) << "failed to find block " << head_block << ", retrying"; + int error = retry_fibmap(fd, path, &block, head_block); + if (error != kUncryptNoError) { + return error; + } + } + add_block_to_ranges(ranges, block); if (encrypted) { - if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd.get(), - static_cast<off64_t>(sb.st_blksize) * block) != 0) { + if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, + static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } @@ -358,72 +390,69 @@ static int produce_block_map(const char* path, const char* map_file, const char* } if (!android::base::WriteStringToFd( - android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd.get())) { - ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno)); + android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd)) { + PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } for (size_t i = 0; i < ranges.size(); i += 2) { if (!android::base::WriteStringToFd( - android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd.get())) { - ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno)); + android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd)) { + PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } } - if (fsync(mapfd.get()) == -1) { - ALOGE("failed to fsync \"%s\": %s", tmp_map_file.c_str(), strerror(errno)); + if (fsync(mapfd) == -1) { + PLOG(ERROR) << "failed to fsync \"" << tmp_map_file << "\""; return kUncryptFileSyncError; } - if (close(mapfd.get()) == -1) { - ALOGE("failed to close %s: %s", tmp_map_file.c_str(), strerror(errno)); + if (close(mapfd.release()) == -1) { + PLOG(ERROR) << "failed to close " << tmp_map_file; return kUncryptFileCloseError; } - mapfd = -1; if (encrypted) { - if (fsync(wfd.get()) == -1) { - ALOGE("failed to fsync \"%s\": %s", blk_dev, strerror(errno)); + if (fsync(wfd) == -1) { + PLOG(ERROR) << "failed to fsync \"" << blk_dev << "\""; return kUncryptFileSyncError; } - if (close(wfd.get()) == -1) { - ALOGE("failed to close %s: %s", blk_dev, strerror(errno)); + if (close(wfd.release()) == -1) { + PLOG(ERROR) << "failed to close " << blk_dev; return kUncryptFileCloseError; } - wfd = -1; } if (rename(tmp_map_file.c_str(), map_file) == -1) { - ALOGE("failed to rename %s to %s: %s", tmp_map_file.c_str(), map_file, strerror(errno)); + PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; return kUncryptFileRenameError; } // Sync dir to make rename() result written to disk. std::string file_name = map_file; std::string dir_name = dirname(&file_name[0]); - unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); - if (!dfd) { - ALOGE("failed to open dir %s: %s", dir_name.c_str(), strerror(errno)); + android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); + if (dfd == -1) { + PLOG(ERROR) << "failed to open dir " << dir_name; return kUncryptFileOpenError; } - if (fsync(dfd.get()) == -1) { - ALOGE("failed to fsync %s: %s", dir_name.c_str(), strerror(errno)); + if (fsync(dfd) == -1) { + PLOG(ERROR) << "failed to fsync " << dir_name; return kUncryptFileSyncError; } - if (close(dfd.get()) == -1) { - ALOGE("failed to close %s: %s", dir_name.c_str(), strerror(errno)); + if (close(dfd.release()) == -1) { + PLOG(ERROR) << "failed to close " << dir_name; return kUncryptFileCloseError; } - dfd = -1; return 0; } static int uncrypt(const char* input_path, const char* map_file, const int socket) { - ALOGI("update package is \"%s\"", input_path); + LOG(INFO) << "update package is \"" << input_path << "\""; // Turn the name of the file we're supposed to convert into an // absolute path, so we can find what filesystem it's on. char path[PATH_MAX+1]; if (realpath(input_path, path) == NULL) { - ALOGE("failed to convert \"%s\" to absolute path: %s", input_path, strerror(errno)); + PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path"; return 1; } @@ -431,15 +460,15 @@ static int uncrypt(const char* input_path, const char* map_file, const int socke bool encrypted; const char* blk_dev = find_block_device(path, &encryptable, &encrypted); if (blk_dev == NULL) { - ALOGE("failed to find block device for %s", path); + LOG(ERROR) << "failed to find block device for " << path; return 1; } // If the filesystem it's on isn't encrypted, we only produce the // block map, we don't rewrite the file contents (it would be // pointless to do so). - ALOGI("encryptable: %s", encryptable ? "yes" : "no"); - ALOGI(" encrypted: %s", encrypted ? "yes" : "no"); + LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no"); + LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no"); // Recovery supports installing packages from 3 paths: /cache, // /data, and /sdcard. (On a particular device, other locations @@ -449,7 +478,7 @@ static int uncrypt(const char* input_path, const char* map_file, const int socke // can read the package without mounting the partition. On /cache // and /sdcard we leave the file alone. if (strncmp(path, "/data/", 6) == 0) { - ALOGI("writing block map %s", map_file); + LOG(INFO) << "writing block map " << map_file; return produce_block_map(path, map_file, blk_dev, encrypted, socket); } @@ -459,7 +488,7 @@ static int uncrypt(const char* input_path, const char* map_file, const int socke static void log_uncrypt_error_code(UncryptErrorCode error_code) { if (!android::base::WriteStringToFile(android::base::StringPrintf( "uncrypt_error: %d\n", error_code), UNCRYPT_STATUS)) { - ALOGW("failed to write to %s: %s", UNCRYPT_STATUS.c_str(), strerror(errno)); + PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } } @@ -489,7 +518,7 @@ static bool uncrypt_wrapper(const char* input_path, const char* map_file, const // Log the time cost and error code if uncrypt fails. uncrypt_message += android::base::StringPrintf("uncrypt_error: %d\n", status); if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { - ALOGW("failed to write to %s: %s", UNCRYPT_STATUS.c_str(), strerror(errno)); + PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(-1, socket); @@ -497,7 +526,7 @@ static bool uncrypt_wrapper(const char* input_path, const char* map_file, const } if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { - ALOGW("failed to write to %s: %s", UNCRYPT_STATUS.c_str(), strerror(errno)); + PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(100, socket); @@ -508,7 +537,7 @@ static bool uncrypt_wrapper(const char* input_path, const char* map_file, const static bool clear_bcb(const int socket) { std::string err; if (!clear_bootloader_message(&err)) { - ALOGE("failed to clear bootloader message: %s", err.c_str()); + LOG(ERROR) << "failed to clear bootloader message: " << err; write_status_to_socket(-1, socket); return false; } @@ -520,7 +549,7 @@ static bool setup_bcb(const int socket) { // c5. receive message length int length; if (!android::base::ReadFully(socket, &length, 4)) { - ALOGE("failed to read the length: %s", strerror(errno)); + PLOG(ERROR) << "failed to read the length"; return false; } length = ntohl(length); @@ -529,17 +558,17 @@ static bool setup_bcb(const int socket) { std::string content; content.resize(length); if (!android::base::ReadFully(socket, &content[0], length)) { - ALOGE("failed to read the length: %s", strerror(errno)); + PLOG(ERROR) << "failed to read the message"; return false; } - ALOGI(" received command: [%s] (%zu)", content.c_str(), content.size()); + LOG(INFO) << " received command: [" << content << "] (" << content.size() << ")"; std::vector<std::string> options = android::base::Split(content, "\n"); std::string wipe_package; for (auto& option : options) { if (android::base::StartsWith(option, "--wipe_package=")) { std::string path = option.substr(strlen("--wipe_package=")); if (!android::base::ReadFileToString(path, &wipe_package)) { - ALOGE("failed to read %s: %s", path.c_str(), strerror(errno)); + PLOG(ERROR) << "failed to read " << path; return false; } option = android::base::StringPrintf("--wipe_package_size=%zu", wipe_package.size()); @@ -549,12 +578,12 @@ static bool setup_bcb(const int socket) { // c8. setup the bcb command std::string err; if (!write_bootloader_message(options, &err)) { - ALOGE("failed to set bootloader message: %s", err.c_str()); + LOG(ERROR) << "failed to set bootloader message: " << err; write_status_to_socket(-1, socket); return false; } if (!wipe_package.empty() && !write_wipe_package(wipe_package, &err)) { - ALOGE("failed to set wipe package: %s", err.c_str()); + PLOG(ERROR) << "failed to set wipe package: " << err; write_status_to_socket(-1, socket); return false; } @@ -571,7 +600,7 @@ static void usage(const char* exename) { } int main(int argc, char** argv) { - enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action; + enum { UNCRYPT, SETUP_BCB, CLEAR_BCB, UNCRYPT_DEBUG } action; const char* input_path = nullptr; const char* map_file = CACHE_BLOCK_MAP.c_str(); @@ -584,7 +613,7 @@ int main(int argc, char** argv) { } else if (argc == 3) { input_path = argv[1]; map_file = argv[2]; - action = UNCRYPT; + action = UNCRYPT_DEBUG; } else { usage(argv[0]); return 2; @@ -595,25 +624,36 @@ int main(int argc, char** argv) { return 1; } + if (action == UNCRYPT_DEBUG) { + LOG(INFO) << "uncrypt called in debug mode, skip socket communication"; + bool success = uncrypt_wrapper(input_path, map_file, -1); + if (success) { + LOG(INFO) << "uncrypt succeeded"; + } else{ + LOG(INFO) << "uncrypt failed"; + } + return success ? 0 : 1; + } + // c3. The socket is created by init when starting the service. uncrypt // will use the socket to communicate with its caller. - unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str())); - if (!service_socket) { - ALOGE("failed to open socket \"%s\": %s", UNCRYPT_SOCKET.c_str(), strerror(errno)); + android::base::unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str())); + if (service_socket == -1) { + PLOG(ERROR) << "failed to open socket \"" << UNCRYPT_SOCKET << "\""; log_uncrypt_error_code(kUncryptSocketOpenError); return 1; } - fcntl(service_socket.get(), F_SETFD, FD_CLOEXEC); + fcntl(service_socket, F_SETFD, FD_CLOEXEC); - if (listen(service_socket.get(), 1) == -1) { - ALOGE("failed to listen on socket %d: %s", service_socket.get(), strerror(errno)); + if (listen(service_socket, 1) == -1) { + PLOG(ERROR) << "failed to listen on socket " << service_socket.get(); log_uncrypt_error_code(kUncryptSocketListenError); return 1; } - unique_fd socket_fd(accept4(service_socket.get(), nullptr, nullptr, SOCK_CLOEXEC)); - if (!socket_fd) { - ALOGE("failed to accept on socket %d: %s", service_socket.get(), strerror(errno)); + android::base::unique_fd socket_fd(accept4(service_socket, nullptr, nullptr, SOCK_CLOEXEC)); + if (socket_fd == -1) { + PLOG(ERROR) << "failed to accept on socket " << service_socket.get(); log_uncrypt_error_code(kUncryptSocketAcceptError); return 1; } @@ -621,16 +661,16 @@ int main(int argc, char** argv) { bool success = false; switch (action) { case UNCRYPT: - success = uncrypt_wrapper(input_path, map_file, socket_fd.get()); + success = uncrypt_wrapper(input_path, map_file, socket_fd); break; case SETUP_BCB: - success = setup_bcb(socket_fd.get()); + success = setup_bcb(socket_fd); break; case CLEAR_BCB: - success = clear_bcb(socket_fd.get()); + success = clear_bcb(socket_fd); break; default: // Should never happen. - ALOGE("Invalid uncrypt action code: %d", action); + LOG(ERROR) << "Invalid uncrypt action code: " << action; return 1; } @@ -638,10 +678,10 @@ int main(int argc, char** argv) { // ensure the client to receive the last status code before the socket gets // destroyed. int code; - if (android::base::ReadFully(socket_fd.get(), &code, 4)) { - ALOGI(" received %d, exiting now", code); + if (android::base::ReadFully(socket_fd, &code, 4)) { + LOG(INFO) << " received " << code << ", exiting now"; } else { - ALOGE("failed to read the code: %s", strerror(errno)); + PLOG(ERROR) << "failed to read the code"; } return success ? 0 : 1; } diff --git a/unique_fd.h b/unique_fd.h deleted file mode 100644 index cc85383f8..000000000 --- a/unique_fd.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 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 UNIQUE_FD_H -#define UNIQUE_FD_H - -#include <stdio.h> - -#include <memory> - -class unique_fd { - public: - unique_fd(int fd) : fd_(fd) { } - - unique_fd(unique_fd&& uf) { - fd_ = uf.fd_; - uf.fd_ = -1; - } - - ~unique_fd() { - if (fd_ != -1) { - close(fd_); - } - } - - int get() { - return fd_; - } - - // Movable. - unique_fd& operator=(unique_fd&& uf) { - fd_ = uf.fd_; - uf.fd_ = -1; - return *this; - } - - explicit operator bool() const { - return fd_ != -1; - } - - private: - int fd_; - - // Non-copyable. - unique_fd(const unique_fd&) = delete; - unique_fd& operator=(const unique_fd&) = delete; -}; - -#endif // UNIQUE_FD_H diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk index 2bfd01622..1acd5eca0 100644 --- a/update_verifier/Android.mk +++ b/update_verifier/Android.mk @@ -20,8 +20,22 @@ LOCAL_CLANG := true LOCAL_SRC_FILES := update_verifier.cpp LOCAL_MODULE := update_verifier -LOCAL_SHARED_LIBRARIES := libbase libcutils libhardware liblog +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libcutils \ + libhardware \ + liblog \ + libutils \ + libhidlbase \ + android.hardware.boot@1.0 +LOCAL_CFLAGS := -Werror LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +LOCAL_INIT_RC := update_verifier.rc + +ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true) + LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1 +endif + include $(BUILD_EXECUTABLE) diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp index 5cff8be93..350020f13 100644 --- a/update_verifier/update_verifier.cpp +++ b/update_verifier/update_verifier.cpp @@ -19,88 +19,146 @@ * update. It gets invoked by init, and will only perform the verification if * it's the first boot post an A/B OTA update. * - * It relies on dm-verity to capture any corruption on the partitions being - * verified. dm-verity must be in enforcing mode, so that it will reboot the - * device on dm-verity failures. When that happens, the bootloader should + * Update_verifier relies on dm-verity to capture any corruption on the partitions + * being verified. And its behavior varies depending on the dm-verity mode. + * Upon detection of failures: + * enforcing mode: dm-verity reboots the device + * eio mode: dm-verity fails the read and update_verifier reboots the device + * other mode: not supported and update_verifier reboots the device + * + * After a predefined number of failing boot attempts, the bootloader should * mark the slot as unbootable and stops trying. Other dm-verity modes ( * for example, veritymode=EIO) are not accepted and simply lead to a * verification failure. * * The current slot will be marked as having booted successfully if the * verifier reaches the end after the verification. - * */ +#include <dirent.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> +#include <unistd.h> #include <string> #include <vector> #include <android-base/file.h> +#include <android-base/logging.h> #include <android-base/parseint.h> +#include <android-base/properties.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> -#include <cutils/properties.h> -#include <hardware/boot_control.h> -#define LOG_TAG "update_verifier" -#include <log/log.h> +#include <android/hardware/boot/1.0/IBootControl.h> +#include <cutils/android_reboot.h> + +using android::sp; +using android::hardware::boot::V1_0::IBootControl; +using android::hardware::boot::V1_0::BoolResult; +using android::hardware::boot::V1_0::CommandResult; constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt"; +constexpr auto DM_PATH_PREFIX = "/sys/block/"; +constexpr auto DM_PATH_SUFFIX = "/dm/name"; +constexpr auto DEV_PATH = "/dev/block/"; constexpr int BLOCKSIZE = 4096; -static bool read_blocks(const std::string& blk_device_prefix, const std::string& range_str) { - char slot_suffix[PROPERTY_VALUE_MAX]; - property_get("ro.boot.slot_suffix", slot_suffix, ""); - std::string blk_device = blk_device_prefix + std::string(slot_suffix); - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY))); - if (fd.get() == -1) { - SLOGE("Error reading partition %s: %s\n", blk_device.c_str(), strerror(errno)); - return false; - } +// Find directories in format of "/sys/block/dm-X". +static int dm_name_filter(const dirent* de) { + if (android::base::StartsWith(de->d_name, "dm-")) { + return 1; + } + return 0; +} - // For block range string, first integer 'count' equals 2 * total number of valid ranges, - // followed by 'count' number comma separated integers. Every two integers reprensent a - // block range with the first number included in range but second number not included. - // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150). - std::vector<std::string> ranges = android::base::Split(range_str, ","); - size_t range_count; - bool status = android::base::ParseUint(ranges[0].c_str(), &range_count); - if (!status || (range_count == 0) || (range_count % 2 != 0) || - (range_count != ranges.size()-1)) { - SLOGE("Error in parsing range string.\n"); - return false; +static bool read_blocks(const std::string& partition, const std::string& range_str) { + if (partition != "system" && partition != "vendor") { + LOG(ERROR) << "partition name must be system or vendor: " << partition; + return false; + } + // Iterate the content of "/sys/block/dm-X/dm/name". If it matches "system" + // (or "vendor"), then dm-X is a dm-wrapped system/vendor partition. + // Afterwards, update_verifier will read every block on the care_map_file of + // "/dev/block/dm-X" to ensure the partition's integrity. + dirent** namelist; + int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort); + if (n == -1) { + PLOG(ERROR) << "Failed to scan dir " << DM_PATH_PREFIX; + return false; + } + if (n == 0) { + LOG(ERROR) << "dm block device not found for " << partition; + return false; + } + + std::string dm_block_device; + while (n--) { + std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX; + std::string content; + if (!android::base::ReadFileToString(path, &content)) { + PLOG(WARNING) << "Failed to read " << path; + } else if (android::base::Trim(content) == partition) { + dm_block_device = DEV_PATH + std::string(namelist[n]->d_name); + while (n--) { + free(namelist[n]); + } + break; } + free(namelist[n]); + } + free(namelist); - size_t blk_count = 0; - for (size_t i = 1; i < ranges.size(); i += 2) { - unsigned int range_start, range_end; - bool parse_status = android::base::ParseUint(ranges[i].c_str(), &range_start); - parse_status = parse_status && android::base::ParseUint(ranges[i+1].c_str(), &range_end); - if (!parse_status || range_start >= range_end) { - SLOGE("Invalid range pair %s, %s.\n", ranges[i].c_str(), ranges[i+1].c_str()); - return false; - } + if (dm_block_device.empty()) { + LOG(ERROR) << "Failed to find dm block device for " << partition; + return false; + } + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY))); + if (fd.get() == -1) { + PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition; + return false; + } - if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) { - SLOGE("lseek to %u failed: %s.\n", range_start, strerror(errno)); - return false; - } + // For block range string, first integer 'count' equals 2 * total number of valid ranges, + // followed by 'count' number comma separated integers. Every two integers reprensent a + // block range with the first number included in range but second number not included. + // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150). + std::vector<std::string> ranges = android::base::Split(range_str, ","); + size_t range_count; + bool status = android::base::ParseUint(ranges[0], &range_count); + if (!status || (range_count == 0) || (range_count % 2 != 0) || + (range_count != ranges.size() - 1)) { + LOG(ERROR) << "Error in parsing range string."; + return false; + } - size_t size = (range_end - range_start) * BLOCKSIZE; - std::vector<uint8_t> buf(size); - if (!android::base::ReadFully(fd.get(), buf.data(), size)) { - SLOGE("Failed to read blocks %u to %u: %s.\n", range_start, range_end, - strerror(errno)); - return false; - } - blk_count += (range_end - range_start); + size_t blk_count = 0; + for (size_t i = 1; i < ranges.size(); i += 2) { + unsigned int range_start, range_end; + bool parse_status = android::base::ParseUint(ranges[i], &range_start); + parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end); + if (!parse_status || range_start >= range_end) { + LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1]; + return false; } - SLOGI("Finished reading %zu blocks on %s.\n", blk_count, blk_device.c_str()); - return true; + if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) { + PLOG(ERROR) << "lseek to " << range_start << " failed"; + return false; + } + + size_t size = (range_end - range_start) * BLOCKSIZE; + std::vector<uint8_t> buf(size); + if (!android::base::ReadFully(fd.get(), buf.data(), size)) { + PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end; + return false; + } + blk_count += (range_end - range_start); + } + + LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device; + return true; } static bool verify_image(const std::string& care_map_name) { @@ -109,24 +167,24 @@ static bool verify_image(const std::string& care_map_name) { // in /data/ota_package. To allow the device to continue booting in this situation, // we should print a warning and skip the block verification. if (care_map_fd.get() == -1) { - SLOGI("Warning: care map %s not found.\n", care_map_name.c_str()); + PLOG(WARNING) << "Failed to open " << care_map_name; return true; } // Care map file has four lines (two lines if vendor partition is not present): - // First line has the block device name, e.g./dev/block/.../by-name/system. + // First line has the block partition name (system/vendor). // Second line holds all ranges of blocks to verify. // The next two lines have the same format but for vendor partition. std::string file_content; if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) { - SLOGE("Error reading care map contents to string.\n"); + LOG(ERROR) << "Error reading care map contents to string."; return false; } std::vector<std::string> lines; lines = android::base::Split(android::base::Trim(file_content), "\n"); if (lines.size() != 2 && lines.size() != 4) { - SLOGE("Invalid lines in care_map: found %zu lines, expecting 2 or 4 lines.\n", - lines.size()); + LOG(ERROR) << "Invalid lines in care_map: found " << lines.size() + << " lines, expecting 2 or 4 lines."; return false; } @@ -139,51 +197,64 @@ static bool verify_image(const std::string& care_map_name) { return true; } +static int reboot_device() { + if (android_reboot(ANDROID_RB_RESTART2, 0, nullptr) == -1) { + LOG(ERROR) << "Failed to reboot."; + return -1; + } + while (true) pause(); +} + int main(int argc, char** argv) { for (int i = 1; i < argc; i++) { - SLOGI("Started with arg %d: %s\n", i, argv[i]); + LOG(INFO) << "Started with arg " << i << ": " << argv[i]; } - const hw_module_t* hw_module; - if (hw_get_module("bootctrl", &hw_module) != 0) { - SLOGE("Error getting bootctrl module.\n"); - return -1; + sp<IBootControl> module = IBootControl::getService(); + if (module == nullptr) { + LOG(ERROR) << "Error getting bootctrl module."; + return reboot_device(); } - boot_control_module_t* module = reinterpret_cast<boot_control_module_t*>( - const_cast<hw_module_t*>(hw_module)); - module->init(module); + uint32_t current_slot = module->getCurrentSlot(); + BoolResult is_successful = module->isSlotMarkedSuccessful(current_slot); + LOG(INFO) << "Booting slot " << current_slot << ": isSlotMarkedSuccessful=" + << static_cast<int32_t>(is_successful); - unsigned current_slot = module->getCurrentSlot(module); - int is_successful= module->isSlotMarkedSuccessful(module, current_slot); - SLOGI("Booting slot %u: isSlotMarkedSuccessful=%d\n", current_slot, is_successful); - if (is_successful == 0) { + if (is_successful == BoolResult::FALSE) { // The current slot has not booted successfully. - char verity_mode[PROPERTY_VALUE_MAX]; - if (property_get("ro.boot.veritymode", verity_mode, "") == -1) { - SLOGE("Failed to get dm-verity mode"); - return -1; - } else if (strcasecmp(verity_mode, "eio") == 0) { - // We shouldn't see verity in EIO mode if the current slot hasn't booted - // successfully before. Therefore, fail the verification when veritymode=eio. - SLOGE("Found dm-verity in EIO mode, skip verification."); - return -1; - } else if (strcmp(verity_mode, "enforcing") != 0) { - SLOGE("Unexpected dm-verity mode : %s, expecting enforcing.", verity_mode); - return -1; - } else if (!verify_image(CARE_MAP_FILE)) { - SLOGE("Failed to verify all blocks in care map file.\n"); - return -1; + +#ifdef PRODUCT_SUPPORTS_VERITY + std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", ""); + if (verity_mode.empty()) { + LOG(ERROR) << "Failed to get dm-verity mode."; + return reboot_device(); + } else if (android::base::EqualsIgnoreCase(verity_mode, "eio")) { + // We shouldn't see verity in EIO mode if the current slot hasn't booted successfully before. + // Continue the verification until we fail to read some blocks. + LOG(WARNING) << "Found dm-verity in EIO mode."; + } else if (verity_mode != "enforcing") { + LOG(ERROR) << "Unexpected dm-verity mode : " << verity_mode << ", expecting enforcing."; + return reboot_device(); + } + + if (!verify_image(CARE_MAP_FILE)) { + LOG(ERROR) << "Failed to verify all blocks in care map file."; + return reboot_device(); } +#else + LOG(WARNING) << "dm-verity not enabled; marking without verification."; +#endif - int ret = module->markBootSuccessful(module); - if (ret != 0) { - SLOGE("Error marking booted successfully: %s\n", strerror(-ret)); - return -1; + CommandResult cr; + module->markBootSuccessful([&cr](CommandResult result) { cr = result; }); + if (!cr.success) { + LOG(ERROR) << "Error marking booted successfully: " << cr.errMsg; + return reboot_device(); } - SLOGI("Marked slot %u as booted successfully.\n", current_slot); + LOG(INFO) << "Marked slot " << current_slot << " as booted successfully."; } - SLOGI("Leaving update_verifier.\n"); + LOG(INFO) << "Leaving update_verifier."; return 0; } diff --git a/update_verifier/update_verifier.rc b/update_verifier/update_verifier.rc new file mode 100644 index 000000000..862b06257 --- /dev/null +++ b/update_verifier/update_verifier.rc @@ -0,0 +1,11 @@ +service update_verifier_nonencrypted /system/bin/update_verifier nonencrypted + user root + group cache system + priority -20 + ioprio rt 0 + +service update_verifier /system/bin/update_verifier ${vold.decrypt} + user root + group cache system + priority -20 + ioprio rt 0 diff --git a/updater/Android.mk b/updater/Android.mk index d7aa613e9..a113fe86c 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -14,52 +14,90 @@ LOCAL_PATH := $(call my-dir) -updater_src_files := \ - install.cpp \ - blockimg.cpp \ - updater.cpp - -# -# Build a statically-linked binary to include in OTA packages -# +tune2fs_static_libraries := \ + libext2_com_err \ + libext2_blkid \ + libext2_quota \ + libext2_uuid \ + libext2_e2p \ + libext2fs + +updater_common_static_libraries := \ + libapplypatch \ + libbspatch \ + libedify \ + libziparchive \ + libotautil \ + libbootloader_message \ + libutils \ + libmounts \ + libotafault \ + libext4_utils \ + libfec \ + libfec_rs \ + libfs_mgr \ + liblog \ + libselinux \ + libsparse \ + libsquashfs_utils \ + libbz \ + libz \ + libbase \ + libcrypto \ + libcrypto_utils \ + libcutils \ + libtune2fs \ + $(tune2fs_static_libraries) + +# libupdater (static library) +# =============================== include $(CLEAR_VARS) -# Build only in eng, so we don't end up with a copy of this in /system -# on user builds. (TODO: find a better way to build device binaries -# needed only for OTA packages.) -LOCAL_MODULE_TAGS := eng +LOCAL_MODULE := libupdater -LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + install.cpp \ + blockimg.cpp -LOCAL_SRC_FILES := $(updater_src_files) +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/include \ + external/e2fsprogs/misc -LOCAL_STATIC_LIBRARIES += libfec libfec_rs libext4_utils_static libsquashfs_utils libcrypto_static +LOCAL_CFLAGS := \ + -Wno-unused-parameter \ + -Werror -ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) -LOCAL_CFLAGS += -DUSE_EXT4 -LOCAL_CFLAGS += -Wno-unused-parameter -LOCAL_C_INCLUDES += system/extras/ext4_utils -LOCAL_STATIC_LIBRARIES += \ - libsparse_static \ - libz -endif +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(LOCAL_PATH)/include -LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) -LOCAL_STATIC_LIBRARIES += libapplypatch libbase libotafault libedify libmtdutils libminzip libz -LOCAL_STATIC_LIBRARIES += libbz -LOCAL_STATIC_LIBRARIES += libcutils liblog libc -LOCAL_STATIC_LIBRARIES += libselinux -tune2fs_static_libraries := \ - libext2_com_err \ - libext2_blkid \ - libext2_quota \ - libext2_uuid_static \ - libext2_e2p \ - libext2fs -LOCAL_STATIC_LIBRARIES += libtune2fs $(tune2fs_static_libraries) +LOCAL_STATIC_LIBRARIES := \ + $(updater_common_static_libraries) -LOCAL_C_INCLUDES += external/e2fsprogs/misc -LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. +include $(BUILD_STATIC_LIBRARY) + +# updater (static executable) +# =============================== +include $(CLEAR_VARS) + +LOCAL_MODULE := updater + +LOCAL_SRC_FILES := \ + updater.cpp + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/include + +LOCAL_CFLAGS := \ + -Wno-unused-parameter \ + -Werror + +LOCAL_STATIC_LIBRARIES := \ + libupdater \ + $(TARGET_RECOVERY_UPDATER_LIBS) \ + $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \ + $(updater_common_static_libraries) # Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function # named "Register_<libname>()". Here we emit a little C function that @@ -72,21 +110,11 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. # any subsidiary static libraries required for your registered # extension libs. -inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc - -# Encode the value of TARGET_RECOVERY_UPDATER_LIBS into the filename of the dependency. -# So if TARGET_RECOVERY_UPDATER_LIBS is changed, a new dependency file will be generated. -# Note that we have to remove any existing depency files before creating new one, -# so no obsolete dependecy file gets used if you switch back to an old value. -inc_dep_file := $(inc).dep.$(subst $(space),-,$(sort $(TARGET_RECOVERY_UPDATER_LIBS))) -$(inc_dep_file): stem := $(inc).dep -$(inc_dep_file) : - $(hide) mkdir -p $(dir $@) - $(hide) rm -f $(stem).* - $(hide) touch $@ +LOCAL_MODULE_CLASS := EXECUTABLES +inc := $(call local-generated-sources-dir)/register.inc $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) -$(inc) : $(inc_dep_file) +$(inc) : $(hide) mkdir -p $(dir $@) $(hide) echo "" > $@ $(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;) @@ -94,13 +122,9 @@ $(inc) : $(inc_dep_file) $(hide) $(foreach lib,$(libs),echo " Register_$(lib)();" >> $@;) $(hide) echo "}" >> $@ -$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc) -LOCAL_C_INCLUDES += $(dir $(inc)) +LOCAL_GENERATED_SOURCES := $(inc) inc := -inc_dep_file := - -LOCAL_MODULE := updater LOCAL_FORCE_STATIC_EXECUTABLE := true diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index a80180a9a..c614ccc47 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -18,7 +18,6 @@ #include <errno.h> #include <dirent.h> #include <fcntl.h> -#include <inttypes.h> #include <linux/fs.h> #include <pthread.h> #include <stdarg.h> @@ -33,114 +32,130 @@ #include <unistd.h> #include <fec/io.h> -#include <map> +#include <functional> #include <memory> #include <string> +#include <unordered_map> #include <vector> +#include <android-base/logging.h> #include <android-base/parseint.h> #include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <applypatch/applypatch.h> +#include <openssl/sha.h> +#include <private/android_filesystem_config.h> +#include <ziparchive/zip_archive.h> -#include "applypatch/applypatch.h" #include "edify/expr.h" #include "error_code.h" -#include "install.h" -#include "openssl/sha.h" -#include "minzip/Hash.h" +#include "updater/install.h" #include "ota_io.h" #include "print_sha1.h" -#include "unique_fd.h" -#include "updater.h" - -#define BLOCKSIZE 4096 +#include "updater/updater.h" // Set this to 0 to interpret 'erase' transfers to mean do a // BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret // erase to mean fill the region with zeroes. #define DEBUG_ERASE 0 -#define STASH_DIRECTORY_BASE "/cache/recovery" -#define STASH_DIRECTORY_MODE 0700 -#define STASH_FILE_MODE 0600 +static constexpr size_t BLOCKSIZE = 4096; +static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery"; +static constexpr mode_t STASH_DIRECTORY_MODE = 0700; +static constexpr mode_t STASH_FILE_MODE = 0600; struct RangeSet { - size_t count; // Limit is INT_MAX. - size_t size; - std::vector<size_t> pos; // Actual limit is INT_MAX. + size_t count; // Limit is INT_MAX. + size_t size; + std::vector<size_t> pos; // Actual limit is INT_MAX. + + // Get the block number for the ith(starting from 0) block in the range set. + int get_block(size_t idx) const { + if (idx >= size) { + LOG(ERROR) << "index: " << idx << " is greater than range set size: " << size; + return -1; + } + for (size_t i = 0; i < pos.size(); i += 2) { + if (idx < pos[i + 1] - pos[i]) { + return pos[i] + idx; + } + idx -= (pos[i + 1] - pos[i]); + } + return -1; + } }; static CauseCode failure_type = kNoCause; static bool is_retry = false; -static std::map<std::string, RangeSet> stash_map; +static std::unordered_map<std::string, RangeSet> stash_map; -static void parse_range(const std::string& range_text, RangeSet& rs) { - - std::vector<std::string> pieces = android::base::Split(range_text, ","); - if (pieces.size() < 3) { - goto err; - } +static RangeSet parse_range(const std::string& range_text) { + RangeSet rs; - size_t num; - if (!android::base::ParseUint(pieces[0].c_str(), &num, static_cast<size_t>(INT_MAX))) { - goto err; - } + std::vector<std::string> pieces = android::base::Split(range_text, ","); + if (pieces.size() < 3) { + goto err; + } - if (num == 0 || num % 2) { - goto err; // must be even - } else if (num != pieces.size() - 1) { - goto err; - } + size_t num; + if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) { + goto err; + } - rs.pos.resize(num); - rs.count = num / 2; - rs.size = 0; + if (num == 0 || num % 2) { + goto err; // must be even + } else if (num != pieces.size() - 1) { + goto err; + } - for (size_t i = 0; i < num; i += 2) { - if (!android::base::ParseUint(pieces[i+1].c_str(), &rs.pos[i], - static_cast<size_t>(INT_MAX))) { - goto err; - } + rs.pos.resize(num); + rs.count = num / 2; + rs.size = 0; - if (!android::base::ParseUint(pieces[i+2].c_str(), &rs.pos[i+1], - static_cast<size_t>(INT_MAX))) { - goto err; - } + for (size_t i = 0; i < num; i += 2) { + if (!android::base::ParseUint(pieces[i + 1], &rs.pos[i], static_cast<size_t>(INT_MAX))) { + goto err; + } - if (rs.pos[i] >= rs.pos[i+1]) { - goto err; // empty or negative range - } + if (!android::base::ParseUint(pieces[i + 2], &rs.pos[i + 1], static_cast<size_t>(INT_MAX))) { + goto err; + } - size_t sz = rs.pos[i+1] - rs.pos[i]; - if (rs.size > SIZE_MAX - sz) { - goto err; // overflow - } + if (rs.pos[i] >= rs.pos[i + 1]) { + goto err; // empty or negative range + } - rs.size += sz; + size_t sz = rs.pos[i + 1] - rs.pos[i]; + if (rs.size > SIZE_MAX - sz) { + goto err; // overflow } - return; + rs.size += sz; + } + + return rs; err: - fprintf(stderr, "failed to parse range '%s'\n", range_text.c_str()); - exit(1); + LOG(ERROR) << "failed to parse range '" << range_text << "'"; + exit(EXIT_FAILURE); } static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) { - for (size_t i = 0; i < r1.count; ++i) { - size_t r1_0 = r1.pos[i * 2]; - size_t r1_1 = r1.pos[i * 2 + 1]; + for (size_t i = 0; i < r1.count; ++i) { + size_t r1_0 = r1.pos[i * 2]; + size_t r1_1 = r1.pos[i * 2 + 1]; - for (size_t j = 0; j < r2.count; ++j) { - size_t r2_0 = r2.pos[j * 2]; - size_t r2_1 = r2.pos[j * 2 + 1]; + for (size_t j = 0; j < r2.count; ++j) { + size_t r2_0 = r2.pos[j * 2]; + size_t r2_1 = r2.pos[j * 2 + 1]; - if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { - return true; - } - } + if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { + return true; + } } + } - return false; + return false; } static int read_all(int fd, uint8_t* data, size_t size) { @@ -149,7 +164,11 @@ static int read_all(int fd, uint8_t* data, size_t size) { ssize_t r = TEMP_FAILURE_RETRY(ota_read(fd, data+so_far, size-so_far)); if (r == -1) { failure_type = kFreadFailure; - fprintf(stderr, "read failed: %s\n", strerror(errno)); + PLOG(ERROR) << "read failed"; + return -1; + } else if (r == 0) { + failure_type = kFreadFailure; + LOG(ERROR) << "read reached unexpected EOF."; return -1; } so_far += r; @@ -167,7 +186,7 @@ static int write_all(int fd, const uint8_t* data, size_t size) { ssize_t w = TEMP_FAILURE_RETRY(ota_write(fd, data+written, size-written)); if (w == -1) { failure_type = kFwriteFailure; - fprintf(stderr, "write failed: %s\n", strerror(errno)); + PLOG(ERROR) << "write failed"; return -1; } written += w; @@ -189,7 +208,7 @@ static bool discard_blocks(int fd, off64_t offset, uint64_t size) { uint64_t args[2] = {static_cast<uint64_t>(offset), size}; int status = ioctl(fd, BLKDISCARD, &args); if (status == -1) { - fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); + PLOG(ERROR) << "BLKDISCARD ioctl failed"; return false; } return true; @@ -199,7 +218,7 @@ static bool check_lseek(int fd, off64_t offset, int whence) { off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence)); if (rc == -1) { failure_type = kLseekFailure; - fprintf(stderr, "lseek64 failed: %s\n", strerror(errno)); + PLOG(ERROR) << "lseek64 failed"; return false; } return true; @@ -213,7 +232,7 @@ static void allocate(size_t size, std::vector<uint8_t>& buffer) { } struct RangeSinkState { - RangeSinkState(RangeSet& rs) : tgt(rs) { }; + explicit RangeSinkState(RangeSet& rs) : tgt(rs) { }; int fd; const RangeSet& tgt; @@ -225,7 +244,7 @@ static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token); if (rss->p_remain == 0) { - fprintf(stderr, "range sink write overrun"); + LOG(ERROR) << "range sink write overrun"; return 0; } @@ -296,8 +315,8 @@ static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { // rss and signals the condition again. struct NewThreadInfo { - ZipArchive* za; - const ZipEntry* entry; + ZipArchiveHandle za; + ZipEntry entry; RangeSinkState* rss; @@ -305,7 +324,7 @@ struct NewThreadInfo { pthread_cond_t cv; }; -static bool receive_new_data(const unsigned char* data, int size, void* cookie) { +static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) { NewThreadInfo* nti = reinterpret_cast<NewThreadInfo*>(cookie); while (size > 0) { @@ -337,8 +356,8 @@ static bool receive_new_data(const unsigned char* data, int size, void* cookie) } static void* unzip_new_data(void* cookie) { - NewThreadInfo* nti = (NewThreadInfo*) cookie; - mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti); + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); + ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti); return nullptr; } @@ -398,7 +417,7 @@ struct CommandParameters { std::string stashbase; bool canwrite; int createdstash; - int fd; + android::base::unique_fd fd; bool foundwrites; bool isunresumable; int version; @@ -410,34 +429,109 @@ struct CommandParameters { uint8_t* patch_start; }; -// Do a source/target load for move/bsdiff/imgdiff in version 1. -// We expect to parse the remainder of the parameter tokens as: -// -// <src_range> <tgt_range> -// -// The source range is loaded into the provided buffer, reallocating -// it to make it larger if necessary. - -static int LoadSrcTgtVersion1(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, - std::vector<uint8_t>& buffer, int fd) { +// Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is +// handled separately). +static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params, + const std::vector<uint8_t>& buffer) { + LOG(INFO) << "unexpected contents of source blocks in cmd:\n" << params.cmdline; + CHECK(params.tokens[0] == "move" || params.tokens[0] == "bsdiff" || + params.tokens[0] == "imgdiff"); + + size_t pos = 0; + // Command example: + // move <onehash> <tgt_range> <src_blk_count> <src_range> [<loc_range> <stashed_blocks>] + // bsdiff <offset> <len> <src_hash> <tgt_hash> <tgt_range> <src_blk_count> <src_range> + // [<loc_range> <stashed_blocks>] + if (params.tokens[0] == "move") { + // src_range for move starts at the 4th position. + if (params.tokens.size() < 5) { + LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline; + return; + } + pos = 4; + } else { + // src_range for diff starts at the 7th position. + if (params.tokens.size() < 8) { + LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline; + return; + } + pos = 7; + } + + // Source blocks in stash only, no work to do. + if (params.tokens[pos] == "-") { + return; + } + + RangeSet src = parse_range(params.tokens[pos++]); + + RangeSet locs; + // If there's no stashed blocks, content in the buffer is consecutive and has the same + // order as the source blocks. + if (pos == params.tokens.size()) { + locs.count = 1; + locs.size = src.size; + locs.pos = { 0, src.size }; + } else { + // Otherwise, the next token is the offset of the source blocks in the target range. + // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> <stashed_blocks>; + // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38]; + // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978. + locs = parse_range(params.tokens[pos++]); + CHECK_EQ(src.size, locs.size); + CHECK_EQ(locs.pos.size() % 2, static_cast<size_t>(0)); + } + + LOG(INFO) << "printing hash in hex for " << src.size << " source blocks"; + for (size_t i = 0; i < src.size; i++) { + int block_num = src.get_block(i); + CHECK_NE(block_num, -1); + int buffer_index = locs.get_block(i); + CHECK_NE(buffer_index, -1); + CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size()); - if (params.cpos + 1 >= params.tokens.size()) { - fprintf(stderr, "invalid parameters\n"); - return -1; - } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data() + buffer_index * BLOCKSIZE, BLOCKSIZE, digest); + std::string hexdigest = print_sha1(digest); + LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; + } +} - // <src_range> - RangeSet src; - parse_range(params.tokens[params.cpos++], src); +// If the calculated hash for the whole stash doesn't match the stash id, print the SHA-1 +// in hex for each block. +static void PrintHashForCorruptedStashedBlocks(const std::string& id, + const std::vector<uint8_t>& buffer, + const RangeSet& src) { + LOG(INFO) << "printing hash in hex for stash_id: " << id; + CHECK_EQ(src.size * BLOCKSIZE, buffer.size()); - // <tgt_range> - parse_range(params.tokens[params.cpos++], tgt); + for (size_t i = 0; i < src.size; i++) { + int block_num = src.get_block(i); + CHECK_NE(block_num, -1); - allocate(src.size * BLOCKSIZE, buffer); - int rc = ReadBlocks(src, buffer, fd); - src_blocks = src.size; + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest); + std::string hexdigest = print_sha1(digest); + LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; + } +} - return rc; +// If the stash file doesn't exist, read the source blocks this stash contains and print the +// SHA-1 for these blocks. +static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) { + if (stash_map.find(id) == stash_map.end()) { + LOG(ERROR) << "No stash saved for id: " << id; + return; + } + + LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id; + const RangeSet& src = stash_map[id]; + std::vector<uint8_t> buffer(src.size * BLOCKSIZE); + if (ReadBlocks(src, buffer, fd) == -1) { + LOG(ERROR) << "failed to read source blocks for stash: " << id; + return; + } + PrintHashForCorruptedStashedBlocks(id, buffer, src); } static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& buffer, @@ -451,8 +545,8 @@ static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& if (hexdigest != expected) { if (printerror) { - fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n", - expected.c_str(), hexdigest.c_str()); + LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read " + << hexdigest << ")"; } return -1; } @@ -472,92 +566,58 @@ static std::string GetStashFileName(const std::string& base, const std::string& return fn; } -typedef void (*StashCallback)(const std::string&, void*); +// Does a best effort enumeration of stash files. Ignores possible non-file items in the stash +// directory and continues despite of errors. Calls the 'callback' function for each file. +static void EnumerateStash(const std::string& dirname, + const std::function<void(const std::string&)>& callback) { + if (dirname.empty()) return; -// Does a best effort enumeration of stash files. Ignores possible non-file -// items in the stash directory and continues despite of errors. Calls the -// 'callback' function for each file and passes 'data' to the function as a -// parameter. + std::unique_ptr<DIR, decltype(&closedir)> directory(opendir(dirname.c_str()), closedir); -static void EnumerateStash(const std::string& dirname, StashCallback callback, void* data) { - if (dirname.empty() || callback == nullptr) { - return; - } - - std::unique_ptr<DIR, int(*)(DIR*)> directory(opendir(dirname.c_str()), closedir); - - if (directory == nullptr) { - if (errno != ENOENT) { - fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno)); - } - return; - } - - struct dirent* item; - while ((item = readdir(directory.get())) != nullptr) { - if (item->d_type != DT_REG) { - continue; - } - - std::string fn = dirname + "/" + std::string(item->d_name); - callback(fn, data); - } -} - -static void UpdateFileSize(const std::string& fn, void* data) { - if (fn.empty() || !data) { - return; - } - - struct stat sb; - if (stat(fn.c_str(), &sb) == -1) { - fprintf(stderr, "stat \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); - return; + if (directory == nullptr) { + if (errno != ENOENT) { + PLOG(ERROR) << "opendir \"" << dirname << "\" failed"; } + return; + } - int* size = reinterpret_cast<int*>(data); - *size += sb.st_size; + dirent* item; + while ((item = readdir(directory.get())) != nullptr) { + if (item->d_type != DT_REG) continue; + callback(dirname + "/" + item->d_name); + } } // Deletes the stash directory and all files in it. Assumes that it only // contains files. There is nothing we can do about unlikely, but possible // errors, so they are merely logged. +static void DeleteFile(const std::string& fn) { + if (fn.empty()) return; -static void DeleteFile(const std::string& fn, void* /* data */) { - if (!fn.empty()) { - fprintf(stderr, "deleting %s\n", fn.c_str()); + LOG(INFO) << "deleting " << fn; - if (unlink(fn.c_str()) == -1 && errno != ENOENT) { - fprintf(stderr, "unlink \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); - } - } -} - -static void DeletePartial(const std::string& fn, void* data) { - if (android::base::EndsWith(fn, ".partial")) { - DeleteFile(fn, data); - } + if (unlink(fn.c_str()) == -1 && errno != ENOENT) { + PLOG(ERROR) << "unlink \"" << fn << "\" failed"; + } } static void DeleteStash(const std::string& base) { - if (base.empty()) { - return; - } + if (base.empty()) return; - fprintf(stderr, "deleting stash %s\n", base.c_str()); + LOG(INFO) << "deleting stash " << base; - std::string dirname = GetStashFileName(base, "", ""); - EnumerateStash(dirname, DeleteFile, nullptr); + std::string dirname = GetStashFileName(base, "", ""); + EnumerateStash(dirname, DeleteFile); - if (rmdir(dirname.c_str()) == -1) { - if (errno != ENOENT && errno != ENOTDIR) { - fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno)); - } + if (rmdir(dirname.c_str()) == -1) { + if (errno != ENOENT && errno != ENOTDIR) { + PLOG(ERROR) << "rmdir \"" << dirname << "\" failed"; } + } } -static int LoadStash(CommandParameters& params, const std::string& base, const std::string& id, - bool verify, size_t* blocks, std::vector<uint8_t>& buffer, bool printnoent) { +static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks, + std::vector<uint8_t>& buffer, bool printnoent) { // In verify mode, if source range_set was saved for the given hash, // check contents in the source blocks first. If the check fails, // search for the stashed files on /cache as usual. @@ -567,52 +627,47 @@ static int LoadStash(CommandParameters& params, const std::string& base, const s allocate(src.size * BLOCKSIZE, buffer); if (ReadBlocks(src, buffer, params.fd) == -1) { - fprintf(stderr, "failed to read source blocks in stash map.\n"); + LOG(ERROR) << "failed to read source blocks in stash map."; return -1; } if (VerifyBlocks(id, buffer, src.size, true) != 0) { - fprintf(stderr, "failed to verify loaded source blocks in stash map.\n"); + LOG(ERROR) << "failed to verify loaded source blocks in stash map."; + PrintHashForCorruptedStashedBlocks(id, buffer, src); return -1; } return 0; } } - if (base.empty()) { - return -1; - } - size_t blockcount = 0; if (!blocks) { blocks = &blockcount; } - std::string fn = GetStashFileName(base, id, ""); + std::string fn = GetStashFileName(params.stashbase, id, ""); struct stat sb; int res = stat(fn.c_str(), &sb); if (res == -1) { if (errno != ENOENT || printnoent) { - fprintf(stderr, "stat \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + PLOG(ERROR) << "stat \"" << fn << "\" failed"; + PrintHashForMissingStashedBlocks(id, params.fd); } return -1; } - fprintf(stderr, " loading %s\n", fn.c_str()); + LOG(INFO) << " loading " << fn; if ((sb.st_size % BLOCKSIZE) != 0) { - fprintf(stderr, "%s size %" PRId64 " not multiple of block size %d", - fn.c_str(), static_cast<int64_t>(sb.st_size), BLOCKSIZE); + LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE; return -1; } - int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)); - unique_fd fd_holder(fd); - + android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); if (fd == -1) { - fprintf(stderr, "open \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + PLOG(ERROR) << "open \"" << fn << "\" failed"; return -1; } @@ -625,8 +680,15 @@ static int LoadStash(CommandParameters& params, const std::string& base, const s *blocks = sb.st_size / BLOCKSIZE; if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { - fprintf(stderr, "unexpected contents in %s\n", fn.c_str()); - DeleteFile(fn, nullptr); + LOG(ERROR) << "unexpected contents in " << fn; + if (stash_map.find(id) == stash_map.end()) { + LOG(ERROR) << "failed to find source blocks number for stash " << id + << " when executing command: " << params.cmdname; + } else { + const RangeSet& src = stash_map[id]; + PrintHashForCorruptedStashedBlocks(id, buffer, src); + } + DeleteFile(fn); return -1; } @@ -634,13 +696,13 @@ static int LoadStash(CommandParameters& params, const std::string& base, const s } static int WriteStash(const std::string& base, const std::string& id, int blocks, - std::vector<uint8_t>& buffer, bool checkspace, bool *exists) { + std::vector<uint8_t>& buffer, bool checkspace, bool *exists) { if (base.empty()) { return -1; } if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) { - fprintf(stderr, "not enough space to write stash\n"); + LOG(ERROR) << "not enough space to write stash"; return -1; } @@ -655,7 +717,7 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks // The file already exists and since the name is the hash of the contents, // it's safe to assume the contents are identical (accidental hash collisions // are unlikely) - fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn.c_str()); + LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn; *exists = true; return 0; } @@ -663,13 +725,17 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks *exists = false; } - fprintf(stderr, " writing %d blocks to %s\n", blocks, cn.c_str()); - - int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE)); - unique_fd fd_holder(fd); + LOG(INFO) << " writing " << blocks << " blocks to " << cn; + android::base::unique_fd fd( + TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE))); if (fd == -1) { - fprintf(stderr, "failed to create \"%s\": %s\n", fn.c_str(), strerror(errno)); + PLOG(ERROR) << "failed to create \"" << fn << "\""; + return -1; + } + + if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) { // system user + PLOG(ERROR) << "failed to chown \"" << fn << "\""; return -1; } @@ -679,29 +745,27 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks if (ota_fsync(fd) == -1) { failure_type = kFsyncFailure; - fprintf(stderr, "fsync \"%s\" failed: %s\n", fn.c_str(), strerror(errno)); + PLOG(ERROR) << "fsync \"" << fn << "\" failed"; return -1; } if (rename(fn.c_str(), cn.c_str()) == -1) { - fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn.c_str(), cn.c_str(), - strerror(errno)); + PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed"; return -1; } std::string dname = GetStashFileName(base, "", ""); - int dfd = TEMP_FAILURE_RETRY(open(dname.c_str(), O_RDONLY | O_DIRECTORY)); - unique_fd dfd_holder(dfd); - + android::base::unique_fd dfd(TEMP_FAILURE_RETRY(ota_open(dname.c_str(), + O_RDONLY | O_DIRECTORY))); if (dfd == -1) { failure_type = kFileOpenFailure; - fprintf(stderr, "failed to open \"%s\" failed: %s\n", dname.c_str(), strerror(errno)); + PLOG(ERROR) << "failed to open \"" << dname << "\" failed"; return -1; } if (ota_fsync(dfd) == -1) { failure_type = kFsyncFailure; - fprintf(stderr, "fsync \"%s\" failed: %s\n", dname.c_str(), strerror(errno)); + PLOG(ERROR) << "fsync \"" << dname << "\" failed"; return -1; } @@ -712,121 +776,94 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks // hash enough space for the expected amount of blocks we need to store. Returns // >0 if we created the directory, zero if it existed already, and <0 of failure. -static int CreateStash(State* state, int maxblocks, const char* blockdev, std::string& base) { - if (blockdev == nullptr) { - return -1; - } - - // Stash directory should be different for each partition to avoid conflicts - // when updating multiple partitions at the same time, so we use the hash of - // the block device name as the base directory - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast<const uint8_t*>(blockdev), strlen(blockdev), digest); - base = print_sha1(digest); - - std::string dirname = GetStashFileName(base, "", ""); - struct stat sb; - int res = stat(dirname.c_str(), &sb); - - if (res == -1 && errno != ENOENT) { - ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", - dirname.c_str(), strerror(errno)); - return -1; - } else if (res != 0) { - fprintf(stderr, "creating stash %s\n", dirname.c_str()); - res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE); - - if (res != 0) { - ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", - dirname.c_str(), strerror(errno)); - return -1; - } - - if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) { - ErrorAbort(state, kStashCreationFailure, "not enough space for stash\n"); - return -1; - } +static int CreateStash(State* state, size_t maxblocks, const std::string& blockdev, + std::string& base) { + if (blockdev.empty()) { + return -1; + } + + // Stash directory should be different for each partition to avoid conflicts + // when updating multiple partitions at the same time, so we use the hash of + // the block device name as the base directory + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(blockdev.data()), blockdev.size(), digest); + base = print_sha1(digest); + + std::string dirname = GetStashFileName(base, "", ""); + struct stat sb; + int res = stat(dirname.c_str(), &sb); + size_t max_stash_size = maxblocks * BLOCKSIZE; + + if (res == -1 && errno != ENOENT) { + ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", dirname.c_str(), + strerror(errno)); + return -1; + } else if (res != 0) { + LOG(INFO) << "creating stash " << dirname; + res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE); - return 1; // Created directory + if (res != 0) { + ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", dirname.c_str(), + strerror(errno)); + return -1; } - fprintf(stderr, "using existing stash %s\n", dirname.c_str()); - - // If the directory already exists, calculate the space already allocated to - // stash files and check if there's enough for all required blocks. Delete any - // partially completed stash files first. - - EnumerateStash(dirname, DeletePartial, nullptr); - int size = 0; - EnumerateStash(dirname, UpdateFileSize, &size); - - size = maxblocks * BLOCKSIZE - size; - - if (size > 0 && CacheSizeCheck(size) != 0) { - ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%d more needed)\n", - size); - return -1; + if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) { // system user + ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s\n", dirname.c_str(), + strerror(errno)); + return -1; } - return 0; // Using existing directory -} - -static int SaveStash(CommandParameters& params, const std::string& base, - std::vector<uint8_t>& buffer, int fd, bool usehash) { - - // <stash_id> <src_range> - if (params.cpos + 1 >= params.tokens.size()) { - fprintf(stderr, "missing id and/or src range fields in stash command\n"); - return -1; + if (CacheSizeCheck(max_stash_size) != 0) { + ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n", + max_stash_size); + return -1; } - const std::string& id = params.tokens[params.cpos++]; - size_t blocks = 0; - if (usehash && LoadStash(params, base, id, true, &blocks, buffer, false) == 0) { - // Stash file already exists and has expected contents. Do not - // read from source again, as the source may have been already - // overwritten during a previous attempt. - return 0; - } + return 1; // Created directory + } - RangeSet src; - parse_range(params.tokens[params.cpos++], src); + LOG(INFO) << "using existing stash " << dirname; - allocate(src.size * BLOCKSIZE, buffer); - if (ReadBlocks(src, buffer, fd) == -1) { - return -1; + // If the directory already exists, calculate the space already allocated to stash files and check + // if there's enough for all required blocks. Delete any partially completed stash files first. + EnumerateStash(dirname, [](const std::string& fn) { + if (android::base::EndsWith(fn, ".partial")) { + DeleteFile(fn); } - blocks = src.size; + }); - if (usehash && VerifyBlocks(id, buffer, blocks, true) != 0) { - // Source blocks have unexpected contents. If we actually need this - // data later, this is an unrecoverable error. However, the command - // that uses the data may have already completed previously, so the - // possible failure will occur during source block verification. - fprintf(stderr, "failed to load source blocks for stash %s\n", id.c_str()); - return 0; + size_t existing = 0; + EnumerateStash(dirname, [&existing](const std::string& fn) { + if (fn.empty()) return; + struct stat sb; + if (stat(fn.c_str(), &sb) == -1) { + PLOG(ERROR) << "stat \"" << fn << "\" failed"; + return; } + existing += static_cast<size_t>(sb.st_size); + }); - // In verify mode, save source range_set instead of stashing blocks. - if (!params.canwrite && usehash) { - stash_map[id] = src; - return 0; + if (max_stash_size > existing) { + size_t needed = max_stash_size - existing; + if (CacheSizeCheck(needed) != 0) { + ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)\n", + needed); + return -1; } + } - fprintf(stderr, "stashing %zu blocks to %s\n", blocks, id.c_str()); - params.stashed += blocks; - return WriteStash(base, id, blocks, buffer, false, nullptr); + return 0; // Using existing directory } static int FreeStash(const std::string& base, const std::string& id) { - if (base.empty() || id.empty()) { - return -1; - } + if (base.empty() || id.empty()) { + return -1; + } - std::string fn = GetStashFileName(base, id, ""); - DeleteFile(fn, nullptr); + DeleteFile(GetStashFileName(base, id, "")); - return 0; + return 0; } static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs, @@ -858,41 +895,39 @@ static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs, // <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> // (loads data from both source image and stashes) // -// On return, buffer is filled with the loaded source data (rearranged -// and combined with stashed data as necessary). buffer may be -// reallocated if needed to accommodate the source data. *tgt is the -// target RangeSet. Any stashes required are loaded using LoadStash. +// On return, params.buffer is filled with the loaded source data (rearranged and combined with +// stashed data as necessary). buffer may be reallocated if needed to accommodate the source data. +// *tgt is the target RangeSet. Any stashes required are loaded using LoadStash. static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, - std::vector<uint8_t>& buffer, int fd, const std::string& stashbase, bool* overlap) { + bool* overlap) { // At least it needs to provide three parameters: <tgt_range>, // <src_block_count> and "-"/<src_range>. if (params.cpos + 2 >= params.tokens.size()) { - fprintf(stderr, "invalid parameters\n"); + LOG(ERROR) << "invalid parameters"; return -1; } // <tgt_range> - parse_range(params.tokens[params.cpos++], tgt); + tgt = parse_range(params.tokens[params.cpos++]); // <src_block_count> const std::string& token = params.tokens[params.cpos++]; if (!android::base::ParseUint(token.c_str(), &src_blocks)) { - fprintf(stderr, "invalid src_block_count \"%s\"\n", token.c_str()); + LOG(ERROR) << "invalid src_block_count \"" << token << "\""; return -1; } - allocate(src_blocks * BLOCKSIZE, buffer); + allocate(src_blocks * BLOCKSIZE, params.buffer); // "-" or <src_range> [<src_loc>] if (params.tokens[params.cpos] == "-") { // no source ranges, only stashes params.cpos++; } else { - RangeSet src; - parse_range(params.tokens[params.cpos++], src); - int res = ReadBlocks(src, buffer, fd); + RangeSet src = parse_range(params.tokens[params.cpos++]); + int res = ReadBlocks(src, params.buffer, params.fd); if (overlap) { *overlap = range_overlaps(src, tgt); @@ -907,9 +942,8 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& return 0; } - RangeSet locs; - parse_range(params.tokens[params.cpos++], locs); - MoveRange(buffer, locs, buffer); + RangeSet locs = parse_range(params.tokens[params.cpos++]); + MoveRange(params.buffer, locs, params.buffer); } // <[stash_id:stash_range]> @@ -919,51 +953,59 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& // stashed data should go. std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":"); if (tokens.size() != 2) { - fprintf(stderr, "invalid parameter\n"); + LOG(ERROR) << "invalid parameter"; return -1; } std::vector<uint8_t> stash; - int res = LoadStash(params, stashbase, tokens[0], false, nullptr, stash, true); + int res = LoadStash(params, tokens[0], false, nullptr, stash, true); if (res == -1) { // These source blocks will fail verification if used later, but we // will let the caller decide if this is a fatal failure - fprintf(stderr, "failed to load stash %s\n", tokens[0].c_str()); + LOG(ERROR) << "failed to load stash " << tokens[0]; continue; } - RangeSet locs; - parse_range(tokens[1], locs); + RangeSet locs = parse_range(tokens[1]); - MoveRange(buffer, locs, stash); + MoveRange(params.buffer, locs, stash); } return 0; } -// Do a source/target load for move/bsdiff/imgdiff in version 3. -// -// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which -// tells the function whether to expect separate source and targe block hashes, or -// if they are both the same and only one hash should be expected, and -// 'isunresumable', which receives a non-zero value if block verification fails in -// a way that the update cannot be resumed anymore. -// -// If the function is unable to load the necessary blocks or their contents don't -// match the hashes, the return value is -1 and the command should be aborted. -// -// If the return value is 1, the command has already been completed according to -// the contents of the target blocks, and should not be performed again. -// -// If the return value is 0, source blocks have expected content and the command -// can be performed. - +/** + * Do a source/target load for move/bsdiff/imgdiff in version 3. + * + * We expect to parse the remainder of the parameter tokens as one of: + * + * <tgt_range> <src_block_count> <src_range> + * (loads data from source image only) + * + * <tgt_range> <src_block_count> - <[stash_id:stash_range] ...> + * (loads data from stashes only) + * + * <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> + * (loads data from both source image and stashes) + * + * Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which tells the function + * whether to expect separate source and targe block hashes, or if they are both the same and only + * one hash should be expected, and 'isunresumable', which receives a non-zero value if block + * verification fails in a way that the update cannot be resumed anymore. + * + * If the function is unable to load the necessary blocks or their contents don't match the hashes, + * the return value is -1 and the command should be aborted. + * + * If the return value is 1, the command has already been completed according to the contents of the + * target blocks, and should not be performed again. + * + * If the return value is 0, source blocks have expected content and the command can be performed. + */ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, - bool onehash, bool& overlap) { - + bool onehash, bool& overlap) { if (params.cpos >= params.tokens.size()) { - fprintf(stderr, "missing source hash\n"); + LOG(ERROR) << "missing source hash"; return -1; } @@ -974,14 +1016,13 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& tgthash = srchash; } else { if (params.cpos >= params.tokens.size()) { - fprintf(stderr, "missing target hash\n"); + LOG(ERROR) << "missing target hash"; return -1; } tgthash = params.tokens[params.cpos++]; } - if (LoadSrcTgtVersion2(params, tgt, src_blocks, params.buffer, params.fd, params.stashbase, - &overlap) == -1) { + if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) { return -1; } @@ -992,7 +1033,7 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& } if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) { - // Target blocks already have expected content, command should be skipped + // Target blocks already have expected content, command should be skipped. return 1; } @@ -1001,128 +1042,150 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& // resume from possible write errors. In verify mode, we can skip stashing // because the source blocks won't be overwritten. if (overlap && params.canwrite) { - fprintf(stderr, "stashing %zu overlapping blocks to %s\n", src_blocks, - srchash.c_str()); + LOG(INFO) << "stashing " << src_blocks << " overlapping blocks to " << srchash; bool stash_exists = false; if (WriteStash(params.stashbase, srchash, src_blocks, params.buffer, true, &stash_exists) != 0) { - fprintf(stderr, "failed to stash overlapping source blocks\n"); + LOG(ERROR) << "failed to stash overlapping source blocks"; return -1; } params.stashed += src_blocks; - // Can be deleted when the write has completed + // Can be deleted when the write has completed. if (!stash_exists) { params.freestash = srchash; } } - // Source blocks have expected content, command can proceed + // Source blocks have expected content, command can proceed. return 0; } - if (overlap && LoadStash(params, params.stashbase, srchash, true, nullptr, params.buffer, - true) == 0) { + if (overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { // Overlapping source blocks were previously stashed, command can proceed. // We are recovering from an interrupted command, so we don't know if the // stash can safely be deleted after this command. return 0; } - // Valid source data not available, update cannot be resumed - fprintf(stderr, "partition has unexpected contents\n"); + // Valid source data not available, update cannot be resumed. + LOG(ERROR) << "partition has unexpected contents"; + PrintHashForCorruptedSourceBlocks(params, params.buffer); + params.isunresumable = true; return -1; } static int PerformCommandMove(CommandParameters& params) { - size_t blocks = 0; - bool overlap = false; - int status = 0; - RangeSet tgt; + size_t blocks = 0; + bool overlap = false; + RangeSet tgt; + int status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap); - if (params.version == 1) { - status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd); - } else if (params.version == 2) { - status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd, - params.stashbase, nullptr); - } else if (params.version >= 3) { - status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap); - } + if (status == -1) { + LOG(ERROR) << "failed to read blocks for move"; + return -1; + } - if (status == -1) { - fprintf(stderr, "failed to read blocks for move\n"); - return -1; - } + if (status == 0) { + params.foundwrites = true; + } else if (params.foundwrites) { + LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } + if (params.canwrite) { if (status == 0) { - params.foundwrites = true; - } else if (params.foundwrites) { - fprintf(stderr, "warning: commands executed out of order [%s]\n", params.cmdname); + LOG(INFO) << " moving " << blocks << " blocks"; + + if (WriteBlocks(tgt, params.buffer, params.fd) == -1) { + return -1; + } + } else { + LOG(INFO) << "skipping " << blocks << " already moved blocks"; } + } - if (params.canwrite) { - if (status == 0) { - fprintf(stderr, " moving %zu blocks\n", blocks); + if (!params.freestash.empty()) { + FreeStash(params.stashbase, params.freestash); + params.freestash.clear(); + } - if (WriteBlocks(tgt, params.buffer, params.fd) == -1) { - return -1; - } - } else { - fprintf(stderr, "skipping %zu already moved blocks\n", blocks); - } + params.written += tgt.size; - } + return 0; +} - if (!params.freestash.empty()) { - FreeStash(params.stashbase, params.freestash); - params.freestash.clear(); - } +static int PerformCommandStash(CommandParameters& params) { + // <stash_id> <src_range> + if (params.cpos + 1 >= params.tokens.size()) { + LOG(ERROR) << "missing id and/or src range fields in stash command"; + return -1; + } - params.written += tgt.size; + const std::string& id = params.tokens[params.cpos++]; + size_t blocks = 0; + if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) { + // Stash file already exists and has expected contents. Do not read from source again, as the + // source may have been already overwritten during a previous attempt. + return 0; + } + RangeSet src = parse_range(params.tokens[params.cpos++]); + + allocate(src.size * BLOCKSIZE, params.buffer); + if (ReadBlocks(src, params.buffer, params.fd) == -1) { + return -1; + } + blocks = src.size; + stash_map[id] = src; + + if (VerifyBlocks(id, params.buffer, blocks, true) != 0) { + // Source blocks have unexpected contents. If we actually need this data later, this is an + // unrecoverable error. However, the command that uses the data may have already completed + // previously, so the possible failure will occur during source block verification. + LOG(ERROR) << "failed to load source blocks for stash " << id; return 0; -} + } -static int PerformCommandStash(CommandParameters& params) { - return SaveStash(params, params.stashbase, params.buffer, params.fd, - (params.version >= 3)); + // In verify mode, we don't need to stash any blocks. + if (!params.canwrite) { + return 0; + } + + LOG(INFO) << "stashing " << blocks << " blocks to " << id; + params.stashed += blocks; + return WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr); } static int PerformCommandFree(CommandParameters& params) { - // <stash_id> - if (params.cpos >= params.tokens.size()) { - fprintf(stderr, "missing stash id in free command\n"); - return -1; - } + // <stash_id> + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing stash id in free command"; + return -1; + } - const std::string& id = params.tokens[params.cpos++]; + const std::string& id = params.tokens[params.cpos++]; + stash_map.erase(id); - if (!params.canwrite && stash_map.find(id) != stash_map.end()) { - stash_map.erase(id); - return 0; - } - - if (params.createdstash || params.canwrite) { - return FreeStash(params.stashbase, id); - } + if (params.createdstash || params.canwrite) { + return FreeStash(params.stashbase, id); + } - return 0; + return 0; } static int PerformCommandZero(CommandParameters& params) { if (params.cpos >= params.tokens.size()) { - fprintf(stderr, "missing target blocks for zero\n"); + LOG(ERROR) << "missing target blocks for zero"; return -1; } - RangeSet tgt; - parse_range(params.tokens[params.cpos++], tgt); + RangeSet tgt = parse_range(params.tokens[params.cpos++]); - fprintf(stderr, " zeroing %zu blocks\n", tgt.size); + LOG(INFO) << " zeroing " << tgt.size << " blocks"; allocate(BLOCKSIZE, params.buffer); memset(params.buffer.data(), 0, BLOCKSIZE); @@ -1159,15 +1222,14 @@ static int PerformCommandZero(CommandParameters& params) { static int PerformCommandNew(CommandParameters& params) { if (params.cpos >= params.tokens.size()) { - fprintf(stderr, "missing target blocks for new\n"); + LOG(ERROR) << "missing target blocks for new"; return -1; } - RangeSet tgt; - parse_range(params.tokens[params.cpos++], tgt); + RangeSet tgt = parse_range(params.tokens[params.cpos++]); if (params.canwrite) { - fprintf(stderr, " writing %zu blocks of new data\n", tgt.size); + LOG(INFO) << " writing " << tgt.size << " blocks of new data"; RangeSinkState rss(tgt); rss.fd = params.fd; @@ -1203,55 +1265,43 @@ static int PerformCommandDiff(CommandParameters& params) { // <offset> <length> if (params.cpos + 1 >= params.tokens.size()) { - fprintf(stderr, "missing patch offset or length for %s\n", params.cmdname); + LOG(ERROR) << "missing patch offset or length for " << params.cmdname; return -1; } size_t offset; if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &offset)) { - fprintf(stderr, "invalid patch offset\n"); + LOG(ERROR) << "invalid patch offset"; return -1; } size_t len; if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &len)) { - fprintf(stderr, "invalid patch offset\n"); + LOG(ERROR) << "invalid patch len"; return -1; } RangeSet tgt; size_t blocks = 0; bool overlap = false; - int status = 0; - if (params.version == 1) { - status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd); - } else if (params.version == 2) { - status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd, - params.stashbase, nullptr); - } else if (params.version >= 3) { - status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap); - } + int status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap); if (status == -1) { - fprintf(stderr, "failed to read blocks for diff\n"); + LOG(ERROR) << "failed to read blocks for diff"; return -1; } if (status == 0) { params.foundwrites = true; } else if (params.foundwrites) { - fprintf(stderr, "warning: commands executed out of order [%s]\n", params.cmdname); + LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; } if (params.canwrite) { if (status == 0) { - fprintf(stderr, "patching %zu blocks to %zu\n", blocks, tgt.size); - - Value patch_value; - patch_value.type = VAL_BLOB; - patch_value.size = len; - patch_value.data = (char*) (params.patch_start + offset); - + LOG(INFO) << "patching " << blocks << " blocks to " << tgt.size; + Value patch_value(VAL_BLOB, + std::string(reinterpret_cast<const char*>(params.patch_start + offset), len)); RangeSinkState rss(tgt); rss.fd = params.fd; rss.p_block = 0; @@ -1269,24 +1319,24 @@ static int PerformCommandDiff(CommandParameters& params) { if (params.cmdname[0] == 'i') { // imgdiff if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, &RangeSinkWrite, &rss, nullptr, nullptr) != 0) { - fprintf(stderr, "Failed to apply image patch.\n"); + LOG(ERROR) << "Failed to apply image patch."; return -1; } } else { if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0, &RangeSinkWrite, &rss, nullptr) != 0) { - fprintf(stderr, "Failed to apply bsdiff patch.\n"); + LOG(ERROR) << "Failed to apply bsdiff patch."; return -1; } } // We expect the output of the patcher to fill the tgt ranges exactly. if (rss.p_block != tgt.count || rss.p_remain != 0) { - fprintf(stderr, "range sink underrun?\n"); + LOG(ERROR) << "range sink underrun?"; } } else { - fprintf(stderr, "skipping %zu blocks already patched to %zu [%s]\n", - blocks, tgt.size, params.cmdline); + LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.size + << " [" << params.cmdline << "]"; } } @@ -1307,25 +1357,24 @@ static int PerformCommandErase(CommandParameters& params) { struct stat sb; if (fstat(params.fd, &sb) == -1) { - fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno)); + PLOG(ERROR) << "failed to fstat device to erase"; return -1; } if (!S_ISBLK(sb.st_mode)) { - fprintf(stderr, "not a block device; skipping erase\n"); + LOG(ERROR) << "not a block device; skipping erase"; return -1; } if (params.cpos >= params.tokens.size()) { - fprintf(stderr, "missing target blocks for erase\n"); + LOG(ERROR) << "missing target blocks for erase"; return -1; } - RangeSet tgt; - parse_range(params.tokens[params.cpos++], tgt); + RangeSet tgt = parse_range(params.tokens[params.cpos++]); if (params.canwrite) { - fprintf(stderr, " erasing %zu blocks\n", tgt.size); + LOG(INFO) << " erasing " << tgt.size << " blocks"; for (size_t i = 0; i < tgt.count; ++i) { uint64_t blocks[2]; @@ -1335,7 +1384,7 @@ static int PerformCommandErase(CommandParameters& params) { blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE; if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { - fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno)); + PLOG(ERROR) << "BLKDISCARD ioctl failed"; return -1; } } @@ -1352,339 +1401,292 @@ struct Command { CommandFunction f; }; -// CompareCommands and CompareCommandNames are for the hash table - -static int CompareCommands(const void* c1, const void* c2) { - return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name); -} - -static int CompareCommandNames(const void* c1, const void* c2) { - return strcmp(((const Command*) c1)->name, (const char*) c2); -} - -// HashString is used to hash command names for the hash table - -static unsigned int HashString(const char *s) { - unsigned int hash = 0; - if (s) { - while (*s) { - hash = hash * 33 + *s++; - } - } - return hash; -} - // args: // - block device (or file) to modify in-place // - transfer list (blob) // - new data stream (filename within package.zip) // - patch stream (filename within package.zip, must be uncompressed) -static Value* PerformBlockImageUpdate(const char* name, State* state, int /* argc */, Expr* argv[], - const Command* commands, size_t cmdcount, bool dryrun) { - CommandParameters params; - memset(¶ms, 0, sizeof(params)); - params.canwrite = !dryrun; - - fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update"); - if (state->is_retry) { - is_retry = true; - fprintf(stderr, "This update is a retry.\n"); - } - - Value* blockdev_filename = nullptr; - Value* transfer_list_value = nullptr; - Value* new_data_fn = nullptr; - Value* patch_data_fn = nullptr; - if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value, - &new_data_fn, &patch_data_fn) < 0) { - return StringValue(strdup("")); - } - std::unique_ptr<Value, decltype(&FreeValue)> blockdev_filename_holder(blockdev_filename, - FreeValue); - std::unique_ptr<Value, decltype(&FreeValue)> transfer_list_value_holder(transfer_list_value, - FreeValue); - std::unique_ptr<Value, decltype(&FreeValue)> new_data_fn_holder(new_data_fn, FreeValue); - std::unique_ptr<Value, decltype(&FreeValue)> patch_data_fn_holder(patch_data_fn, FreeValue); - - if (blockdev_filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", - name); - return StringValue(strdup("")); - } - if (transfer_list_value->type != VAL_BLOB) { - ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name); - return StringValue(strdup("")); - } - if (new_data_fn->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name); - return StringValue(strdup("")); - } - if (patch_data_fn->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", - name); - return StringValue(strdup("")); - } - - UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie); - - if (ui == nullptr) { - return StringValue(strdup("")); - } - - FILE* cmd_pipe = ui->cmd_pipe; - ZipArchive* za = ui->package_zip; - - if (cmd_pipe == nullptr || za == nullptr) { - return StringValue(strdup("")); - } - - const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data); - if (patch_entry == nullptr) { - fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data); - return StringValue(strdup("")); - } - - params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry); - const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data); - if (new_entry == nullptr) { - fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data); - return StringValue(strdup("")); - } - - params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR)); - unique_fd fd_holder(params.fd); +static Value* PerformBlockImageUpdate(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv, + const Command* commands, size_t cmdcount, bool dryrun) { + CommandParameters params = {}; + params.canwrite = !dryrun; + + LOG(INFO) << "performing " << (dryrun ? "verification" : "update"); + if (state->is_retry) { + is_retry = true; + LOG(INFO) << "This update is a retry."; + } + if (argv.size() != 4) { + ErrorAbort(state, kArgsParsingFailure, "block_image_update expects 4 arguments, got %zu", + argv.size()); + return StringValue(""); + } + + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } + + const Value* blockdev_filename = args[0].get(); + const Value* transfer_list_value = args[1].get(); + const Value* new_data_fn = args[2].get(); + const Value* patch_data_fn = args[3].get(); + + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); + return StringValue(""); + } + if (transfer_list_value->type != VAL_BLOB) { + ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name); + return StringValue(""); + } + if (new_data_fn->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name); + return StringValue(""); + } + if (patch_data_fn->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", name); + return StringValue(""); + } + + UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); + if (ui == nullptr) { + return StringValue(""); + } + + FILE* cmd_pipe = ui->cmd_pipe; + ZipArchiveHandle za = ui->package_zip; + + if (cmd_pipe == nullptr || za == nullptr) { + return StringValue(""); + } + + ZipString path_data(patch_data_fn->data.c_str()); + ZipEntry patch_entry; + if (FindEntry(za, path_data, &patch_entry) != 0) { + LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package"; + return StringValue(""); + } + + params.patch_start = ui->package_zip_addr + patch_entry.offset; + ZipString new_data(new_data_fn->data.c_str()); + ZipEntry new_entry; + if (FindEntry(za, new_data, &new_entry) != 0) { + LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package"; + return StringValue(""); + } + + params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data.c_str(), O_RDWR))); + if (params.fd == -1) { + PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed"; + return StringValue(""); + } + + if (params.canwrite) { + params.nti.za = za; + params.nti.entry = new_entry; + + pthread_mutex_init(¶ms.nti.mu, nullptr); + pthread_cond_init(¶ms.nti.cv, nullptr); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); + if (error != 0) { + PLOG(ERROR) << "pthread_create failed"; + return StringValue(""); + } + } + + std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n"); + if (lines.size() < 2) { + ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n", + lines.size()); + return StringValue(""); + } + + // First line in transfer list is the version number. + if (!android::base::ParseInt(lines[0], ¶ms.version, 3, 4)) { + LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]"; + return StringValue(""); + } + + LOG(INFO) << "blockimg version is " << params.version; + + // Second line in transfer list is the total number of blocks we expect to write. + size_t total_blocks; + if (!android::base::ParseUint(lines[1], &total_blocks)) { + ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str()); + return StringValue(""); + } + + if (total_blocks == 0) { + return StringValue("t"); + } + + size_t start = 2; + if (lines.size() < 4) { + ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n", + lines.size()); + return StringValue(""); + } + + // Third line is how many stash entries are needed simultaneously. + LOG(INFO) << "maximum stash entries " << lines[2]; + + // Fourth line is the maximum number of blocks that will be stashed simultaneously + size_t stash_max_blocks; + if (!android::base::ParseUint(lines[3], &stash_max_blocks)) { + ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n", + lines[3].c_str()); + return StringValue(""); + } + + int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase); + if (res == -1) { + return StringValue(""); + } + + params.createdstash = res; + + start += 2; + + // Build a map of the available commands + std::unordered_map<std::string, const Command*> cmd_map; + for (size_t i = 0; i < cmdcount; ++i) { + if (cmd_map.find(commands[i].name) != cmd_map.end()) { + LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map."; + return StringValue(strdup("")); + } + cmd_map[commands[i].name] = &commands[i]; + } + + int rc = -1; + + // Subsequent lines are all individual transfer commands + for (auto it = lines.cbegin() + start; it != lines.cend(); it++) { + const std::string& line(*it); + if (line.empty()) continue; + + params.tokens = android::base::Split(line, " "); + params.cpos = 0; + params.cmdname = params.tokens[params.cpos++].c_str(); + params.cmdline = line.c_str(); + + if (cmd_map.find(params.cmdname) == cmd_map.end()) { + LOG(ERROR) << "unexpected command [" << params.cmdname << "]"; + goto pbiudone; + } + + const Command* cmd = cmd_map[params.cmdname]; - if (params.fd == -1) { - fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno)); - return StringValue(strdup("")); + if (cmd->f != nullptr && cmd->f(params) == -1) { + LOG(ERROR) << "failed to execute command [" << line << "]"; + goto pbiudone; } if (params.canwrite) { - params.nti.za = za; - params.nti.entry = new_entry; - - pthread_mutex_init(¶ms.nti.mu, nullptr); - pthread_cond_init(¶ms.nti.cv, nullptr); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - - int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); - if (error != 0) { - fprintf(stderr, "pthread_create failed: %s\n", strerror(error)); - return StringValue(strdup("")); - } - } - - // Copy all the lines in transfer_list_value into std::string for - // processing. - const std::string transfer_list(transfer_list_value->data, transfer_list_value->size); - std::vector<std::string> lines = android::base::Split(transfer_list, "\n"); - if (lines.size() < 2) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n", - lines.size()); - return StringValue(strdup("")); - } - - // First line in transfer list is the version number - if (!android::base::ParseInt(lines[0].c_str(), ¶ms.version, 1, 4)) { - fprintf(stderr, "unexpected transfer list version [%s]\n", lines[0].c_str()); - return StringValue(strdup("")); - } - - fprintf(stderr, "blockimg version is %d\n", params.version); - - // Second line in transfer list is the total number of blocks we expect to write - int total_blocks; - if (!android::base::ParseInt(lines[1].c_str(), &total_blocks, 0)) { - ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str()); - return StringValue(strdup("")); - } - - if (total_blocks == 0) { - return StringValue(strdup("t")); + if (ota_fsync(params.fd) == -1) { + failure_type = kFsyncFailure; + PLOG(ERROR) << "fsync failed"; + goto pbiudone; + } + fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks); + fflush(cmd_pipe); } + } - size_t start = 2; - if (params.version >= 2) { - if (lines.size() < 4) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n", - lines.size()); - return StringValue(strdup("")); - } - - // Third line is how many stash entries are needed simultaneously - fprintf(stderr, "maximum stash entries %s\n", lines[2].c_str()); - - // Fourth line is the maximum number of blocks that will be stashed simultaneously - int stash_max_blocks; - if (!android::base::ParseInt(lines[3].c_str(), &stash_max_blocks, 0)) { - ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n", - lines[3].c_str()); - return StringValue(strdup("")); - } + if (params.canwrite) { + pthread_join(params.thread, nullptr); - int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase); - if (res == -1) { - return StringValue(strdup("")); - } + LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks; + LOG(INFO) << "stashed " << params.stashed << " blocks"; + LOG(INFO) << "max alloc needed was " << params.buffer.size(); - params.createdstash = res; - - start += 2; + const char* partition = strrchr(blockdev_filename->data.c_str(), '/'); + if (partition != nullptr && *(partition + 1) != 0) { + fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE); + fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE); + fflush(cmd_pipe); } + // Delete stash only after successfully completing the update, as it may contain blocks needed + // to complete the update later. + DeleteStash(params.stashbase); + } else { + LOG(INFO) << "verified partition contents; update may be resumed"; + } - // Build a hash table of the available commands - HashTable* cmdht = mzHashTableCreate(cmdcount, nullptr); - std::unique_ptr<HashTable, decltype(&mzHashTableFree)> cmdht_holder(cmdht, mzHashTableFree); - - for (size_t i = 0; i < cmdcount; ++i) { - unsigned int cmdhash = HashString(commands[i].name); - mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true); - } - - int rc = -1; - - // Subsequent lines are all individual transfer commands - for (auto it = lines.cbegin() + start; it != lines.cend(); it++) { - const std::string& line_str(*it); - if (line_str.empty()) { - continue; - } - - params.tokens = android::base::Split(line_str, " "); - params.cpos = 0; - params.cmdname = params.tokens[params.cpos++].c_str(); - params.cmdline = line_str.c_str(); - - unsigned int cmdhash = HashString(params.cmdname); - const Command* cmd = reinterpret_cast<const Command*>(mzHashTableLookup(cmdht, cmdhash, - const_cast<char*>(params.cmdname), CompareCommandNames, - false)); - - if (cmd == nullptr) { - fprintf(stderr, "unexpected command [%s]\n", params.cmdname); - goto pbiudone; - } - - if (cmd->f != nullptr && cmd->f(params) == -1) { - fprintf(stderr, "failed to execute command [%s]\n", line_str.c_str()); - goto pbiudone; - } - - if (params.canwrite) { - if (ota_fsync(params.fd) == -1) { - failure_type = kFsyncFailure; - fprintf(stderr, "fsync failed: %s\n", strerror(errno)); - goto pbiudone; - } - fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks); - fflush(cmd_pipe); - } - } - - if (params.canwrite) { - pthread_join(params.thread, nullptr); - - fprintf(stderr, "wrote %zu blocks; expected %d\n", params.written, total_blocks); - fprintf(stderr, "stashed %zu blocks\n", params.stashed); - fprintf(stderr, "max alloc needed was %zu\n", params.buffer.size()); - - const char* partition = strrchr(blockdev_filename->data, '/'); - if (partition != nullptr && *(partition+1) != 0) { - fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, - params.written * BLOCKSIZE); - fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, - params.stashed * BLOCKSIZE); - fflush(cmd_pipe); - } - // Delete stash only after successfully completing the update, as it - // may contain blocks needed to complete the update later. - DeleteStash(params.stashbase); - } else { - fprintf(stderr, "verified partition contents; update may be resumed\n"); - } - - rc = 0; + rc = 0; pbiudone: - if (ota_fsync(params.fd) == -1) { - failure_type = kFsyncFailure; - fprintf(stderr, "fsync failed: %s\n", strerror(errno)); - } - // params.fd will be automatically closed because of the fd_holder above. - - // Only delete the stash if the update cannot be resumed, or it's - // a verification run and we created the stash. - if (params.isunresumable || (!params.canwrite && params.createdstash)) { - DeleteStash(params.stashbase); - } - - if (failure_type != kNoCause && state->cause_code == kNoCause) { - state->cause_code = failure_type; - } - - return StringValue(rc == 0 ? strdup("t") : strdup("")); + if (ota_fsync(params.fd) == -1) { + failure_type = kFsyncFailure; + PLOG(ERROR) << "fsync failed"; + } + // params.fd will be automatically closed because it's a unique_fd. + + // Only delete the stash if the update cannot be resumed, or it's a verification run and we + // created the stash. + if (params.isunresumable || (!params.canwrite && params.createdstash)) { + DeleteStash(params.stashbase); + } + + if (failure_type != kNoCause && state->cause_code == kNoCause) { + state->cause_code = failure_type; + } + + return StringValue(rc == 0 ? "t" : ""); } -// The transfer list is a text file containing commands to -// transfer data from one place to another on the target -// partition. We parse it and execute the commands in order: -// -// zero [rangeset] -// - fill the indicated blocks with zeros -// -// new [rangeset] -// - fill the blocks with data read from the new_data file -// -// erase [rangeset] -// - mark the given blocks as empty -// -// move <...> -// bsdiff <patchstart> <patchlen> <...> -// imgdiff <patchstart> <patchlen> <...> -// - read the source blocks, apply a patch (or not in the -// case of move), write result to target blocks. bsdiff or -// imgdiff specifies the type of patch; move means no patch -// at all. -// -// The format of <...> differs between versions 1 and 2; -// see the LoadSrcTgtVersion{1,2}() functions for a -// description of what's expected. -// -// stash <stash_id> <src_range> -// - (version 2+ only) load the given source range and stash -// the data in the given slot of the stash table. -// -// free <stash_id> -// - (version 3+ only) free the given stash data. -// -// The creator of the transfer list will guarantee that no block -// is read (ie, used as the source for a patch or move) after it -// has been written. -// -// In version 2, the creator will guarantee that a given stash is -// loaded (with a stash command) before it's used in a -// move/bsdiff/imgdiff command. -// -// Within one command the source and target ranges may overlap so -// in general we need to read the entire source into memory before -// writing anything to the target blocks. -// -// All the patch data is concatenated into one patch_data file in -// the update package. It must be stored uncompressed because we -// memory-map it in directly from the archive. (Since patches are -// already compressed, we lose very little by not compressing -// their concatenation.) -// -// In version 3, commands that read data from the partition (i.e. -// move/bsdiff/imgdiff/stash) have one or more additional hashes -// before the range parameters, which are used to check if the -// command has already been completed and verify the integrity of -// the source data. - -Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) { +/** + * The transfer list is a text file containing commands to transfer data from one place to another + * on the target partition. We parse it and execute the commands in order: + * + * zero [rangeset] + * - Fill the indicated blocks with zeros. + * + * new [rangeset] + * - Fill the blocks with data read from the new_data file. + * + * erase [rangeset] + * - Mark the given blocks as empty. + * + * move <...> + * bsdiff <patchstart> <patchlen> <...> + * imgdiff <patchstart> <patchlen> <...> + * - Read the source blocks, apply a patch (or not in the case of move), write result to target + * blocks. bsdiff or imgdiff specifies the type of patch; move means no patch at all. + * + * See the comments in LoadSrcTgtVersion3() for a description of the <...> format. + * + * stash <stash_id> <src_range> + * - Load the given source range and stash the data in the given slot of the stash table. + * + * free <stash_id> + * - Free the given stash data. + * + * The creator of the transfer list will guarantee that no block is read (ie, used as the source for + * a patch or move) after it has been written. + * + * The creator will guarantee that a given stash is loaded (with a stash command) before it's used + * in a move/bsdiff/imgdiff command. + * + * Within one command the source and target ranges may overlap so in general we need to read the + * entire source into memory before writing anything to the target blocks. + * + * All the patch data is concatenated into one patch_data file in the update package. It must be + * stored uncompressed because we memory-map it in directly from the archive. (Since patches are + * already compressed, we lose very little by not compressing their concatenation.) + * + * Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more + * additional hashes before the range parameters, which are used to check if the command has already + * been completed and verify the integrity of the source data. + */ +Value* BlockImageVerifyFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { // Commands which are not tested are set to nullptr to skip them completely const Command commands[] = { { "bsdiff", PerformCommandDiff }, @@ -1698,11 +1700,12 @@ Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[] }; // Perform a dry run without writing to test if an update can proceed - return PerformBlockImageUpdate(name, state, argc, argv, commands, + return PerformBlockImageUpdate(name, state, argv, commands, sizeof(commands) / sizeof(commands[0]), true); } -Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) { +Value* BlockImageUpdateFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { const Command commands[] = { { "bsdiff", PerformCommandDiff }, { "erase", PerformCommandErase }, @@ -1714,41 +1717,43 @@ Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[] { "zero", PerformCommandZero } }; - return PerformBlockImageUpdate(name, state, argc, argv, commands, + return PerformBlockImageUpdate(name, state, argv, commands, sizeof(commands) / sizeof(commands[0]), false); } -Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) { - Value* blockdev_filename; - Value* ranges; +Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", + argv.size()); + return StringValue(""); + } - if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) { - return StringValue(strdup("")); + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; } - std::unique_ptr<Value, decltype(&FreeValue)> ranges_holder(ranges, FreeValue); - std::unique_ptr<Value, decltype(&FreeValue)> blockdev_filename_holder(blockdev_filename, - FreeValue); + + const Value* blockdev_filename = args[0].get(); + const Value* ranges = args[1].get(); if (blockdev_filename->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); - return StringValue(strdup("")); + return StringValue(""); } if (ranges->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(strdup("")); + return StringValue(""); } - int fd = open(blockdev_filename->data, O_RDWR); - unique_fd fd_holder(fd); - if (fd < 0) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", blockdev_filename->data, - strerror(errno)); - return StringValue(strdup("")); + android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR)); + if (fd == -1) { + ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", + blockdev_filename->data.c_str(), strerror(errno)); + return StringValue(""); } - RangeSet rs; - parse_range(ranges->data, rs); + RangeSet rs = parse_range(ranges->data); SHA_CTX ctx; SHA1_Init(&ctx); @@ -1756,16 +1761,16 @@ Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) std::vector<uint8_t> buffer(BLOCKSIZE); for (size_t i = 0; i < rs.count; ++i) { if (!check_lseek(fd, (off64_t)rs.pos[i*2] * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data, - strerror(errno)); - return StringValue(strdup("")); + ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", + blockdev_filename->data.c_str(), strerror(errno)); + return StringValue(""); } for (size_t j = rs.pos[i*2]; j < rs.pos[i*2+1]; ++j) { if (read_all(fd, buffer, BLOCKSIZE) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", blockdev_filename->data, - strerror(errno)); - return StringValue(strdup("")); + ErrorAbort(state, kFreadFailure, "failed to read %s: %s", + blockdev_filename->data.c_str(), strerror(errno)); + return StringValue(""); } SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); @@ -1774,7 +1779,7 @@ Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) uint8_t digest[SHA_DIGEST_LENGTH]; SHA1_Final(digest, &ctx); - return StringValue(strdup(print_sha1(digest).c_str())); + return StringValue(print_sha1(digest)); } // This function checks if a device has been remounted R/W prior to an incremental @@ -1782,34 +1787,40 @@ Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) // 1st block of each partition and check for mounting time/count. It return string "t" // if executes successfully and an empty string otherwise. -Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[]) { - Value* arg_filename; +Value* CheckFirstBlockFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu", + argv.size()); + return StringValue(""); + } - if (ReadValueArgs(state, argv, 1, &arg_filename) < 0) { + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { return nullptr; } - std::unique_ptr<Value, decltype(&FreeValue)> filename(arg_filename, FreeValue); - if (filename->type != VAL_STRING) { + const Value* arg_filename = args[0].get(); + + if (arg_filename->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(strdup("")); + return StringValue(""); } - int fd = open(arg_filename->data, O_RDONLY); - unique_fd fd_holder(fd); + android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY)); if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data, + ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(), strerror(errno)); - return StringValue(strdup("")); + return StringValue(""); } RangeSet blk0 {1 /*count*/, 1/*size*/, std::vector<size_t> {0, 1}/*position*/}; std::vector<uint8_t> block0_buffer(BLOCKSIZE); if (ReadBlocks(blk0, block0_buffer, fd) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data, + ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(), strerror(errno)); - return StringValue(strdup("")); + return StringValue(""); } // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout @@ -1827,56 +1838,60 @@ Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[]) uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); } - return StringValue(strdup("t")); + return StringValue("t"); } -Value* BlockImageRecoverFn(const char* name, State* state, int argc, Expr* argv[]) { - Value* arg_filename; - Value* arg_ranges; +Value* BlockImageRecoverFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu", + argv.size()); + return StringValue(""); + } - if (ReadValueArgs(state, argv, 2, &arg_filename, &arg_ranges) < 0) { - return NULL; + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; } - std::unique_ptr<Value, decltype(&FreeValue)> filename(arg_filename, FreeValue); - std::unique_ptr<Value, decltype(&FreeValue)> ranges(arg_ranges, FreeValue); + const Value* filename = args[0].get(); + const Value* ranges = args[1].get(); if (filename->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(strdup("")); + return StringValue(""); } if (ranges->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(strdup("")); + return StringValue(""); } // Output notice to log when recover is attempted - fprintf(stderr, "%s image corrupted, attempting to recover...\n", filename->data); + LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read - fec::io fh(filename->data, O_RDWR); + fec::io fh(filename->data.c_str(), O_RDWR); if (!fh) { - ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data, + ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), strerror(errno)); - return StringValue(strdup("")); + return StringValue(""); } if (!fh.has_ecc() || !fh.has_verity()) { ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors"); - return StringValue(strdup("")); + return StringValue(""); } fec_status status; if (!fh.get_status(status)) { ErrorAbort(state, kLibfecFailure, "failed to read FEC status"); - return StringValue(strdup("")); + return StringValue(""); } - RangeSet rs; - parse_range(ranges->data, rs); + RangeSet rs = parse_range(ranges->data); uint8_t buffer[BLOCKSIZE]; @@ -1889,8 +1904,8 @@ Value* BlockImageRecoverFn(const char* name, State* state, int argc, Expr* argv[ if (fh.pread(buffer, BLOCKSIZE, (off64_t)j * BLOCKSIZE) != BLOCKSIZE) { ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", - filename->data, j, strerror(errno)); - return StringValue(strdup("")); + filename->data.c_str(), j, strerror(errno)); + return StringValue(""); } // If we want to be able to recover from a situation where rewriting a corrected @@ -1905,8 +1920,8 @@ Value* BlockImageRecoverFn(const char* name, State* state, int argc, Expr* argv[ // read and check if the errors field value has increased. } } - fprintf(stderr, "...%s image recovered successfully.\n", filename->data); - return StringValue(strdup("t")); + LOG(INFO) << "..." << filename->data << " image recovered successfully."; + return StringValue("t"); } void RegisterBlockImageFunctions() { diff --git a/updater/blockimg.h b/updater/include/updater/blockimg.h index 2f4ad3c04..2f4ad3c04 100644 --- a/updater/blockimg.h +++ b/updater/include/updater/blockimg.h diff --git a/updater/install.h b/updater/include/updater/install.h index 70e343404..8d6ca4728 100644 --- a/updater/install.h +++ b/updater/include/updater/install.h @@ -17,11 +17,12 @@ #ifndef _UPDATER_INSTALL_H_ #define _UPDATER_INSTALL_H_ +struct State; + void RegisterInstallFunctions(); // uiPrintf function prints msg to screen as well as logs -void uiPrintf(State* state, const char* format, ...); - -static int make_parents(char* name); +void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) + __attribute__((__format__(printf, 2, 3))); #endif diff --git a/updater/updater.h b/updater/include/updater/updater.h index d1dfdd05e..f4a2fe874 100644 --- a/updater/updater.h +++ b/updater/include/updater/updater.h @@ -18,20 +18,18 @@ #define _UPDATER_UPDATER_H_ #include <stdio.h> -#include "minzip/Zip.h" - -#include <selinux/selinux.h> -#include <selinux/label.h> +#include <ziparchive/zip_archive.h> typedef struct { FILE* cmd_pipe; - ZipArchive* package_zip; + ZipArchiveHandle package_zip; int version; uint8_t* package_zip_addr; size_t package_zip_len; } UpdaterInfo; +struct selabel_handle; extern struct selabel_handle *sehandle; #endif diff --git a/updater/install.cpp b/updater/install.cpp index 74feb56be..84cf5d6c0 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -14,1336 +14,749 @@ * limitations under the License. */ +#include "updater/install.h" + #include <ctype.h> #include <errno.h> +#include <fcntl.h> +#include <ftw.h> +#include <inttypes.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/capability.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> -#include <unistd.h> -#include <fcntl.h> -#include <time.h> -#include <selinux/selinux.h> -#include <ftw.h> -#include <sys/capability.h> #include <sys/xattr.h> -#include <linux/xattr.h> -#include <inttypes.h> +#include <time.h> +#include <unistd.h> +#include <utime.h> #include <memory> #include <string> #include <vector> #include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parsedouble.h> #include <android-base/parseint.h> -#include <android-base/strings.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <applypatch/applypatch.h> +#include <bootloader_message/bootloader_message.h> +#include <cutils/android_reboot.h> +#include <ext4_utils/make_ext4fs.h> +#include <ext4_utils/wipe.h> +#include <openssl/sha.h> +#include <selinux/label.h> +#include <selinux/selinux.h> +#include <ziparchive/zip_archive.h> -#include "bootloader.h" -#include "applypatch/applypatch.h" -#include "cutils/android_reboot.h" -#include "cutils/misc.h" -#include "cutils/properties.h" #include "edify/expr.h" #include "error_code.h" -#include "minzip/DirUtil.h" -#include "mtdutils/mounts.h" -#include "mtdutils/mtdutils.h" -#include "openssl/sha.h" +#include "mounts.h" #include "ota_io.h" -#include "updater.h" -#include "install.h" +#include "otautil/DirUtil.h" +#include "otautil/ZipUtil.h" +#include "print_sha1.h" #include "tune2fs.h" - -#ifdef USE_EXT4 -#include "make_ext4fs.h" -#include "wipe.h" -#endif +#include "updater/updater.h" // Send over the buffer to recovery though the command pipe. static void uiPrint(State* state, const std::string& buffer) { - UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie); - - // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". - // So skip sending empty strings to UI. - std::vector<std::string> lines = android::base::Split(buffer, "\n"); - for (auto& line: lines) { - if (!line.empty()) { - fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str()); - fprintf(ui->cmd_pipe, "ui_print\n"); - } + UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); + + // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". + // So skip sending empty strings to UI. + std::vector<std::string> lines = android::base::Split(buffer, "\n"); + for (auto& line : lines) { + if (!line.empty()) { + fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str()); } + } - // On the updater side, we need to dump the contents to stderr (which has - // been redirected to the log file). Because the recovery will only print - // the contents to screen when processing pipe command ui_print. - fprintf(stderr, "%s", buffer.c_str()); + // On the updater side, we need to dump the contents to stderr (which has + // been redirected to the log file). Because the recovery will only print + // the contents to screen when processing pipe command ui_print. + LOG(INFO) << buffer; } -__attribute__((__format__(printf, 2, 3))) __nonnull((2)) -void uiPrintf(State* state, const char* format, ...) { - std::string error_msg; +void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) { + std::string error_msg; - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&error_msg, format, ap); - va_end(ap); + va_list ap; + va_start(ap, format); + android::base::StringAppendV(&error_msg, format, ap); + va_end(ap); - uiPrint(state, error_msg); + uiPrint(state, error_msg); } -// Take a sha-1 digest and return it as a newly-allocated hex string. -char* PrintSha1(const uint8_t* digest) { - char* buffer = reinterpret_cast<char*>(malloc(SHA_DIGEST_LENGTH*2 + 1)); - const char* alphabet = "0123456789abcdef"; - size_t i; - for (i = 0; i < SHA_DIGEST_LENGTH; ++i) { - buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf]; - buffer[i*2+1] = alphabet[digest[i] & 0xf]; - } - buffer[i*2] = '\0'; - return buffer; +static bool is_dir(const std::string& dirpath) { + struct stat st; + return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode); } -// mount(fs_type, partition_type, location, mount_point) -// -// fs_type="yaffs2" partition_type="MTD" location=partition -// fs_type="ext4" partition_type="EMMC" location=device -Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 4 && argc != 5) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %d", name, argc); - } - char* fs_type; - char* partition_type; - char* location; - char* mount_point; - char* mount_options; - bool has_mount_options; - if (argc == 5) { - has_mount_options = true; - if (ReadArgs(state, argv, 5, &fs_type, &partition_type, - &location, &mount_point, &mount_options) < 0) { - return NULL; - } - } else { - has_mount_options = false; - if (ReadArgs(state, argv, 4, &fs_type, &partition_type, - &location, &mount_point) < 0) { - return NULL; - } - } - - if (strlen(fs_type) == 0) { - ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name); - goto done; - } - if (strlen(partition_type) == 0) { - ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty", - name); - goto done; - } - if (strlen(location) == 0) { - ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name); - goto done; - } - if (strlen(mount_point) == 0) { - ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty", - name); - goto done; - } - - { - char *secontext = NULL; - - if (sehandle) { - selabel_lookup(sehandle, &secontext, mount_point, 0755); - setfscreatecon(secontext); - } - - mkdir(mount_point, 0755); - - if (secontext) { - freecon(secontext); - setfscreatecon(NULL); - } - } +// Create all parent directories of name, if necessary. +static bool make_parents(const std::string& name) { + size_t prev_end = 0; + while (prev_end < name.size()) { + size_t next_end = name.find('/', prev_end + 1); + if (next_end == std::string::npos) { + break; + } + std::string dir_path = name.substr(0, next_end); + if (!is_dir(dir_path)) { + int result = mkdir(dir_path.c_str(), 0700); + if (result != 0) { + PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name; + return false; + } + + LOG(INFO) << "created [" << dir_path << "]"; + } + prev_end = next_end; + } + return true; +} - if (strcmp(partition_type, "MTD") == 0) { - mtd_scan_partitions(); - const MtdPartition* mtd; - mtd = mtd_find_partition_by_name(location); - if (mtd == NULL) { - uiPrintf(state, "%s: no mtd partition named \"%s\"\n", - name, location); - result = strdup(""); - goto done; - } - if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) { - uiPrintf(state, "mtd mount of %s failed: %s\n", - location, strerror(errno)); - result = strdup(""); - goto done; - } - result = mount_point; - } else { - if (mount(location, mount_point, fs_type, - MS_NOATIME | MS_NODEV | MS_NODIRATIME, - has_mount_options ? mount_options : "") < 0) { - uiPrintf(state, "%s: failed to mount %s at %s: %s\n", - name, location, mount_point, strerror(errno)); - result = strdup(""); - } else { - result = mount_point; - } - } +// mount(fs_type, partition_type, location, mount_point) +// mount(fs_type, partition_type, location, mount_point, mount_options) -done: - free(fs_type); - free(partition_type); - free(location); - if (result != mount_point) free(mount_point); - if (has_mount_options) free(mount_options); - return StringValue(result); +// fs_type="ext4" partition_type="EMMC" location=device +Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 4 && argv.size() != 5) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& fs_type = args[0]; + const std::string& partition_type = args[1]; + const std::string& location = args[2]; + const std::string& mount_point = args[3]; + std::string mount_options; + + if (argv.size() == 5) { + mount_options = args[4]; + } + + if (fs_type.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name); + } + if (partition_type.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty", + name); + } + if (location.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name); + } + if (mount_point.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty", + name); + } + + { + char* secontext = nullptr; + + if (sehandle) { + selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755); + setfscreatecon(secontext); + } + + mkdir(mount_point.c_str(), 0755); + + if (secontext) { + freecon(secontext); + setfscreatecon(nullptr); + } + } + + if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(), + MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) { + uiPrintf(state, "%s: failed to mount %s at %s: %s\n", name, location.c_str(), + mount_point.c_str(), strerror(errno)); + return StringValue(""); + } + + return StringValue(mount_point); } - // is_mounted(mount_point) -Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } - char* mount_point; - if (ReadArgs(state, argv, 1, &mount_point) < 0) { - return NULL; - } - if (strlen(mount_point) == 0) { - ErrorAbort(state, kArgsParsingFailure, "mount_point argument to unmount() can't be empty"); - goto done; - } - - scan_mounted_volumes(); - { - const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); - if (vol == NULL) { - result = strdup(""); - } else { - result = mount_point; - } - } - -done: - if (result != mount_point) free(mount_point); - return StringValue(result); +Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& mount_point = args[0]; + if (mount_point.empty()) { + return ErrorAbort(state, kArgsParsingFailure, + "mount_point argument to unmount() can't be empty"); + } + + scan_mounted_volumes(); + MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str()); + if (vol == nullptr) { + return StringValue(""); + } + + return StringValue(mount_point); } - -Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } - char* mount_point; - if (ReadArgs(state, argv, 1, &mount_point) < 0) { - return NULL; - } - if (strlen(mount_point) == 0) { - ErrorAbort(state, kArgsParsingFailure, "mount_point argument to unmount() can't be empty"); - goto done; - } - - scan_mounted_volumes(); - { - const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point); - if (vol == NULL) { - uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point); - result = strdup(""); - } else { - int ret = unmount_mounted_volume(vol); - if (ret != 0) { - uiPrintf(state, "unmount of %s failed (%d): %s\n", - mount_point, ret, strerror(errno)); - } - result = mount_point; - } - } - -done: - if (result != mount_point) free(mount_point); - return StringValue(result); +Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& mount_point = args[0]; + if (mount_point.empty()) { + return ErrorAbort(state, kArgsParsingFailure, + "mount_point argument to unmount() can't be empty"); + } + + scan_mounted_volumes(); + MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str()); + if (vol == nullptr) { + uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point.c_str()); + return nullptr; + } else { + int ret = unmount_mounted_volume(vol); + if (ret != 0) { + uiPrintf(state, "unmount of %s failed (%d): %s\n", mount_point.c_str(), ret, strerror(errno)); + } + } + + return StringValue(mount_point); } static int exec_cmd(const char* path, char* const argv[]) { - int status; - pid_t child; - if ((child = vfork()) == 0) { - execv(path, argv); - _exit(-1); - } - waitpid(child, &status, 0); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - printf("%s failed with status %d\n", path, WEXITSTATUS(status)); - } - return WEXITSTATUS(status); + pid_t child; + if ((child = vfork()) == 0) { + execv(path, argv); + _exit(EXIT_FAILURE); + } + + int status; + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status); + } + return WEXITSTATUS(status); } - // format(fs_type, partition_type, location, fs_size, mount_point) // -// fs_type="yaffs2" partition_type="MTD" location=partition fs_size=<bytes> mount_point=<location> -// fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> -// fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> +// fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> +// fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location> // if fs_size == 0, then make fs uses the entire partition. // if fs_size > 0, that is the size to use // if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") -Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 5) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %d", name, argc); - } - char* fs_type; - char* partition_type; - char* location; - char* fs_size; - char* mount_point; - - if (ReadArgs(state, argv, 5, &fs_type, &partition_type, &location, &fs_size, &mount_point) < 0) { - return NULL; - } - - if (strlen(fs_type) == 0) { - ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name); - goto done; - } - if (strlen(partition_type) == 0) { - ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty", - name); - goto done; - } - if (strlen(location) == 0) { - ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name); - goto done; - } - - if (strlen(mount_point) == 0) { - ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty", - name); - goto done; - } - - if (strcmp(partition_type, "MTD") == 0) { - mtd_scan_partitions(); - const MtdPartition* mtd = mtd_find_partition_by_name(location); - if (mtd == NULL) { - printf("%s: no mtd partition named \"%s\"", - name, location); - result = strdup(""); - goto done; - } - MtdWriteContext* ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - printf("%s: can't write \"%s\"", name, location); - result = strdup(""); - goto done; - } - if (mtd_erase_blocks(ctx, -1) == -1) { - mtd_write_close(ctx); - printf("%s: failed to erase \"%s\"", name, location); - result = strdup(""); - goto done; - } - if (mtd_write_close(ctx) != 0) { - printf("%s: failed to close \"%s\"", name, location); - result = strdup(""); - goto done; - } - result = location; -#ifdef USE_EXT4 - } else if (strcmp(fs_type, "ext4") == 0) { - int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle); - if (status != 0) { - printf("%s: make_ext4fs failed (%d) on %s", - name, status, location); - result = strdup(""); - goto done; - } - result = location; - } else if (strcmp(fs_type, "f2fs") == 0) { - char *num_sectors; - if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) { - printf("format_volume: failed to create %s command for %s\n", fs_type, location); - result = strdup(""); - goto done; - } - const char *f2fs_path = "/sbin/mkfs.f2fs"; - const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL}; - int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv); - free(num_sectors); - if (status != 0) { - printf("%s: mkfs.f2fs failed (%d) on %s", - name, status, location); - result = strdup(""); - goto done; - } - result = location; -#endif - } else { - printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"", - name, fs_type, partition_type); - } - -done: - free(fs_type); - free(partition_type); - if (result != location) free(location); - return StringValue(result); +Value* FormatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 5) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& fs_type = args[0]; + const std::string& partition_type = args[1]; + const std::string& location = args[2]; + const std::string& fs_size = args[3]; + const std::string& mount_point = args[4]; + + if (fs_type.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name); + } + if (partition_type.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty", + name); + } + if (location.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name); + } + if (mount_point.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty", + name); + } + + int64_t size; + if (!android::base::ParseInt(fs_size, &size)) { + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name, + fs_size.c_str()); + } + + if (fs_type == "ext4") { + int status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle); + if (status != 0) { + LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location; + return StringValue(""); + } + return StringValue(location); + } else if (fs_type == "f2fs") { + if (size < 0) { + LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size; + return StringValue(""); + } + std::string num_sectors = std::to_string(size / 512); + + const char* f2fs_path = "/sbin/mkfs.f2fs"; + const char* const f2fs_argv[] = { "mkfs.f2fs", "-t", "-d1", location.c_str(), + num_sectors.c_str(), nullptr }; + int status = exec_cmd(f2fs_path, const_cast<char* const*>(f2fs_argv)); + if (status != 0) { + LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location; + return StringValue(""); + } + return StringValue(location); + } else { + LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \"" + << partition_type << "\""; + } + + return nullptr; } -Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } - - char* src_name; - char* dst_name; - - if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) { - return NULL; - } - if (strlen(src_name) == 0) { - ErrorAbort(state, kArgsParsingFailure, "src_name argument to %s() can't be empty", name); - goto done; - } - if (strlen(dst_name) == 0) { - ErrorAbort(state, kArgsParsingFailure, "dst_name argument to %s() can't be empty", name); - goto done; - } - if (make_parents(dst_name) != 0) { - ErrorAbort(state, kFileRenameFailure, "Creating parent of %s failed, error %s", - dst_name, strerror(errno)); - } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) { - // File was already moved - result = dst_name; - } else if (rename(src_name, dst_name) != 0) { - ErrorAbort(state, kFileRenameFailure, "Rename of %s to %s failed, error %s", - src_name, dst_name, strerror(errno)); - } else { - result = dst_name; - } - -done: - free(src_name); - if (result != dst_name) free(dst_name); - return StringValue(result); +Value* ShowProgressFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& frac_str = args[0]; + const std::string& sec_str = args[1]; + + double frac; + if (!android::base::ParseDouble(frac_str.c_str(), &frac)) { + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name, + frac_str.c_str()); + } + int sec; + if (!android::base::ParseInt(sec_str.c_str(), &sec)) { + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name, + sec_str.c_str()); + } + + UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); + fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); + + return StringValue(frac_str); } -Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) { - char** paths = reinterpret_cast<char**>(malloc(argc * sizeof(char*))); - for (int i = 0; i < argc; ++i) { - paths[i] = Evaluate(state, argv[i]); - if (paths[i] == NULL) { - for (int j = 0; j < i; ++j) { - free(paths[j]); - } - free(paths); - return NULL; - } - } - - bool recursive = (strcmp(name, "delete_recursive") == 0); - - int success = 0; - for (int i = 0; i < argc; ++i) { - if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0) - ++success; - free(paths[i]); - } - free(paths); +Value* SetProgressFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } - char buffer[10]; - sprintf(buffer, "%d", success); - return StringValue(strdup(buffer)); -} - - -Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } - char* frac_str; - char* sec_str; - if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) { - return NULL; - } + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& frac_str = args[0]; - double frac = strtod(frac_str, NULL); - int sec; - android::base::ParseInt(sec_str, &sec); + double frac; + if (!android::base::ParseDouble(frac_str.c_str(), &frac)) { + return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name, + frac_str.c_str()); + } - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec); + UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); + fprintf(ui->cmd_pipe, "set_progress %f\n", frac); - free(sec_str); - return StringValue(frac_str); + return StringValue(frac_str); } -Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } - char* frac_str; - if (ReadArgs(state, argv, 1, &frac_str) < 0) { - return NULL; - } - - double frac = strtod(frac_str, NULL); - - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - fprintf(ui->cmd_pipe, "set_progress %f\n", frac); - - return StringValue(frac_str); -} - -// package_extract_dir(package_path, destination_path) +// package_extract_dir(package_dir, dest_dir) +// Extracts all files from the package underneath package_dir and writes them to the +// corresponding tree beneath dest_dir. Any existing files are overwritten. +// Example: package_extract_dir("system", "/system") +// +// Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path. Value* PackageExtractDirFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } - char* zip_path; - char* dest_path; - if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; - - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; + const std::vector<std::unique_ptr<Expr>>&argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } - // To create a consistent system image, never use the clock for timestamps. - struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& zip_path = args[0]; + const std::string& dest_path = args[1]; - bool success = mzExtractRecursive(za, zip_path, dest_path, - ×tamp, - NULL, NULL, sehandle); - free(zip_path); - free(dest_path); - return StringValue(strdup(success ? "t" : "")); -} - - -// package_extract_file(package_path, destination_path) -// or -// package_extract_file(package_path) -// to return the entire contents of the file as the result of this -// function (the char* returned is actually a FileContents*). -Value* PackageExtractFileFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc < 1 || argc > 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %d", - name, argc); - } - bool success = false; + ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - if (argc == 2) { - // The two-argument version extracts to a file. + // To create a consistent system image, never use the clock for timestamps. + constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; - - char* zip_path; - char* dest_path; - if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL; - - const ZipEntry* entry = mzFindZipEntry(za, zip_path); - if (entry == NULL) { - printf("%s: no %s in package\n", name, zip_path); - goto done2; - } - - { - int fd = TEMP_FAILURE_RETRY(ota_open(dest_path, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, - S_IRUSR | S_IWUSR)); - if (fd == -1) { - printf("%s: can't open %s for write: %s\n", name, dest_path, strerror(errno)); - goto done2; - } - success = mzExtractZipEntryToFile(za, entry, fd); - if (ota_fsync(fd) == -1) { - printf("fsync of \"%s\" failed: %s\n", dest_path, strerror(errno)); - success = false; - } - if (ota_close(fd) == -1) { - printf("close of \"%s\" failed: %s\n", dest_path, strerror(errno)); - success = false; - } - } + bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp, sehandle); - done2: - free(zip_path); - free(dest_path); - return StringValue(strdup(success ? "t" : "")); - } else { - // The one-argument version returns the contents of the file - // as the result. - - char* zip_path; - if (ReadArgs(state, argv, 1, &zip_path) < 0) return NULL; - - Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value))); - v->type = VAL_BLOB; - v->size = -1; - v->data = NULL; - - ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip; - const ZipEntry* entry = mzFindZipEntry(za, zip_path); - if (entry == NULL) { - printf("%s: no %s in package\n", name, zip_path); - goto done1; - } - - v->size = mzGetZipEntryUncompLen(entry); - v->data = reinterpret_cast<char*>(malloc(v->size)); - if (v->data == NULL) { - printf("%s: failed to allocate %ld bytes for %s\n", - name, (long)v->size, zip_path); - goto done1; - } - - success = mzExtractZipEntryToBuffer(za, entry, - (unsigned char *)v->data); - - done1: - free(zip_path); - if (!success) { - free(v->data); - v->data = NULL; - v->size = -1; - } - return v; - } -} - -// Create all parent directories of name, if necessary. -static int make_parents(char* name) { - char* p; - for (p = name + (strlen(name)-1); p > name; --p) { - if (*p != '/') continue; - *p = '\0'; - if (make_parents(name) < 0) return -1; - int result = mkdir(name, 0700); - if (result == 0) printf("created [%s]\n", name); - *p = '/'; - if (result == 0 || errno == EEXIST) { - // successfully created or already existed; we're done - return 0; - } else { - printf("failed to mkdir %s: %s\n", name, strerror(errno)); - return -1; - } - } - return 0; -} - -// symlink target src1 src2 ... -// unlinks any previously existing src1, src2, etc before creating symlinks. -Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc == 0) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1+ args, got %d", name, argc); - } - char* target; - target = Evaluate(state, argv[0]); - if (target == NULL) return NULL; - - char** srcs = ReadVarArgs(state, argc-1, argv+1); - if (srcs == NULL) { - free(target); - return NULL; - } - - int bad = 0; - int i; - for (i = 0; i < argc-1; ++i) { - if (unlink(srcs[i]) < 0) { - if (errno != ENOENT) { - printf("%s: failed to remove %s: %s\n", - name, srcs[i], strerror(errno)); - ++bad; - } - } - if (make_parents(srcs[i])) { - printf("%s: failed to symlink %s to %s: making parents failed\n", - name, srcs[i], target); - ++bad; - } - if (symlink(target, srcs[i]) < 0) { - printf("%s: failed to symlink %s to %s: %s\n", - name, srcs[i], target, strerror(errno)); - ++bad; - } - free(srcs[i]); - } - free(srcs); - if (bad) { - return ErrorAbort(state, kSymlinkFailure, "%s: some symlinks failed", name); - } - return StringValue(strdup("")); -} - -struct perm_parsed_args { - bool has_uid; - uid_t uid; - bool has_gid; - gid_t gid; - bool has_mode; - mode_t mode; - bool has_fmode; - mode_t fmode; - bool has_dmode; - mode_t dmode; - bool has_selabel; - char* selabel; - bool has_capabilities; - uint64_t capabilities; -}; - -static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) { - int i; - struct perm_parsed_args parsed; - int bad = 0; - static int max_warnings = 20; - - memset(&parsed, 0, sizeof(parsed)); - - for (i = 1; i < argc; i += 2) { - if (strcmp("uid", args[i]) == 0) { - int64_t uid; - if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) { - parsed.uid = uid; - parsed.has_uid = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("gid", args[i]) == 0) { - int64_t gid; - if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) { - parsed.gid = gid; - parsed.has_gid = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("mode", args[i]) == 0) { - int32_t mode; - if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { - parsed.mode = mode; - parsed.has_mode = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("dmode", args[i]) == 0) { - int32_t mode; - if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { - parsed.dmode = mode; - parsed.has_dmode = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("fmode", args[i]) == 0) { - int32_t mode; - if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) { - parsed.fmode = mode; - parsed.has_fmode = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("capabilities", args[i]) == 0) { - int64_t capabilities; - if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) { - parsed.capabilities = capabilities; - parsed.has_capabilities = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (strcmp("selabel", args[i]) == 0) { - if (args[i+1][0] != '\0') { - parsed.selabel = args[i+1]; - parsed.has_selabel = true; - } else { - uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]); - bad++; - } - continue; - } - if (max_warnings != 0) { - printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]); - max_warnings--; - if (max_warnings == 0) { - printf("ParsedPermArgs: suppressing further warnings\n"); - } - } - } - return parsed; + return StringValue(success ? "t" : ""); } -static int ApplyParsedPerms( - State * state, - const char* filename, - const struct stat *statptr, - struct perm_parsed_args parsed) -{ - int bad = 0; - - if (parsed.has_selabel) { - if (lsetfilecon(filename, parsed.selabel) != 0) { - uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", - filename, parsed.selabel, strerror(errno)); - bad++; - } - } - - /* ignore symlinks */ - if (S_ISLNK(statptr->st_mode)) { - return bad; - } +// package_extract_file(package_file[, dest_file]) +// Extracts a single package_file from the update package and writes it to dest_file, +// overwriting existing files if necessary. Without the dest_file argument, returns the +// contents of the package file as a binary blob. +Value* PackageExtractFileFn(const char* name, State* state, + const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1 || argv.size() > 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name, + argv.size()); + } - if (parsed.has_uid) { - if (chown(filename, parsed.uid, -1) < 0) { - uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n", - filename, parsed.uid, strerror(errno)); - bad++; - } - } + if (argv.size() == 2) { + // The two-argument version extracts to a file. - if (parsed.has_gid) { - if (chown(filename, -1, parsed.gid) < 0) { - uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", - filename, parsed.gid, strerror(errno)); - bad++; - } + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, + argv.size()); } + const std::string& zip_path = args[0]; + const std::string& dest_path = args[1]; - if (parsed.has_mode) { - if (chmod(filename, parsed.mode) < 0) { - uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.mode, strerror(errno)); - bad++; - } + ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; + ZipString zip_string_path(zip_path.c_str()); + ZipEntry entry; + if (FindEntry(za, zip_string_path, &entry) != 0) { + LOG(ERROR) << name << ": no " << zip_path << " in package"; + return StringValue(""); } - if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { - if (chmod(filename, parsed.dmode) < 0) { - uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.dmode, strerror(errno)); - bad++; - } + unique_fd fd(TEMP_FAILURE_RETRY( + ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); + if (fd == -1) { + PLOG(ERROR) << name << ": can't open " << dest_path << " for write"; + return StringValue(""); } - if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { - if (chmod(filename, parsed.fmode) < 0) { - uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", - filename, parsed.fmode, strerror(errno)); - bad++; - } + bool success = true; + int32_t ret = ExtractEntryToFile(za, &entry, fd); + if (ret != 0) { + LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" (" + << entry.uncompressed_length << " bytes) to \"" << dest_path + << "\": " << ErrorCodeString(ret); + success = false; } - - if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { - if (parsed.capabilities == 0) { - if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) { - // Report failure unless it's ENODATA (attribute not set) - uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", - filename, parsed.capabilities, strerror(errno)); - bad++; - } - } else { - struct vfs_cap_data cap_data; - memset(&cap_data, 0, sizeof(cap_data)); - cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE; - cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff); - cap_data.data[0].inheritable = 0; - cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32); - cap_data.data[1].inheritable = 0; - if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) { - uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", - filename, parsed.capabilities, strerror(errno)); - bad++; - } - } + if (ota_fsync(fd) == -1) { + PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed"; + success = false; } - - return bad; -} - -// nftw doesn't allow us to pass along context, so we need to use -// global variables. *sigh* -static struct perm_parsed_args recursive_parsed_args; -static State* recursive_state; - -static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr, - int fileflags, struct FTW *pfwt) { - return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args); -} - -static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) { - int bad = 0; - struct stat sb; - Value* result = NULL; - - bool recursive = (strcmp(name, "set_metadata_recursive") == 0); - - if ((argc % 2) != 1) { - return ErrorAbort(state, kArgsParsingFailure, - "%s() expects an odd number of arguments, got %d", name, argc); + if (ota_close(fd) == -1) { + PLOG(ERROR) << "close of \"" << dest_path << "\" failed"; + success = false; } - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) return NULL; - - if (lstat(args[0], &sb) == -1) { - result = ErrorAbort(state, kSetMetadataFailure, "%s: Error on lstat of \"%s\": %s", - name, args[0], strerror(errno)); - goto done; - } + return StringValue(success ? "t" : ""); + } else { + // The one-argument version returns the contents of the file as the result. - { - struct perm_parsed_args parsed = ParsePermArgs(state, argc, args); - - if (recursive) { - recursive_parsed_args = parsed; - recursive_state = state; - bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); - memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); - recursive_state = NULL; - } else { - bad += ApplyParsedPerms(state, args[0], &sb, parsed); - } + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, + argv.size()); } + const std::string& zip_path = args[0]; -done: - for (int i = 0; i < argc; ++i) { - free(args[i]); + ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; + ZipString zip_string_path(zip_path.c_str()); + ZipEntry entry; + if (FindEntry(za, zip_string_path, &entry) != 0) { + return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name, + zip_path.c_str()); } - free(args); - if (result != NULL) { - return result; - } + std::string buffer; + buffer.resize(entry.uncompressed_length); - if (bad > 0) { - return ErrorAbort(state, kSetMetadataFailure, "%s: some changes failed", name); + int32_t ret = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size()); + if (ret != 0) { + return ErrorAbort(state, kPackageExtractFileFailure, + "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name, + zip_path.c_str(), buffer.size(), ErrorCodeString(ret)); } - return StringValue(strdup("")); + return new Value(VAL_BLOB, buffer); + } } -Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } - char* key = Evaluate(state, argv[0]); - if (key == NULL) return NULL; - - char value[PROPERTY_VALUE_MAX]; - property_get(key, value, ""); - free(key); - - return StringValue(strdup(value)); +Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } + std::string key; + if (!Evaluate(state, argv[0], &key)) { + return nullptr; + } + std::string value = android::base::GetProperty(key, ""); + + return StringValue(value); } // file_getprop(file, key) // // interprets 'file' as a getprop-style file (key=value pairs, one -// per line. # comment lines,blank lines, lines without '=' ignored), +// per line. # comment lines, blank lines, lines without '=' ignored), // and returns the value for 'key' (or "" if it isn't defined). -Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - char* buffer = NULL; - char* filename; - char* key; - if (ReadArgs(state, argv, 2, &filename, &key) < 0) { - return NULL; - } - - struct stat st; - if (stat(filename, &st) < 0) { - ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name, filename, - strerror(errno)); - goto done; - } - -#define MAX_FILE_GETPROP_SIZE 65536 - - if (st.st_size > MAX_FILE_GETPROP_SIZE) { - ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %d)", filename, name, - MAX_FILE_GETPROP_SIZE); - goto done; - } - - buffer = reinterpret_cast<char*>(malloc(st.st_size+1)); - if (buffer == NULL) { - ErrorAbort(state, kFileGetPropFailure, "%s: failed to alloc %lld bytes", name, - (long long)st.st_size+1); - goto done; - } - - FILE* f; - f = fopen(filename, "rb"); - if (f == NULL) { - ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename, - strerror(errno)); - goto done; - } - - if (ota_fread(buffer, 1, st.st_size, f) != static_cast<size_t>(st.st_size)) { - ErrorAbort(state, kFreadFailure, "%s: failed to read %lld bytes from %s", - name, (long long)st.st_size+1, filename); - fclose(f); - goto done; - } - buffer[st.st_size] = '\0'; - - fclose(f); - - char* line; - line = strtok(buffer, "\n"); - do { - // skip whitespace at start of line - while (*line && isspace(*line)) ++line; - - // comment or blank line: skip to next line - if (*line == '\0' || *line == '#') continue; - - char* equal = strchr(line, '='); - if (equal == NULL) { - continue; - } - - // trim whitespace between key and '=' - char* key_end = equal-1; - while (key_end > line && isspace(*key_end)) --key_end; - key_end[1] = '\0'; - - // not the key we're looking for - if (strcmp(key, line) != 0) continue; - - // skip whitespace after the '=' to the start of the value - char* val_start = equal+1; - while(*val_start && isspace(*val_start)) ++val_start; - - // trim trailing whitespace - char* val_end = val_start + strlen(val_start)-1; - while (val_end > val_start && isspace(*val_end)) --val_end; - val_end[1] = '\0'; - - result = strdup(val_start); - break; - - } while ((line = strtok(NULL, "\n"))); - - if (result == NULL) result = strdup(""); - - done: - free(filename); - free(key); - free(buffer); - return StringValue(result); -} - -// write_raw_image(filename_or_blob, partition) -Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) { - char* result = NULL; - - Value* partition_value; - Value* contents; - if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) { - return NULL; - } - - char* partition = NULL; - if (partition_value->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "partition argument to %s must be string", name); - goto done; - } - partition = partition_value->data; - if (strlen(partition) == 0) { - ErrorAbort(state, kArgsParsingFailure, "partition argument to %s can't be empty", name); - goto done; - } - if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) { - ErrorAbort(state, kArgsParsingFailure, "file argument to %s can't be empty", name); - goto done; - } - - mtd_scan_partitions(); - const MtdPartition* mtd; - mtd = mtd_find_partition_by_name(partition); - if (mtd == NULL) { - printf("%s: no mtd partition named \"%s\"\n", name, partition); - result = strdup(""); - goto done; - } - - MtdWriteContext* ctx; - ctx = mtd_write_partition(mtd); - if (ctx == NULL) { - printf("%s: can't write mtd partition \"%s\"\n", - name, partition); - result = strdup(""); - goto done; - } - - bool success; - - if (contents->type == VAL_STRING) { - // we're given a filename as the contents - char* filename = contents->data; - FILE* f = ota_fopen(filename, "rb"); - if (f == NULL) { - printf("%s: can't open %s: %s\n", name, filename, strerror(errno)); - result = strdup(""); - goto done; - } - - success = true; - char* buffer = reinterpret_cast<char*>(malloc(BUFSIZ)); - int read; - while (success && (read = ota_fread(buffer, 1, BUFSIZ, f)) > 0) { - int wrote = mtd_write_data(ctx, buffer, read); - success = success && (wrote == read); - } - free(buffer); - ota_fclose(f); - } else { - // we're given a blob as the contents - ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size); - success = (wrote == contents->size); - } - if (!success) { - printf("mtd_write_data to %s failed: %s\n", - partition, strerror(errno)); - } - - if (mtd_erase_blocks(ctx, -1) == -1) { - printf("%s: error erasing blocks of %s\n", name, partition); - } - if (mtd_write_close(ctx) != 0) { - printf("%s: error closing write of %s\n", name, partition); - } - - printf("%s %s partition\n", - success ? "wrote" : "failed to write", partition); - - result = success ? partition : strdup(""); - -done: - if (result != partition) FreeValue(partition_value); - FreeValue(contents); - return StringValue(result); +Value* FileGetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + const std::string& key = args[1]; + + struct stat st; + if (stat(filename.c_str(), &st) < 0) { + return ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name, + filename.c_str(), strerror(errno)); + } + + constexpr off_t MAX_FILE_GETPROP_SIZE = 65536; + if (st.st_size > MAX_FILE_GETPROP_SIZE) { + return ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %lld)", + filename.c_str(), name, static_cast<long long>(MAX_FILE_GETPROP_SIZE)); + } + + std::string buffer(st.st_size, '\0'); + unique_file f(ota_fopen(filename.c_str(), "rb")); + if (f == nullptr) { + return ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename.c_str(), + strerror(errno)); + } + + if (ota_fread(&buffer[0], 1, st.st_size, f.get()) != static_cast<size_t>(st.st_size)) { + ErrorAbort(state, kFreadFailure, "%s: failed to read %zu bytes from %s", name, + static_cast<size_t>(st.st_size), filename.c_str()); + return nullptr; + } + + ota_fclose(f); + + std::vector<std::string> lines = android::base::Split(buffer, "\n"); + for (size_t i = 0; i < lines.size(); i++) { + std::string line = android::base::Trim(lines[i]); + + // comment or blank line: skip to next line + if (line.empty() || line[0] == '#') { + continue; + } + size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + continue; + } + + // trim whitespace between key and '=' + std::string str = android::base::Trim(line.substr(0, equal_pos)); + + // not the key we're looking for + if (key != str) continue; + + return StringValue(android::base::Trim(line.substr(equal_pos + 1))); + } + + return StringValue(""); } // apply_patch_space(bytes) -Value* ApplyPatchSpaceFn(const char* name, State* state, - int argc, Expr* argv[]) { - char* bytes_str; - if (ReadArgs(state, argv, 1, &bytes_str) < 0) { - return NULL; - } - - size_t bytes; - if (!android::base::ParseUint(bytes_str, &bytes)) { - ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n", - name, bytes_str); - free(bytes_str); - return nullptr; - } - - return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t")); +Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name, + argv.size()); + } + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& bytes_str = args[0]; + + size_t bytes; + if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n", + name, bytes_str.c_str()); + } + + return StringValue(CacheSizeCheck(bytes) ? "" : "t"); } -// apply_patch(file, size, init_sha1, tgt_sha1, patch) - -Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc < 6 || (argc % 2) == 1) { +// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...]) +// Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the +// same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1 +// hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a +// 40-character hex string) and a blob. The blob is the patch to be applied when the source +// file's current contents have the given SHA1. +// +// The patching is done in a safe manner that guarantees the target file either has the desired +// SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate +// state. If the process is interrupted during patching, the target file may be in an intermediate +// state; a copy exists in the cache partition so restarting the update can successfully update +// the file. +Value* ApplyPatchFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 6 || (argv.size() % 2) == 1) { return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 6 args and an " - "even number, got %d", name, argc); + "even number, got %zu", name, argv.size()); } - char* source_filename; - char* target_filename; - char* target_sha1; - char* target_size_str; - if (ReadArgs(state, argv, 4, &source_filename, &target_filename, - &target_sha1, &target_size_str) < 0) { - return NULL; + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args, 0, 4)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); } + const std::string& source_filename = args[0]; + const std::string& target_filename = args[1]; + const std::string& target_sha1 = args[2]; + const std::string& target_size_str = args[3]; size_t target_size; - if (!android::base::ParseUint(target_size_str, &target_size)) { - ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", - name, target_size_str); - free(source_filename); - free(target_filename); - free(target_sha1); - free(target_size_str); - return nullptr; + if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", + name, target_size_str.c_str()); } - int patchcount = (argc-4) / 2; - std::unique_ptr<Value*, decltype(&free)> arg_values(ReadValueVarArgs(state, argc-4, argv+4), - free); - if (!arg_values) { + int patchcount = (argv.size()-4) / 2; + std::vector<std::unique_ptr<Value>> arg_values; + if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) { return nullptr; } - std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patch_shas; - std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patches; - // Protect values by unique_ptrs first to get rid of memory leak. - for (int i = 0; i < patchcount * 2; i += 2) { - patch_shas.emplace_back(arg_values.get()[i], FreeValue); - patches.emplace_back(arg_values.get()[i+1], FreeValue); - } for (int i = 0; i < patchcount; ++i) { - if (patch_shas[i]->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i); - return nullptr; + if (arg_values[i * 2]->type != VAL_STRING) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, + i * 2); } - if (patches[i]->type != VAL_BLOB) { - ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i); - return nullptr; + if (arg_values[i * 2 + 1]->type != VAL_BLOB) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, + i * 2 + 1); } } - std::vector<char*> patch_sha_str; - std::vector<Value*> patch_ptrs; + std::vector<std::string> patch_sha_str; + std::vector<std::unique_ptr<Value>> patches; for (int i = 0; i < patchcount; ++i) { - patch_sha_str.push_back(patch_shas[i]->data); - patch_ptrs.push_back(patches[i].get()); + patch_sha_str.push_back(arg_values[i * 2]->data); + patches.push_back(std::move(arg_values[i * 2 + 1])); } - int result = applypatch(source_filename, target_filename, - target_sha1, target_size, - patchcount, patch_sha_str.data(), patch_ptrs.data(), NULL); + int result = applypatch(source_filename.c_str(), target_filename.c_str(), + target_sha1.c_str(), target_size, + patch_sha_str, patches, nullptr); - return StringValue(strdup(result == 0 ? "t" : "")); + return StringValue(result == 0 ? "t" : ""); } -// apply_patch_check(file, [sha1_1, ...]) -Value* ApplyPatchCheckFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %d", - name, argc); - } - - char* filename; - if (ReadArgs(state, argv, 1, &filename) < 0) { - return NULL; - } - - int patchcount = argc-1; - char** sha1s = ReadVarArgs(state, argc-1, argv+1); - - int result = applypatch_check(filename, patchcount, sha1s); - - int i; - for (i = 0; i < patchcount; ++i) { - free(sha1s[i]); - } - free(sha1s); - - return StringValue(strdup(result == 0 ? "t" : "")); +// apply_patch_check(filename, [sha1, ...]) +// Returns true if the contents of filename or the temporary copy in the cache partition (if +// present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are +// specified as 40 hex digits. This function differs from sha1_check(read_file(filename), +// sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will +// succeed even if the file was corrupted by an interrupted apply_patch() update. +Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args, 0, 1)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + + std::vector<std::string> sha1s; + if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + int result = applypatch_check(filename.c_str(), sha1s); + + return StringValue(result == 0 ? "t" : ""); } // This is the updater side handler for ui_print() in edify script. Contents // will be sent over to the recovery side for on-screen display. -Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) { - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) { - return NULL; - } - - std::string buffer; - for (int i = 0; i < argc; ++i) { - buffer += args[i]; - free(args[i]); - } - free(args); - - buffer += "\n"; - uiPrint(state, buffer); - return StringValue(strdup(buffer.c_str())); +Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + + std::string buffer = android::base::Join(args, "") + "\n"; + uiPrint(state, buffer); + return StringValue(buffer); } -Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 0) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %d", name, argc); - } - fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "wipe_cache\n"); - return StringValue(strdup("t")); +Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (!argv.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, + argv.size()); + } + fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n"); + return StringValue("t"); } -Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); - } - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) { - return NULL; - } - - char** args2 = reinterpret_cast<char**>(malloc(sizeof(char*) * (argc+1))); - memcpy(args2, args, sizeof(char*) * argc); - args2[argc] = NULL; - - printf("about to run program [%s] with %d args\n", args2[0], argc); - - pid_t child = fork(); - if (child == 0) { - execv(args2[0], args2); - printf("run_program: execv failed: %s\n", strerror(errno)); - _exit(1); - } - int status; - waitpid(child, &status, 0); - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - printf("run_program: child exited with status %d\n", - WEXITSTATUS(status)); - } - } else if (WIFSIGNALED(status)) { - printf("run_program: child terminated by signal %d\n", - WTERMSIG(status)); - } - - int i; - for (i = 0; i < argc; ++i) { - free(args[i]); - } - free(args); - free(args2); - - char buffer[20]; - sprintf(buffer, "%d", status); - - return StringValue(strdup(buffer)); +Value* RunProgramFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + + char* args2[argv.size() + 1]; + for (size_t i = 0; i < argv.size(); i++) { + args2[i] = &args[i][0]; + } + args2[argv.size()] = nullptr; + + LOG(INFO) << "about to run program [" << args2[0] << "] with " << argv.size() << " args"; + + pid_t child = fork(); + if (child == 0) { + execv(args2[0], args2); + PLOG(ERROR) << "run_program: execv failed"; + _exit(EXIT_FAILURE); + } + + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status); + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status); + } + + return StringValue(std::to_string(status)); } // sha1_check(data) @@ -1354,104 +767,92 @@ Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) { // returns the sha1 of the file if it matches any of the hex // strings passed, or "" if it does not equal any of them. // -Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); - } - - std::unique_ptr<Value*, decltype(&free)> arg_values(ReadValueVarArgs(state, argc, argv), free); - if (arg_values == nullptr) { - return nullptr; - } - std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> args; - for (int i = 0; i < argc; ++i) { - args.emplace_back(arg_values.get()[i], FreeValue); - } - - if (args[0]->size < 0) { - return StringValue(strdup("")); - } - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast<uint8_t*>(args[0]->data), args[0]->size, digest); - - if (argc == 1) { - return StringValue(PrintSha1(digest)); - } - - int i; +Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() < 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); + } + + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } + + if (args[0]->type == VAL_INVALID) { + return StringValue(""); + } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest); + + if (argv.size() == 1) { + return StringValue(print_sha1(digest)); + } + + for (size_t i = 1; i < argv.size(); ++i) { uint8_t arg_digest[SHA_DIGEST_LENGTH]; - for (i = 1; i < argc; ++i) { - if (args[i]->type != VAL_STRING) { - printf("%s(): arg %d is not a string; skipping", - name, i); - } else if (ParseSha1(args[i]->data, arg_digest) != 0) { - // Warn about bad args and skip them. - printf("%s(): error parsing \"%s\" as sha-1; skipping", - name, args[i]->data); - } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) { - break; - } - } - if (i >= argc) { - // Didn't match any of the hex strings; return false. - return StringValue(strdup("")); - } - // Found a match. - return args[i].release(); + if (args[i]->type != VAL_STRING) { + LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping"; + } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) { + // Warn about bad args and skip them. + LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping"; + } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) { + // Found a match. + return args[i].release(); + } + } + + // Didn't match any of the hex strings; return false. + return StringValue(""); } // Read a local file and return its contents (the Value* returned // is actually a FileContents*). -Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } - char* filename; - if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; +Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } - Value* v = static_cast<Value*>(malloc(sizeof(Value))); - if (v == nullptr) { - return nullptr; - } + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + + Value* v = new Value(VAL_INVALID, ""); + + FileContents fc; + if (LoadFileContents(filename.c_str(), &fc) == 0) { v->type = VAL_BLOB; - v->size = -1; - v->data = nullptr; - - FileContents fc; - if (LoadFileContents(filename, &fc) == 0) { - v->data = static_cast<char*>(malloc(fc.data.size())); - if (v->data != nullptr) { - memcpy(v->data, fc.data.data(), fc.data.size()); - v->size = fc.data.size(); - } - } - free(filename); - return v; + v->data = std::string(fc.data.begin(), fc.data.end()); + } + return v; } // write_value(value, filename) // Writes 'value' to 'filename'. // Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq") -Value* WriteValueFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } - - char* value; - char* filename; - if (ReadArgs(state, argv, 2, &value, &filename) < 0) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", - name); - } - - bool ret = android::base::WriteStringToFile(value, filename); - if (!ret) { - printf("%s: Failed to write to \"%s\": %s\n", name, filename, strerror(errno)); - } - - free(value); - free(filename); - return StringValue(strdup(ret ? "t" : "")); +Value* WriteValueFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); + } + + const std::string& filename = args[1]; + if (filename.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Filename cannot be empty", name); + } + + const std::string& value = args[0]; + if (!android::base::WriteStringToFile(value, filename)) { + PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\""; + return StringValue(""); + } else { + return StringValue("t"); + } } // Immediately reboot the device. Recovery is not finished normally, @@ -1463,36 +864,40 @@ Value* WriteValueFn(const char* name, State* state, int argc, Expr* argv[]) { // property. It can be "recovery" to boot from the recovery // partition, or "" (empty string) to boot from the regular boot // partition. -Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } - - char* filename; - char* property; - if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL; - - char buffer[80]; - - // zero out the 'command' field of the bootloader message. - memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); - FILE* f = fopen(filename, "r+b"); - fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); - ota_fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); - fclose(f); - free(filename); - - strcpy(buffer, "reboot,"); - if (property != NULL) { - strncat(buffer, property, sizeof(buffer)-10); - } - - property_set(ANDROID_RB_PROPERTY, buffer); - - sleep(5); - free(property); - ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); - return NULL; +Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + const std::string& property = args[1]; + + // Zero out the 'command' field of the bootloader message. Leave the rest intact. + bootloader_message boot; + std::string err; + if (!read_bootloader_message_from(&boot, filename, &err)) { + LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err; + return StringValue(""); + } + memset(boot.command, 0, sizeof(boot.command)); + if (!write_bootloader_message_to(boot, filename, &err)) { + LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err; + return StringValue(""); + } + + std::string reboot_cmd = "reboot," + property; + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + reboot_cmd += ",quiescent"; + } + android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd); + + sleep(5); + return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); } // Store a string value somewhere that future invocations of recovery @@ -1505,166 +910,157 @@ Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { // ("/misc" in the fstab), which is where this value is stored. The // second argument is the string to store; it should not exceed 31 // bytes. -Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } - - char* filename; - char* stagestr; - if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL; - - // Store this value in the misc partition, immediately after the - // bootloader message that the main recovery uses to save its - // arguments in case of the device restarting midway through - // package installation. - FILE* f = fopen(filename, "r+b"); - fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); - int to_write = strlen(stagestr)+1; - int max_size = sizeof(((struct bootloader_message*)0)->stage); - if (to_write > max_size) { - to_write = max_size; - stagestr[max_size-1] = 0; - } - ota_fwrite(stagestr, to_write, 1, f); - fclose(f); - - free(stagestr); - return StringValue(filename); +Value* SetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + const std::string& stagestr = args[1]; + + // Store this value in the misc partition, immediately after the + // bootloader message that the main recovery uses to save its + // arguments in case of the device restarting midway through + // package installation. + bootloader_message boot; + std::string err; + if (!read_bootloader_message_from(&boot, filename, &err)) { + LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err; + return StringValue(""); + } + strlcpy(boot.stage, stagestr.c_str(), sizeof(boot.stage)); + if (!write_bootloader_message_to(boot, filename, &err)) { + LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err; + return StringValue(""); + } + + return StringValue(filename); } // Return the value most recently saved with SetStageFn. The argument // is the block device for the misc partition. -Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } - - char* filename; - if (ReadArgs(state, argv, 1, &filename) < 0) return NULL; - - char buffer[sizeof(((struct bootloader_message*)0)->stage)]; - FILE* f = fopen(filename, "rb"); - fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); - ota_fread(buffer, sizeof(buffer), 1, f); - fclose(f); - buffer[sizeof(buffer)-1] = '\0'; +Value* GetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + + bootloader_message boot; + std::string err; + if (!read_bootloader_message_from(&boot, filename, &err)) { + LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err; + return StringValue(""); + } + + return StringValue(boot.stage); +} - return StringValue(strdup(buffer)); +Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + const std::string& len_str = args[1]; + + size_t len; + if (!android::base::ParseUint(len_str.c_str(), &len)) { + return nullptr; + } + unique_fd fd(ota_open(filename.c_str(), O_WRONLY, 0644)); + // The wipe_block_device function in ext4_utils returns 0 on success and 1 + // for failure. + int status = wipe_block_device(fd, len); + return StringValue((status == 0) ? "t" : ""); } -Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } +Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (!argv.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, + argv.size()); + } + UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie); + fprintf(ui->cmd_pipe, "enable_reboot\n"); + return StringValue("t"); +} - char* filename; - char* len_str; - if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL; +Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { + if (argv.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size()); + } + + std::vector<std::string> args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name); + } + + char* args2[argv.size() + 1]; + // Tune2fs expects the program name as its args[0] + args2[0] = const_cast<char*>(name); + if (args2[0] == nullptr) { + return nullptr; + } + for (size_t i = 0; i < argv.size(); ++i) { + args2[i + 1] = &args[i][0]; + } + + // tune2fs changes the file system parameters on an ext2 file system; it + // returns 0 on success. + int result = tune2fs_main(argv.size() + 1, args2); + if (result != 0) { + return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result); + } + return StringValue("t"); +} - size_t len; - android::base::ParseUint(len_str, &len); - int fd = ota_open(filename, O_WRONLY, 0644); - int success = wipe_block_device(fd, len); +void RegisterInstallFunctions() { + RegisterFunction("mount", MountFn); + RegisterFunction("is_mounted", IsMountedFn); + RegisterFunction("unmount", UnmountFn); + RegisterFunction("format", FormatFn); + RegisterFunction("show_progress", ShowProgressFn); + RegisterFunction("set_progress", SetProgressFn); + RegisterFunction("package_extract_dir", PackageExtractDirFn); + RegisterFunction("package_extract_file", PackageExtractFileFn); - free(filename); - free(len_str); + RegisterFunction("getprop", GetPropFn); + RegisterFunction("file_getprop", FileGetPropFn); - ota_close(fd); + RegisterFunction("apply_patch", ApplyPatchFn); + RegisterFunction("apply_patch_check", ApplyPatchCheckFn); + RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); - return StringValue(strdup(success ? "t" : "")); -} + RegisterFunction("wipe_block_device", WipeBlockDeviceFn); -Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 0) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %d", name, argc); - } - UpdaterInfo* ui = (UpdaterInfo*)(state->cookie); - fprintf(ui->cmd_pipe, "enable_reboot\n"); - return StringValue(strdup("t")); -} + RegisterFunction("read_file", ReadFileFn); + RegisterFunction("sha1_check", Sha1CheckFn); + RegisterFunction("write_value", WriteValueFn); -Value* Tune2FsFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc == 0) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %d", name, argc); - } + RegisterFunction("wipe_cache", WipeCacheFn); - char** args = ReadVarArgs(state, argc, argv); - if (args == NULL) { - return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name); - } + RegisterFunction("ui_print", UIPrintFn); - char** args2 = reinterpret_cast<char**>(malloc(sizeof(char*) * (argc+1))); - // Tune2fs expects the program name as its args[0] - args2[0] = strdup(name); - for (int i = 0; i < argc; ++i) { - args2[i + 1] = args[i]; - } - int result = tune2fs_main(argc + 1, args2); - for (int i = 0; i < argc; ++i) { - free(args[i]); - } - free(args); + RegisterFunction("run_program", RunProgramFn); - free(args2[0]); - free(args2); - if (result != 0) { - return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", - name, result); - } - return StringValue(strdup("t")); -} + RegisterFunction("reboot_now", RebootNowFn); + RegisterFunction("get_stage", GetStageFn); + RegisterFunction("set_stage", SetStageFn); -void RegisterInstallFunctions() { - RegisterFunction("mount", MountFn); - RegisterFunction("is_mounted", IsMountedFn); - RegisterFunction("unmount", UnmountFn); - RegisterFunction("format", FormatFn); - RegisterFunction("show_progress", ShowProgressFn); - RegisterFunction("set_progress", SetProgressFn); - RegisterFunction("delete", DeleteFn); - RegisterFunction("delete_recursive", DeleteFn); - RegisterFunction("package_extract_dir", PackageExtractDirFn); - RegisterFunction("package_extract_file", PackageExtractFileFn); - RegisterFunction("symlink", SymlinkFn); - - // Usage: - // set_metadata("filename", "key1", "value1", "key2", "value2", ...) - // Example: - // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); - RegisterFunction("set_metadata", SetMetadataFn); - - // Usage: - // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...) - // Example: - // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); - RegisterFunction("set_metadata_recursive", SetMetadataFn); - - RegisterFunction("getprop", GetPropFn); - RegisterFunction("file_getprop", FileGetPropFn); - RegisterFunction("write_raw_image", WriteRawImageFn); - - RegisterFunction("apply_patch", ApplyPatchFn); - RegisterFunction("apply_patch_check", ApplyPatchCheckFn); - RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); - - RegisterFunction("wipe_block_device", WipeBlockDeviceFn); - - RegisterFunction("read_file", ReadFileFn); - RegisterFunction("sha1_check", Sha1CheckFn); - RegisterFunction("rename", RenameFn); - RegisterFunction("write_value", WriteValueFn); - - RegisterFunction("wipe_cache", WipeCacheFn); - - RegisterFunction("ui_print", UIPrintFn); - - RegisterFunction("run_program", RunProgramFn); - - RegisterFunction("reboot_now", RebootNowFn); - RegisterFunction("get_stage", GetStageFn); - RegisterFunction("set_stage", SetStageFn); - - RegisterFunction("enable_reboot", EnableRebootFn); - RegisterFunction("tune2fs", Tune2FsFn); + RegisterFunction("enable_reboot", EnableRebootFn); + RegisterFunction("tune2fs", Tune2FsFn); } diff --git a/updater/updater.cpp b/updater/updater.cpp index e956dd557..f3e282044 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -14,18 +14,28 @@ * limitations under the License. */ +#include "updater/updater.h" + #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> -#include "edify/expr.h" -#include "updater.h" -#include "install.h" -#include "blockimg.h" -#include "minzip/Zip.h" -#include "minzip/SysUtil.h" +#include <string> + +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <selinux/android.h> +#include <selinux/label.h> +#include <selinux/selinux.h> +#include <ziparchive/zip_archive.h> + #include "config.h" +#include "edify/expr.h" +#include "otautil/DirUtil.h" +#include "otautil/SysUtil.h" +#include "updater/blockimg.h" +#include "updater/install.h" // Generated by the makefile, this function defines the // RegisterDeviceExtensions() function, which calls all the @@ -34,170 +44,176 @@ // Where in the package we expect to find the edify script to execute. // (Note it's "updateR-script", not the older "update-script".) -#define SCRIPT_NAME "META-INF/com/google/android/updater-script" +static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script"; extern bool have_eio_error; struct selabel_handle *sehandle; -int main(int argc, char** argv) { - // Various things log information to stdout or stderr more or less - // at random (though we've tried to standardize on stdout). The - // log file makes more sense if buffering is turned off so things - // appear in the right order. - setbuf(stdout, NULL); - setbuf(stderr, NULL); - - if (argc != 4 && argc != 5) { - printf("unexpected number of arguments (%d)\n", argc); - return 1; - } - - char* version = argv[1]; - if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || - version[1] != '\0') { - // We support version 1, 2, or 3. - printf("wrong updater binary API; expected 1, 2, or 3; " - "got %s\n", - argv[1]); - return 2; - } - - // Set up the pipe for sending commands back to the parent process. - - int fd = atoi(argv[2]); - FILE* cmd_pipe = fdopen(fd, "wb"); - setlinebuf(cmd_pipe); - - // Extract the script from the package. - - const char* package_filename = argv[3]; - MemMapping map; - if (sysMapFile(package_filename, &map) != 0) { - printf("failed to map package %s\n", argv[3]); - return 3; - } - ZipArchive za; - int err; - err = mzOpenZipArchive(map.addr, map.length, &za); - if (err != 0) { - printf("failed to open package %s: %s\n", - argv[3], strerror(err)); - return 3; - } - ota_io_init(&za); - - const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME); - if (script_entry == NULL) { - printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename); - return 4; - } - - char* script = reinterpret_cast<char*>(malloc(script_entry->uncompLen+1)); - if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) { - printf("failed to read script from package\n"); - return 5; - } - script[script_entry->uncompLen] = '\0'; - - // Configure edify's functions. - - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - RegisterDeviceExtensions(); - FinishRegistration(); - - // Parse the script. +static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */, + const char* /* tag */, const char* /* file */, unsigned int /* line */, + const char* message) { + fprintf(stdout, "%s\n", message); +} - Expr* root; - int error_count = 0; - int error = parse_string(script, &root, &error_count); - if (error != 0 || error_count > 0) { - printf("%d parse errors\n", error_count); - return 6; +int main(int argc, char** argv) { + // Various things log information to stdout or stderr more or less + // at random (though we've tried to standardize on stdout). The + // log file makes more sense if buffering is turned off so things + // appear in the right order. + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + // We don't have logcat yet under recovery. Update logs will always be written to stdout + // (which is redirected to recovery.log). + android::base::InitLogging(argv, &UpdaterLogger); + + if (argc != 4 && argc != 5) { + LOG(ERROR) << "unexpected number of arguments: " << argc; + return 1; + } + + char* version = argv[1]; + if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') { + // We support version 1, 2, or 3. + LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1]; + return 2; + } + + // Set up the pipe for sending commands back to the parent process. + + int fd = atoi(argv[2]); + FILE* cmd_pipe = fdopen(fd, "wb"); + setlinebuf(cmd_pipe); + + // Extract the script from the package. + + const char* package_filename = argv[3]; + MemMapping map; + if (sysMapFile(package_filename, &map) != 0) { + LOG(ERROR) << "failed to map package " << argv[3]; + return 3; + } + ZipArchiveHandle za; + int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za); + if (open_err != 0) { + LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err); + CloseArchive(za); + return 3; + } + + ZipString script_name(SCRIPT_NAME); + ZipEntry script_entry; + int find_err = FindEntry(za, script_name, &script_entry); + if (find_err != 0) { + LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": " + << ErrorCodeString(find_err); + CloseArchive(za); + return 4; + } + + std::string script; + script.resize(script_entry.uncompressed_length); + int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]), + script_entry.uncompressed_length); + if (extract_err != 0) { + LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err); + CloseArchive(za); + return 5; + } + + // Configure edify's functions. + + RegisterBuiltins(); + RegisterInstallFunctions(); + RegisterBlockImageFunctions(); + RegisterDeviceExtensions(); + + // Parse the script. + + std::unique_ptr<Expr> root; + int error_count = 0; + int error = parse_string(script.c_str(), &root, &error_count); + if (error != 0 || error_count > 0) { + LOG(ERROR) << error_count << " parse errors"; + CloseArchive(za); + return 6; + } + + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + + if (!sehandle) { + fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); + } + + // Evaluate the parsed script. + + UpdaterInfo updater_info; + updater_info.cmd_pipe = cmd_pipe; + updater_info.package_zip = za; + updater_info.version = atoi(version); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + State state(script, &updater_info); + + if (argc == 5) { + if (strcmp(argv[4], "retry") == 0) { + state.is_retry = true; + } else { + printf("unexpected argument: %s", argv[4]); } + } + ota_io_init(za, state.is_retry); - struct selinux_opt seopts[] = { - { SELABEL_OPT_PATH, "/file_contexts" } - }; - - sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + std::string result; + bool status = Evaluate(&state, root, &result); - if (!sehandle) { - fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n"); - } + if (have_eio_error) { + fprintf(cmd_pipe, "retry_update\n"); + } - // Evaluate the parsed script. - - UpdaterInfo updater_info; - updater_info.cmd_pipe = cmd_pipe; - updater_info.package_zip = &za; - updater_info.version = atoi(version); - updater_info.package_zip_addr = map.addr; - updater_info.package_zip_len = map.length; - - State state; - state.cookie = &updater_info; - state.script = script; - state.errmsg = NULL; - - if (argc == 5) { - if (strcmp(argv[4], "retry") == 0) { - state.is_retry = true; - } else { - printf("unexpected argument: %s", argv[4]); + if (!status) { + if (state.errmsg.empty()) { + LOG(ERROR) << "script aborted (no error message)"; + fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); + } else { + LOG(ERROR) << "script aborted: " << state.errmsg; + const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n"); + for (const std::string& line : lines) { + // Parse the error code in abort message. + // Example: "E30: This package is for bullhead devices." + if (!line.empty() && line[0] == 'E') { + if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) { + LOG(ERROR) << "Failed to parse error code: [" << line << "]"; + } } + fprintf(cmd_pipe, "ui_print %s\n", line.c_str()); + } } - char* result = Evaluate(&state, root); - - if (have_eio_error) { - fprintf(cmd_pipe, "retry_update\n"); - } - - if (result == NULL) { - if (state.errmsg == NULL) { - printf("script aborted (no error message)\n"); - fprintf(cmd_pipe, "ui_print script aborted (no error message)\n"); - } else { - printf("script aborted: %s\n", state.errmsg); - char* line = strtok(state.errmsg, "\n"); - while (line) { - // Parse the error code in abort message. - // Example: "E30: This package is for bullhead devices." - if (*line == 'E') { - if (sscanf(line, "E%u: ", &state.error_code) != 1) { - printf("Failed to parse error code: [%s]\n", line); - } - } - fprintf(cmd_pipe, "ui_print %s\n", line); - line = strtok(NULL, "\n"); - } - fprintf(cmd_pipe, "ui_print\n"); - } - - if (state.error_code != kNoError) { - fprintf(cmd_pipe, "log error: %d\n", state.error_code); - // Cause code should provide additional information about the abort; - // report only when an error exists. - if (state.cause_code != kNoCause) { - fprintf(cmd_pipe, "log cause: %d\n", state.cause_code); - } - } - - free(state.errmsg); - return 7; - } else { - fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result); - free(result); + if (state.error_code != kNoError) { + fprintf(cmd_pipe, "log error: %d\n", state.error_code); + // Cause code should provide additional information about the abort; + // report only when an error exists. + if (state.cause_code != kNoCause) { + fprintf(cmd_pipe, "log cause: %d\n", state.cause_code); + } } if (updater_info.package_zip) { - mzCloseZipArchive(updater_info.package_zip); + CloseArchive(updater_info.package_zip); } - sysReleaseMap(&map); - free(script); + return 7; + } else { + fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str()); + } + + if (updater_info.package_zip) { + CloseArchive(updater_info.package_zip); + } + sysReleaseMap(&map); - return 0; + return 0; } diff --git a/verifier.cpp b/verifier.cpp index 2d1b0e74f..2ef9c4c37 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -14,24 +14,25 @@ * limitations under the License. */ +#include "verifier.h" + #include <errno.h> -#include <malloc.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <algorithm> +#include <functional> #include <memory> +#include <vector> +#include <android-base/logging.h> +#include <openssl/bn.h> #include <openssl/ecdsa.h> #include <openssl/obj_mac.h> #include "asn1_decoder.h" -#include "common.h" #include "print_sha1.h" -#include "ui.h" -#include "verifier.h" - -extern RecoveryUI* ui; static constexpr size_t MiB = 1024 * 1024; @@ -60,255 +61,246 @@ static constexpr size_t MiB = 1024 * 1024; * SEQUENCE (SignatureAlgorithmIdentifier) * OCTET STRING (SignatureValue) */ -static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der, - size_t* sig_der_length) { - asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len); - if (ctx == NULL) { - return false; - } +static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len, + std::vector<uint8_t>* sig_der) { + CHECK(sig_der != nullptr); + sig_der->clear(); - asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx); - if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) { - asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq); - if (signed_data_app != NULL) { - asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app); - if (signed_data_seq != NULL - && asn1_sequence_next(signed_data_seq) - && asn1_sequence_next(signed_data_seq) - && asn1_sequence_next(signed_data_seq) - && asn1_constructed_skip_all(signed_data_seq)) { - asn1_context_t *sig_set = asn1_set_get(signed_data_seq); - if (sig_set != NULL) { - asn1_context_t* sig_seq = asn1_sequence_get(sig_set); - if (sig_seq != NULL - && asn1_sequence_next(sig_seq) - && asn1_sequence_next(sig_seq) - && asn1_sequence_next(sig_seq) - && asn1_sequence_next(sig_seq)) { - uint8_t* sig_der_ptr; - if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) { - *sig_der = (uint8_t*) malloc(*sig_der_length); - if (*sig_der != NULL) { - memcpy(*sig_der, sig_der_ptr, *sig_der_length); - } - } - asn1_context_free(sig_seq); - } - asn1_context_free(sig_set); - } - asn1_context_free(signed_data_seq); - } - asn1_context_free(signed_data_app); - } - asn1_context_free(pkcs7_seq); - } - asn1_context_free(ctx); + asn1_context ctx(pkcs7_der, pkcs7_der_len); - return *sig_der != NULL; -} + std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get()); + if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) { + return false; + } -// Look for an RSA signature embedded in the .ZIP file comment given -// the path to the zip. Verify it matches one of the given public -// keys. -// -// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered -// or no key matches the signature). - -int verify_file(unsigned char* addr, size_t length, - const std::vector<Certificate>& keys) { - ui->SetProgress(0.0); - - // An archive with a whole-file signature will end in six bytes: - // - // (2-byte signature start) $ff $ff (2-byte comment size) - // - // (As far as the ZIP format is concerned, these are part of the - // archive comment.) We start by reading this footer, this tells - // us how far back from the end we have to start reading to find - // the whole comment. + std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get()); + if (signed_data_app == nullptr) { + return false; + } -#define FOOTER_SIZE 6 + std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get()); + if (signed_data_seq == nullptr || + !signed_data_seq->asn1_sequence_next() || + !signed_data_seq->asn1_sequence_next() || + !signed_data_seq->asn1_sequence_next() || + !signed_data_seq->asn1_constructed_skip_all()) { + return false; + } - if (length < FOOTER_SIZE) { - LOGE("not big enough to contain footer\n"); - return VERIFY_FAILURE; - } + std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get()); + if (sig_set == nullptr) { + return false; + } - unsigned char* footer = addr + length - FOOTER_SIZE; + std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get()); + if (sig_seq == nullptr || + !sig_seq->asn1_sequence_next() || + !sig_seq->asn1_sequence_next() || + !sig_seq->asn1_sequence_next() || + !sig_seq->asn1_sequence_next()) { + return false; + } - if (footer[2] != 0xff || footer[3] != 0xff) { - LOGE("footer is wrong\n"); - return VERIFY_FAILURE; - } + const uint8_t* sig_der_ptr; + size_t sig_der_length; + if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) { + return false; + } - size_t comment_size = footer[4] + (footer[5] << 8); - size_t signature_start = footer[0] + (footer[1] << 8); - LOGI("comment is %zu bytes; signature %zu bytes from end\n", - comment_size, signature_start); + sig_der->resize(sig_der_length); + std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin()); + return true; +} - if (signature_start > comment_size) { - LOGE("signature start: %zu is larger than comment size: %zu\n", signature_start, - comment_size); - return VERIFY_FAILURE; - } +/* + * Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. Verifies + * that it matches one of the given public keys. A callback function can be optionally provided for + * posting the progress. + * + * Returns VERIFY_SUCCESS or VERIFY_FAILURE (if any error is encountered or no key matches the + * signature). + */ +int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys, + const std::function<void(float)>& set_progress) { + if (set_progress) { + set_progress(0.0); + } - if (signature_start <= FOOTER_SIZE) { - LOGE("Signature start is in the footer"); - return VERIFY_FAILURE; - } + // An archive with a whole-file signature will end in six bytes: + // + // (2-byte signature start) $ff $ff (2-byte comment size) + // + // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by + // reading this footer, this tells us how far back from the end we have to start reading to find + // the whole comment. -#define EOCD_HEADER_SIZE 22 +#define FOOTER_SIZE 6 - // The end-of-central-directory record is 22 bytes plus any - // comment length. - size_t eocd_size = comment_size + EOCD_HEADER_SIZE; + if (length < FOOTER_SIZE) { + LOG(ERROR) << "not big enough to contain footer"; + return VERIFY_FAILURE; + } - if (length < eocd_size) { - LOGE("not big enough to contain EOCD\n"); - return VERIFY_FAILURE; - } + const unsigned char* footer = addr + length - FOOTER_SIZE; - // Determine how much of the file is covered by the signature. - // This is everything except the signature data and length, which - // includes all of the EOCD except for the comment length field (2 - // bytes) and the comment data. - size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; + if (footer[2] != 0xff || footer[3] != 0xff) { + LOG(ERROR) << "footer is wrong"; + return VERIFY_FAILURE; + } - unsigned char* eocd = addr + length - eocd_size; + size_t comment_size = footer[4] + (footer[5] << 8); + size_t signature_start = footer[0] + (footer[1] << 8); + LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start + << " bytes from end"; - // If this is really is the EOCD record, it will begin with the - // magic number $50 $4b $05 $06. - if (eocd[0] != 0x50 || eocd[1] != 0x4b || - eocd[2] != 0x05 || eocd[3] != 0x06) { - LOGE("signature length doesn't match EOCD marker\n"); - return VERIFY_FAILURE; - } + if (signature_start > comment_size) { + LOG(ERROR) << "signature start: " << signature_start << " is larger than comment size: " + << comment_size; + return VERIFY_FAILURE; + } - for (size_t i = 4; i < eocd_size-3; ++i) { - if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && - eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { - // if the sequence $50 $4b $05 $06 appears anywhere after - // the real one, minzip will find the later (wrong) one, - // which could be exploitable. Fail verification if - // this sequence occurs anywhere after the real one. - LOGE("EOCD marker occurs after start of EOCD\n"); - return VERIFY_FAILURE; - } - } + if (signature_start <= FOOTER_SIZE) { + LOG(ERROR) << "Signature start is in the footer"; + return VERIFY_FAILURE; + } - bool need_sha1 = false; - bool need_sha256 = false; - for (const auto& key : keys) { - switch (key.hash_len) { - case SHA_DIGEST_LENGTH: need_sha1 = true; break; - case SHA256_DIGEST_LENGTH: need_sha256 = true; break; - } - } +#define EOCD_HEADER_SIZE 22 - SHA_CTX sha1_ctx; - SHA256_CTX sha256_ctx; - SHA1_Init(&sha1_ctx); - SHA256_Init(&sha256_ctx); - - double frac = -1.0; - size_t so_far = 0; - while (so_far < signed_len) { - // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a - // 1196MiB full OTA and 60% for an 89MiB incremental OTA. - // http://b/28135231. - size_t size = std::min(signed_len - so_far, 16 * MiB); - - if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); - if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); - so_far += size; - - double f = so_far / (double)signed_len; - if (f > frac + 0.02 || size == so_far) { - ui->SetProgress(f); - frac = f; - } - } + // The end-of-central-directory record is 22 bytes plus any comment length. + size_t eocd_size = comment_size + EOCD_HEADER_SIZE; - uint8_t sha1[SHA_DIGEST_LENGTH]; - SHA1_Final(sha1, &sha1_ctx); - uint8_t sha256[SHA256_DIGEST_LENGTH]; - SHA256_Final(sha256, &sha256_ctx); + if (length < eocd_size) { + LOG(ERROR) << "not big enough to contain EOCD"; + return VERIFY_FAILURE; + } - uint8_t* sig_der = nullptr; - size_t sig_der_length = 0; + // Determine how much of the file is covered by the signature. This is everything except the + // signature data and length, which includes all of the EOCD except for the comment length field + // (2 bytes) and the comment data. + size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2; - uint8_t* signature = eocd + eocd_size - signature_start; - size_t signature_size = signature_start - FOOTER_SIZE; + const unsigned char* eocd = addr + length - eocd_size; - LOGI("signature (offset: 0x%zx, length: %zu): %s\n", - length - signature_start, signature_size, - print_hex(signature, signature_size).c_str()); + // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06. + if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) { + LOG(ERROR) << "signature length doesn't match EOCD marker"; + return VERIFY_FAILURE; + } - if (!read_pkcs7(signature, signature_size, &sig_der, &sig_der_length)) { - LOGE("Could not find signature DER block\n"); - return VERIFY_FAILURE; + for (size_t i = 4; i < eocd_size-3; ++i) { + if (eocd[i] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { + // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will + // find the later (wrong) one, which could be exploitable. Fail the verification if this + // sequence occurs anywhere after the real one. + LOG(ERROR) << "EOCD marker occurs after start of EOCD"; + return VERIFY_FAILURE; } + } - /* - * Check to make sure at least one of the keys matches the signature. Since - * any key can match, we need to try each before determining a verification - * failure has happened. - */ - size_t i = 0; - for (const auto& key : keys) { - const uint8_t* hash; - int hash_nid; - switch (key.hash_len) { - case SHA_DIGEST_LENGTH: - hash = sha1; - hash_nid = NID_sha1; - break; - case SHA256_DIGEST_LENGTH: - hash = sha256; - hash_nid = NID_sha256; - break; - default: - continue; - } + bool need_sha1 = false; + bool need_sha256 = false; + for (const auto& key : keys) { + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: need_sha1 = true; break; + case SHA256_DIGEST_LENGTH: need_sha256 = true; break; + } + } - // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that - // the signing tool appends after the signature itself. - if (key.key_type == Certificate::KEY_TYPE_RSA) { - if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der, - sig_der_length, key.rsa.get())) { - LOGI("failed to verify against RSA key %zu\n", i); - continue; - } + SHA_CTX sha1_ctx; + SHA256_CTX sha256_ctx; + SHA1_Init(&sha1_ctx); + SHA256_Init(&sha256_ctx); + + double frac = -1.0; + size_t so_far = 0; + while (so_far < signed_len) { + // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a + // 1196MiB full OTA and 60% for an 89MiB incremental OTA. + // http://b/28135231. + size_t size = std::min(signed_len - so_far, 16 * MiB); + + if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); + if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); + so_far += size; + + if (set_progress) { + double f = so_far / (double)signed_len; + if (f > frac + 0.02 || size == so_far) { + set_progress(f); + frac = f; + } + } + } - LOGI("whole-file signature verified against RSA key %zu\n", i); - free(sig_der); - return VERIFY_SUCCESS; - } else if (key.key_type == Certificate::KEY_TYPE_EC - && key.hash_len == SHA256_DIGEST_LENGTH) { - if (!ECDSA_verify(0, hash, key.hash_len, sig_der, - sig_der_length, key.ec.get())) { - LOGI("failed to verify against EC key %zu\n", i); - continue; - } + uint8_t sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(sha1, &sha1_ctx); + uint8_t sha256[SHA256_DIGEST_LENGTH]; + SHA256_Final(sha256, &sha256_ctx); - LOGI("whole-file signature verified against EC key %zu\n", i); - free(sig_der); - return VERIFY_SUCCESS; - } else { - LOGI("Unknown key type %d\n", key.key_type); - } - i++; - } + const uint8_t* signature = eocd + eocd_size - signature_start; + size_t signature_size = signature_start - FOOTER_SIZE; - if (need_sha1) { - LOGI("SHA-1 digest: %s\n", print_hex(sha1, SHA_DIGEST_LENGTH).c_str()); - } - if (need_sha256) { - LOGI("SHA-256 digest: %s\n", print_hex(sha256, SHA256_DIGEST_LENGTH).c_str()); - } - free(sig_der); - LOGE("failed to verify whole-file signature\n"); + LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: " + << signature_size << "): " << print_hex(signature, signature_size); + + std::vector<uint8_t> sig_der; + if (!read_pkcs7(signature, signature_size, &sig_der)) { + LOG(ERROR) << "Could not find signature DER block"; return VERIFY_FAILURE; + } + + // Check to make sure at least one of the keys matches the signature. Since any key can match, + // we need to try each before determining a verification failure has happened. + size_t i = 0; + for (const auto& key : keys) { + const uint8_t* hash; + int hash_nid; + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: + hash = sha1; + hash_nid = NID_sha1; + break; + case SHA256_DIGEST_LENGTH: + hash = sha256; + hash_nid = NID_sha256; + break; + default: + continue; + } + + // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends + // after the signature itself. + if (key.key_type == Certificate::KEY_TYPE_RSA) { + if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(), + key.rsa.get())) { + LOG(INFO) << "failed to verify against RSA key " << i; + continue; + } + + LOG(INFO) << "whole-file signature verified against RSA key " << i; + return VERIFY_SUCCESS; + } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) { + if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) { + LOG(INFO) << "failed to verify against EC key " << i; + continue; + } + + LOG(INFO) << "whole-file signature verified against EC key " << i; + return VERIFY_SUCCESS; + } else { + LOG(INFO) << "Unknown key type " << key.key_type; + } + i++; + } + + if (need_sha1) { + LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH); + } + if (need_sha256) { + LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH); + } + LOG(ERROR) << "failed to verify whole-file signature"; + return VERIFY_FAILURE; } std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { @@ -328,7 +320,7 @@ std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { } if (key_len_words > 8192 / 32) { - LOGE("key length (%d) too large\n", key_len_words); + LOG(ERROR) << "key length (" << key_len_words << ") too large"; return nullptr; } @@ -384,7 +376,7 @@ std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { } struct BNDeleter { - void operator()(BIGNUM* bn) { + void operator()(BIGNUM* bn) const { BN_free(bn); } }; @@ -484,7 +476,7 @@ std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) { bool load_keys(const char* filename, std::vector<Certificate>& certs) { std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose); if (!f) { - LOGE("opening %s: %s\n", filename, strerror(errno)); + PLOG(ERROR) << "error opening " << filename; return false; } @@ -534,14 +526,14 @@ bool load_keys(const char* filename, std::vector<Certificate>& certs) { return false; } - LOGI("read key e=%d hash=%d\n", exponent, cert.hash_len); + LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len; } else if (cert.key_type == Certificate::KEY_TYPE_EC) { cert.ec = parse_ec_key(f.get()); if (!cert.ec) { return false; } } else { - LOGE("Unknown key type %d\n", cert.key_type); + LOG(ERROR) << "Unknown key type " << cert.key_type; return false; } @@ -553,7 +545,7 @@ bool load_keys(const char* filename, std::vector<Certificate>& certs) { } else if (ch == EOF) { break; } else { - LOGE("unexpected character between keys\n"); + LOG(ERROR) << "unexpected character between keys"; return false; } } diff --git a/verifier.h b/verifier.h index 58083fe14..6fa8f2b0a 100644 --- a/verifier.h +++ b/verifier.h @@ -17,6 +17,7 @@ #ifndef _RECOVERY_VERIFIER_H #define _RECOVERY_VERIFIER_H +#include <functional> #include <memory> #include <vector> @@ -25,13 +26,13 @@ #include <openssl/sha.h> struct RSADeleter { - void operator()(RSA* rsa) { + void operator()(RSA* rsa) const { RSA_free(rsa); } }; struct ECKEYDeleter { - void operator()(EC_KEY* ec_key) { + void operator()(EC_KEY* ec_key) const { EC_KEY_free(ec_key); } }; @@ -58,13 +59,14 @@ struct Certificate { std::unique_ptr<EC_KEY, ECKEYDeleter> ec; }; -/* addr and length define a an update package file that has been - * loaded (or mmap'ed, or whatever) into memory. Verify that the file - * is signed and the signature matches one of the given keys. Return - * one of the constants below. +/* + * 'addr' and 'length' define an update package file that has been loaded (or mmap'ed, or + * whatever) into memory. Verifies that the file is signed and the signature matches one of the + * given keys. It optionally accepts a callback function for posting the progress to. Returns one + * of the constants of VERIFY_SUCCESS and VERIFY_FAILURE. */ -int verify_file(unsigned char* addr, size_t length, - const std::vector<Certificate>& keys); +int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys, + const std::function<void(float)>& set_progress = nullptr); bool load_keys(const char* filename, std::vector<Certificate>& certs); diff --git a/wear_touch.cpp b/wear_touch.cpp index f22d40b88..e2ab44d2d 100644 --- a/wear_touch.cpp +++ b/wear_touch.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#include "common.h" -#include "wear_touch.h" - #include <dirent.h> #include <fcntl.h> #include <stdio.h> @@ -25,8 +22,11 @@ #include <errno.h> #include <string.h> +#include <android-base/logging.h> #include <linux/input.h> +#include "wear_touch.h" + #define DEVICE_PATH "/dev/input" WearSwipeDetector::WearSwipeDetector(int low, int high, OnSwipeCallback callback, void* cookie): @@ -49,11 +49,11 @@ void WearSwipeDetector::detect(int dx, int dy) { } else if (abs(dx) < mLowThreshold && abs(dy) > mHighThreshold) { direction = dy < 0 ? UP : DOWN; } else { - LOGD("Ignore %d %d\n", dx, dy); + LOG(DEBUG) << "Ignore " << dx << " " << dy; return; } - LOGD("Swipe direction=%d\n", direction); + LOG(DEBUG) << "Swipe direction=" << direction; mCallback(mCookie, direction); } @@ -105,7 +105,7 @@ void WearSwipeDetector::process(struct input_event *event) { void WearSwipeDetector::run() { int fd = findDevice(DEVICE_PATH); if (fd < 0) { - LOGE("no input devices found\n"); + LOG(ERROR) << "no input devices found"; return; } @@ -118,23 +118,23 @@ void WearSwipeDetector::run() { } void* WearSwipeDetector::touch_thread(void* cookie) { - ((WearSwipeDetector*)cookie)->run(); + (static_cast<WearSwipeDetector*>(cookie))->run(); return NULL; } -#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) +#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)%8))) int WearSwipeDetector::openDevice(const char *device) { int fd = open(device, O_RDONLY); if (fd < 0) { - LOGE("could not open %s, %s\n", device, strerror(errno)); + PLOG(ERROR) << "could not open " << device; return false; } char name[80]; name[sizeof(name) - 1] = '\0'; if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) { - LOGE("could not get device name for %s, %s\n", device, strerror(errno)); + PLOG(ERROR) << "could not get device name for " << device; name[0] = '\0'; } @@ -143,7 +143,7 @@ int WearSwipeDetector::openDevice(const char *device) { int ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits); if (ret > 0) { if (test_bit(ABS_MT_POSITION_X, bits) && test_bit(ABS_MT_POSITION_Y, bits)) { - LOGD("Found %s %s\n", device, name); + LOG(DEBUG) << "Found " << device << " " << name; return fd; } } @@ -155,7 +155,7 @@ int WearSwipeDetector::openDevice(const char *device) { int WearSwipeDetector::findDevice(const char* path) { DIR* dir = opendir(path); if (dir == NULL) { - LOGE("Could not open directory %s", path); + PLOG(ERROR) << "Could not open directory " << path; return false; } diff --git a/wear_ui.cpp b/wear_ui.cpp index 660a078fc..6c0286558 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "wear_ui.h" + #include <errno.h> #include <fcntl.h> #include <stdarg.h> @@ -25,14 +27,16 @@ #include <time.h> #include <unistd.h> +#include <string> #include <vector> +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <android-base/stringprintf.h> +#include <minui/minui.h> + #include "common.h" #include "device.h" -#include "wear_ui.h" -#include "cutils/properties.h" -#include "android-base/strings.h" -#include "android-base/stringprintf.h" // There's only (at most) one of these objects, and global callbacks // (for pthread_create, and the input event system) need to find it, @@ -119,8 +123,8 @@ void WearRecoveryUI::draw_screen_locked() int y = outer_height; int x = outer_width; if (show_menu) { - char recovery_fingerprint[PROPERTY_VALUE_MAX]; - property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); + std::string recovery_fingerprint = + android::base::GetProperty("ro.bootimage.build.fingerprint", ""); SetColor(HEADER); DrawTextLine(x + 4, &y, "Android Recovery", true); for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { @@ -190,8 +194,10 @@ void WearRecoveryUI::update_progress_locked() { gr_flip(); } -void WearRecoveryUI::InitTextParams() { - ScreenRecoveryUI::InitTextParams(); +bool WearRecoveryUI::InitTextParams() { + if (!ScreenRecoveryUI::InitTextParams()) { + return false; + } text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_; @@ -199,49 +205,51 @@ void WearRecoveryUI::InitTextParams() { if (text_cols_ > kMaxCols) text_cols_ = kMaxCols; visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_; + return true; } -void WearRecoveryUI::Init() { - ScreenRecoveryUI::Init(); +bool WearRecoveryUI::Init(const std::string& locale) { + if (!ScreenRecoveryUI::Init(locale)) { + return false; + } - LoadBitmap("icon_error", &backgroundIcon[ERROR]); - backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + LoadBitmap("icon_error", &backgroundIcon[ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; - // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING] - // as NULL which is fine since draw_background_locked() doesn't use them. -} + // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING] + // as NULL which is fine since draw_background_locked() doesn't use them. -void WearRecoveryUI::SetStage(int current, int max) -{ + return true; } -void WearRecoveryUI::Print(const char *fmt, ...) -{ - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, 256, fmt, ap); - va_end(ap); +void WearRecoveryUI::SetStage(int current, int max) {} - fputs(buf, stdout); +void WearRecoveryUI::Print(const char* fmt, ...) { + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, 256, fmt, ap); + va_end(ap); - // This can get called before ui_init(), so be careful. - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - char *ptr; - for (ptr = buf; *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { - text_[text_row_][text_col_] = '\0'; - text_col_ = 0; - text_row_ = (text_row_ + 1) % text_rows_; - if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } + fputs(buf, stdout); + + // This can get called before ui_init(), so be careful. + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + char* ptr; + for (ptr = buf; *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col_ >= text_cols_) { text_[text_row_][text_col_] = '\0'; - update_screen_locked(); + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } - pthread_mutex_unlock(&updateMutex); + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items, @@ -294,8 +302,8 @@ int WearRecoveryUI::SelectMenu(int sel) { } void WearRecoveryUI::ShowFile(FILE* fp) { - std::vector<long> offsets; - offsets.push_back(ftell(fp)); + std::vector<off_t> offsets; + offsets.push_back(ftello(fp)); ClearText(); struct stat sb; @@ -305,7 +313,7 @@ void WearRecoveryUI::ShowFile(FILE* fp) { while (true) { if (show_prompt) { Print("--(%d%% of %d bytes)--", - static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))), + static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), static_cast<int>(sb.st_size)); Redraw(); while (show_prompt) { @@ -324,7 +332,7 @@ void WearRecoveryUI::ShowFile(FILE* fp) { if (feof(fp)) { return; } - offsets.push_back(ftell(fp)); + offsets.push_back(ftello(fp)); } } ClearText(); @@ -19,11 +19,13 @@ #include "screen_ui.h" +#include <string> + class WearRecoveryUI : public ScreenRecoveryUI { public: WearRecoveryUI(); - void Init() override; + bool Init(const std::string& locale) override; void SetStage(int current, int max) override; @@ -52,7 +54,7 @@ class WearRecoveryUI : public ScreenRecoveryUI { int GetProgressBaseline() override; - void InitTextParams() override; + bool InitTextParams() override; void update_progress_locked() override; |